glib/gio/inotify/inotify-helper.c
Ryan Lortie c5d5e2916b inotify: implement "boredom" algorithm
Use the "interesting" value from g_file_monitor_source_handle_event() to
decide if we're currently being flooded by a stream of boring events.

The main case here is when one or more files is being written to and the
change events are all being rate-limited in the GFileMonitor frontends.

In that case, we become "bored" with the event stream and add a backoff
timeout.  In the case that it is exactly one large file being written
(which is the common case) then leaving the event in the queue also lets
the kernel perform merging on it, so when we wake up, we will only see
the one event.  Even in the case that the kernel is unable to perform
merging, the context switch overhead will be vastly reduced.

In testing, this cuts down on the number of wake ups during a large file
copy, by a couple orders of magnitude (ie: less than 1% of the number of
wake ups).
2015-03-20 12:01:35 -04:00

284 lines
7.8 KiB
C

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
/* inotify-helper.c - GVFS Monitor based on inotify.
Copyright (C) 2007 John McCutchan
The Gnome Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
see <http://www.gnu.org/licenses/>.
Authors:
John McCutchan <john@johnmccutchan.com>
*/
#include "config.h"
#include <errno.h>
#include <time.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
/* Just include the local header to stop all the pain */
#include <sys/inotify.h>
#include <gio/glocalfilemonitor.h>
#include <gio/gfile.h>
#include "inotify-helper.h"
#include "inotify-missing.h"
#include "inotify-path.h"
static gboolean ih_debug_enabled = FALSE;
#define IH_W if (ih_debug_enabled) g_warning
static gboolean ih_event_callback (ik_event_t *event,
inotify_sub *sub,
gboolean file_event);
static void ih_not_missing_callback (inotify_sub *sub);
/* We share this lock with inotify-kernel.c and inotify-missing.c
*
* inotify-kernel.c takes the lock when it reads events from
* the kernel and when it processes those events
*
* inotify-missing.c takes the lock when it is scanning the missing
* list.
*
* We take the lock in all public functions
*/
G_LOCK_DEFINE (inotify_lock);
static GFileMonitorEvent ih_mask_to_EventFlags (guint32 mask);
/**
* _ih_startup:
*
* Initializes the inotify backend. This must be called before
* any other functions in this module.
*
* Returns: #TRUE if initialization succeeded, #FALSE otherwise
*/
gboolean
_ih_startup (void)
{
static gboolean initialized = FALSE;
static gboolean result = FALSE;
G_LOCK (inotify_lock);
if (initialized == TRUE)
{
G_UNLOCK (inotify_lock);
return result;
}
result = _ip_startup (ih_event_callback);
if (!result)
{
G_UNLOCK (inotify_lock);
return FALSE;
}
_im_startup (ih_not_missing_callback);
IH_W ("started gvfs inotify backend\n");
initialized = TRUE;
G_UNLOCK (inotify_lock);
return TRUE;
}
/*
* Adds a subscription to be monitored.
*/
gboolean
_ih_sub_add (inotify_sub *sub)
{
G_LOCK (inotify_lock);
if (!_ip_start_watching (sub))
_im_add (sub);
G_UNLOCK (inotify_lock);
return TRUE;
}
/*
* Cancels a subscription which was being monitored.
*/
gboolean
_ih_sub_cancel (inotify_sub *sub)
{
G_LOCK (inotify_lock);
if (!sub->cancelled)
{
IH_W ("cancelling %s\n", sub->dirname);
sub->cancelled = TRUE;
_im_rm (sub);
_ip_stop_watching (sub);
}
G_UNLOCK (inotify_lock);
return TRUE;
}
static char *
_ih_fullpath_from_event (ik_event_t *event,
const char *dirname,
const char *filename)
{
char *fullpath;
if (filename)
fullpath = g_strdup_printf ("%s/%s", dirname, filename);
else if (event->name)
fullpath = g_strdup_printf ("%s/%s", dirname, event->name);
else
fullpath = g_strdup_printf ("%s/", dirname);
return fullpath;
}
static gboolean
ih_event_callback (ik_event_t *event,
inotify_sub *sub,
gboolean file_event)
{
gboolean interesting;
g_assert (!file_event); /* XXX hardlink support */
if (event->mask & IN_MOVE)
{
/* We either have a rename (in the same directory) or a move
* (between different directories).
*/
if (event->pair && event->pair->wd == event->wd)
{
/* this is a rename */
interesting = g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_RENAMED,
event->name, event->pair->name, NULL, event->timestamp);
}
else
{
GFile *other;
if (event->pair)
{
const char *parent_dir;
gchar *fullpath;
parent_dir = _ip_get_path_for_wd (event->pair->wd);
fullpath = _ih_fullpath_from_event (event->pair, parent_dir, NULL);
other = g_file_new_for_path (fullpath);
g_free (fullpath);
}
else
other = NULL;
/* this is either an incoming or outgoing move */
interesting = g_file_monitor_source_handle_event (sub->user_data, ih_mask_to_EventFlags (event->mask),
event->name, NULL, other, event->timestamp);
if (other)
g_object_unref (other);
}
}
else
/* unpaired event -- no 'other' field */
interesting = g_file_monitor_source_handle_event (sub->user_data, ih_mask_to_EventFlags (event->mask),
event->name, NULL, NULL, event->timestamp);
if (event->mask & IN_CREATE)
{
const gchar *parent_dir;
gchar *fullname;
struct stat buf;
gint s;
/* The kernel reports IN_CREATE for two types of events:
*
* - creat(), in which case IN_CLOSE_WRITE will come soon; or
* - link(), mkdir(), mknod(), etc., in which case it won't
*
* We can attempt to detect the second case and send the
* CHANGES_DONE immediately so that the user isn't left waiting.
*
* The detection for link() is not 100% reliable since the link
* count could be 1 if the original link was deleted or if
* O_TMPFILE was being used, but in that case the virtual
* CHANGES_DONE will be emitted to close the loop.
*/
parent_dir = _ip_get_path_for_wd (event->wd);
fullname = _ih_fullpath_from_event (event, parent_dir, NULL);
s = stat (fullname, &buf);
g_free (fullname);
/* if it doesn't look like the result of creat()... */
if (s != 0 || !S_ISREG (buf.st_mode) || buf.st_nlink != 1)
g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
event->name, NULL, NULL, event->timestamp);
}
return interesting;
}
static void
ih_not_missing_callback (inotify_sub *sub)
{
gint now = g_get_monotonic_time ();
g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CREATED,
sub->filename, NULL, NULL, now);
g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
sub->filename, NULL, NULL, now);
}
/* Transforms a inotify event to a GVFS event. */
static GFileMonitorEvent
ih_mask_to_EventFlags (guint32 mask)
{
mask &= ~IN_ISDIR;
switch (mask)
{
case IN_MODIFY:
return G_FILE_MONITOR_EVENT_CHANGED;
case IN_CLOSE_WRITE:
return G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT;
case IN_ATTRIB:
return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
case IN_MOVE_SELF:
case IN_DELETE:
case IN_DELETE_SELF:
return G_FILE_MONITOR_EVENT_DELETED;
case IN_CREATE:
return G_FILE_MONITOR_EVENT_CREATED;
case IN_MOVED_FROM:
return G_FILE_MONITOR_EVENT_MOVED_OUT;
case IN_MOVED_TO:
return G_FILE_MONITOR_EVENT_MOVED_IN;
case IN_UNMOUNT:
return G_FILE_MONITOR_EVENT_UNMOUNTED;
case IN_Q_OVERFLOW:
case IN_OPEN:
case IN_CLOSE_NOWRITE:
case IN_ACCESS:
case IN_IGNORED:
default:
return -1;
}
}