mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-03-15 04:05:11 +01:00
inotify: rewrite inotify-kernel
Remove the hardwired 1 second event queue logic from inotify-kernel and replace it with something vastly less complicated. Events are now reported as soon as is possible instead of after a delay. We still must delay IN_MOVED_FROM events in order to look for the matching IN_MOVED_TO events, and since we want to report events in order this means that events behind those events can also be delayed. We limit ourselves, however: - no more than 100 events can be delayed at a time - no event can be delayed by more than 10ms https://bugzilla.gnome.org/show_bug.cgi?id=627285
This commit is contained in:
parent
fd8b45eb67
commit
779c809a3d
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (C) 2005 John McCutchan
|
Copyright (C) 2005 John McCutchan
|
||||||
|
Copyright © 2015 Canonical Limited
|
||||||
|
|
||||||
The Gnome Library is free software; you can redistribute it and/or
|
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
|
modify it under the terms of the GNU Library General Public License as
|
||||||
@ -15,7 +16,8 @@
|
|||||||
License along with the Gnome Library; see the file COPYING.LIB. If not,
|
License along with the Gnome Library; see the file COPYING.LIB. If not,
|
||||||
see <http://www.gnu.org/licenses/>.
|
see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Authors:.
|
Authors:
|
||||||
|
Ryan Lortie <desrt@desrt.ca>
|
||||||
John McCutchan <john@johnmccutchan.com>
|
John McCutchan <john@johnmccutchan.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -29,229 +31,35 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include "inotify-kernel.h"
|
#include "inotify-kernel.h"
|
||||||
#include <sys/inotify.h>
|
#include <sys/inotify.h>
|
||||||
|
#include <glib/glib-unix.h>
|
||||||
|
|
||||||
#include "glib-private.h"
|
#include "glib-private.h"
|
||||||
|
|
||||||
/* Timings for pairing MOVED_TO / MOVED_FROM events */
|
/* Define limits on the maximum amount of time and maximum amount of
|
||||||
#define PROCESS_EVENTS_TIME 1000 /* 1000 milliseconds (1 hz) */
|
* interceding events between FROM/TO that can be merged.
|
||||||
#define DEFAULT_HOLD_UNTIL_TIME 0 /* 0 millisecond */
|
*/
|
||||||
#define MOVE_HOLD_UNTIL_TIME 500 /* 500 microseconds or 0.5 milliseconds */
|
#define MOVE_PAIR_DELAY (10 * G_TIME_SPAN_MILLISECOND)
|
||||||
|
#define MOVE_PAIR_DISTANCE (100)
|
||||||
static int inotify_instance_fd = -1;
|
|
||||||
static GQueue *events_to_process = NULL;
|
|
||||||
static GQueue *event_queue = NULL;
|
|
||||||
static GHashTable * cookie_hash = NULL;
|
|
||||||
static GIOChannel *inotify_read_ioc;
|
|
||||||
static GPollFD ik_poll_fd;
|
|
||||||
static gboolean ik_poll_fd_enabled = TRUE;
|
|
||||||
static void (*user_cb)(ik_event_t *event);
|
|
||||||
|
|
||||||
static gboolean ik_read_callback (gpointer user_data);
|
|
||||||
static gboolean ik_process_eq_callback (gpointer user_data);
|
|
||||||
|
|
||||||
static guint32 ik_move_matches = 0;
|
|
||||||
static guint32 ik_move_misses = 0;
|
|
||||||
|
|
||||||
static gboolean process_eq_running = FALSE;
|
|
||||||
|
|
||||||
/* We use the lock from inotify-helper.c
|
/* We use the lock from inotify-helper.c
|
||||||
*
|
*
|
||||||
* There are two places that we take this lock
|
* We only have to take it on our read callback.
|
||||||
*
|
|
||||||
* 1) In ik_read_callback
|
|
||||||
*
|
|
||||||
* 2) ik_process_eq_callback.
|
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* The rest of locking is taken care of in inotify-helper.c
|
* The rest of locking is taken care of in inotify-helper.c
|
||||||
*/
|
*/
|
||||||
G_LOCK_EXTERN (inotify_lock);
|
G_LOCK_EXTERN (inotify_lock);
|
||||||
|
|
||||||
typedef struct ik_event_internal {
|
|
||||||
ik_event_t *event;
|
|
||||||
gboolean seen;
|
|
||||||
gboolean sent;
|
|
||||||
GTimeVal hold_until;
|
|
||||||
struct ik_event_internal *pair;
|
|
||||||
} ik_event_internal_t;
|
|
||||||
|
|
||||||
/* In order to perform non-sleeping inotify event chunking we need
|
|
||||||
* a custom GSource
|
|
||||||
*/
|
|
||||||
static gboolean
|
|
||||||
ik_source_prepare (GSource *source,
|
|
||||||
gint *timeout)
|
|
||||||
{
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
ik_source_timeout (gpointer data)
|
|
||||||
{
|
|
||||||
GSource *source = (GSource *)data;
|
|
||||||
|
|
||||||
/* Re-active the PollFD */
|
|
||||||
g_source_add_poll (source, &ik_poll_fd);
|
|
||||||
g_source_unref (source);
|
|
||||||
ik_poll_fd_enabled = TRUE;
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define MAX_PENDING_COUNT 2
|
|
||||||
#define PENDING_THRESHOLD(qsize) ((qsize) >> 1)
|
|
||||||
#define PENDING_MARGINAL_COST(p) ((unsigned int)(1 << (p)))
|
|
||||||
#define MAX_QUEUED_EVENTS 2048
|
|
||||||
#define AVERAGE_EVENT_SIZE sizeof (struct inotify_event) + 16
|
|
||||||
#define TIMEOUT_MILLISECONDS 10
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
ik_source_check (GSource *source)
|
|
||||||
{
|
|
||||||
static int prev_pending = 0, pending_count = 0;
|
|
||||||
|
|
||||||
/* We already disabled the PollFD or
|
|
||||||
* nothing to be read from inotify */
|
|
||||||
if (!ik_poll_fd_enabled || !(ik_poll_fd.revents & G_IO_IN))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
if (pending_count < MAX_PENDING_COUNT)
|
|
||||||
{
|
|
||||||
GSource *timeout_source;
|
|
||||||
unsigned int pending;
|
|
||||||
|
|
||||||
if (ioctl (inotify_instance_fd, FIONREAD, &pending) == -1)
|
|
||||||
goto do_read;
|
|
||||||
|
|
||||||
pending /= AVERAGE_EVENT_SIZE;
|
|
||||||
|
|
||||||
/* Don't wait if the number of pending events is too close
|
|
||||||
* to the maximum queue size.
|
|
||||||
*/
|
|
||||||
if (pending > PENDING_THRESHOLD (MAX_QUEUED_EVENTS))
|
|
||||||
goto do_read;
|
|
||||||
|
|
||||||
/* With each successive iteration, the minimum rate for
|
|
||||||
* further sleep doubles.
|
|
||||||
*/
|
|
||||||
if (pending-prev_pending < PENDING_MARGINAL_COST (pending_count))
|
|
||||||
goto do_read;
|
|
||||||
|
|
||||||
prev_pending = pending;
|
|
||||||
pending_count++;
|
|
||||||
|
|
||||||
/* We are going to wait to read the events: */
|
|
||||||
|
|
||||||
/* Remove the PollFD from the source */
|
|
||||||
g_source_remove_poll (source, &ik_poll_fd);
|
|
||||||
/* To avoid threading issues we need to flag that we've done that */
|
|
||||||
ik_poll_fd_enabled = FALSE;
|
|
||||||
/* Set a timeout to re-add the PollFD to the source */
|
|
||||||
g_source_ref (source);
|
|
||||||
|
|
||||||
timeout_source = g_timeout_source_new (TIMEOUT_MILLISECONDS);
|
|
||||||
g_source_set_callback (timeout_source, ik_source_timeout, source, NULL);
|
|
||||||
g_source_attach (timeout_source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
|
|
||||||
g_source_unref (timeout_source);
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
do_read:
|
|
||||||
/* We are ready to read events from inotify */
|
|
||||||
|
|
||||||
prev_pending = 0;
|
|
||||||
pending_count = 0;
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
ik_source_dispatch (GSource *source,
|
|
||||||
GSourceFunc callback,
|
|
||||||
gpointer user_data)
|
|
||||||
{
|
|
||||||
if (callback)
|
|
||||||
return callback (user_data);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static GSourceFuncs ik_source_funcs =
|
|
||||||
{
|
|
||||||
ik_source_prepare,
|
|
||||||
ik_source_check,
|
|
||||||
ik_source_dispatch,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
gboolean _ik_startup (void (*cb)(ik_event_t *event))
|
|
||||||
{
|
|
||||||
static gboolean initialized = FALSE;
|
|
||||||
GSource *source;
|
|
||||||
|
|
||||||
user_cb = cb;
|
|
||||||
/* Ignore multi-calls */
|
|
||||||
if (initialized)
|
|
||||||
return inotify_instance_fd >= 0;
|
|
||||||
|
|
||||||
initialized = TRUE;
|
|
||||||
|
|
||||||
inotify_instance_fd = inotify_init1 (IN_CLOEXEC);
|
|
||||||
|
|
||||||
if (inotify_instance_fd < 0)
|
|
||||||
inotify_instance_fd = inotify_init ();
|
|
||||||
|
|
||||||
if (inotify_instance_fd < 0)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
inotify_read_ioc = g_io_channel_unix_new (inotify_instance_fd);
|
|
||||||
ik_poll_fd.fd = inotify_instance_fd;
|
|
||||||
ik_poll_fd.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
|
|
||||||
g_io_channel_set_encoding (inotify_read_ioc, NULL, NULL);
|
|
||||||
g_io_channel_set_flags (inotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL);
|
|
||||||
|
|
||||||
source = g_source_new (&ik_source_funcs, sizeof (GSource));
|
|
||||||
g_source_set_name (source, "GIO Inotify");
|
|
||||||
g_source_add_poll (source, &ik_poll_fd);
|
|
||||||
g_source_set_callback (source, ik_read_callback, NULL, NULL);
|
|
||||||
g_source_attach (source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
|
|
||||||
g_source_unref (source);
|
|
||||||
|
|
||||||
cookie_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
||||||
event_queue = g_queue_new ();
|
|
||||||
events_to_process = g_queue_new ();
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ik_event_internal_t *
|
|
||||||
ik_event_internal_new (ik_event_t *event)
|
|
||||||
{
|
|
||||||
ik_event_internal_t *internal_event = g_new0 (ik_event_internal_t, 1);
|
|
||||||
GTimeVal tv;
|
|
||||||
|
|
||||||
g_assert (event);
|
|
||||||
|
|
||||||
g_get_current_time (&tv);
|
|
||||||
g_time_val_add (&tv, DEFAULT_HOLD_UNTIL_TIME);
|
|
||||||
internal_event->event = event;
|
|
||||||
internal_event->hold_until = tv;
|
|
||||||
|
|
||||||
return internal_event;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ik_event_t *
|
static ik_event_t *
|
||||||
ik_event_new (char *buffer)
|
ik_event_new (struct inotify_event *kevent,
|
||||||
|
gint64 now)
|
||||||
{
|
{
|
||||||
struct inotify_event *kevent = (struct inotify_event *)buffer;
|
|
||||||
ik_event_t *event = g_new0 (ik_event_t, 1);
|
ik_event_t *event = g_new0 (ik_event_t, 1);
|
||||||
|
|
||||||
g_assert (buffer);
|
|
||||||
|
|
||||||
event->wd = kevent->wd;
|
event->wd = kevent->wd;
|
||||||
event->mask = kevent->mask;
|
event->mask = kevent->mask;
|
||||||
event->cookie = kevent->cookie;
|
event->cookie = kevent->cookie;
|
||||||
event->len = kevent->len;
|
event->len = kevent->len;
|
||||||
|
event->timestamp = now;
|
||||||
if (event->len)
|
if (event->len)
|
||||||
event->name = g_strdup (kevent->name);
|
event->name = g_strdup (kevent->name);
|
||||||
else
|
else
|
||||||
@ -265,10 +73,184 @@ _ik_event_free (ik_event_t *event)
|
|||||||
{
|
{
|
||||||
if (event->pair)
|
if (event->pair)
|
||||||
_ik_event_free (event->pair);
|
_ik_event_free (event->pair);
|
||||||
|
|
||||||
g_free (event->name);
|
g_free (event->name);
|
||||||
g_free (event);
|
g_free (event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GSource source;
|
||||||
|
|
||||||
|
gpointer fd_tag;
|
||||||
|
GQueue queue;
|
||||||
|
gint fd;
|
||||||
|
} InotifyKernelSource;
|
||||||
|
|
||||||
|
static InotifyKernelSource *inotify_source;
|
||||||
|
|
||||||
|
static gint64
|
||||||
|
ik_source_get_ready_time (InotifyKernelSource *iks)
|
||||||
|
{
|
||||||
|
ik_event_t *head;
|
||||||
|
|
||||||
|
head = g_queue_peek_head (&iks->queue);
|
||||||
|
|
||||||
|
/* nothing in the queue: not ready */
|
||||||
|
if (!head)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* if it's not an unpaired move, it is ready now */
|
||||||
|
if (~head->mask & IN_MOVED_FROM || head->pair)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* if the queue is too long then it's ready now */
|
||||||
|
if (iks->queue.length > MOVE_PAIR_DISTANCE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* otherwise, it's ready after the delay */
|
||||||
|
return head->timestamp + MOVE_PAIR_DELAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
ik_source_can_dispatch_now (InotifyKernelSource *iks,
|
||||||
|
gint64 now)
|
||||||
|
{
|
||||||
|
gint64 ready_time;
|
||||||
|
|
||||||
|
ready_time = ik_source_get_ready_time (iks);
|
||||||
|
|
||||||
|
return 0 <= ready_time && ready_time <= now;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ik_source_try_to_pair_head (InotifyKernelSource *iks)
|
||||||
|
{
|
||||||
|
ik_event_t *head;
|
||||||
|
GList *node;
|
||||||
|
|
||||||
|
node = g_queue_peek_head_link (&iks->queue);
|
||||||
|
|
||||||
|
if (!node)
|
||||||
|
return;
|
||||||
|
|
||||||
|
head = node->data;
|
||||||
|
|
||||||
|
/* we should only be here if... */
|
||||||
|
g_assert (head->mask & IN_MOVED_FROM && !head->pair);
|
||||||
|
|
||||||
|
while ((node = node->next))
|
||||||
|
{
|
||||||
|
ik_event_t *candidate = node->data;
|
||||||
|
|
||||||
|
if (candidate->cookie == head->cookie && candidate->mask & IN_MOVED_TO)
|
||||||
|
{
|
||||||
|
g_queue_remove (&iks->queue, candidate);
|
||||||
|
candidate->is_second_in_pair = TRUE;
|
||||||
|
head->pair = candidate;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
ik_source_dispatch (GSource *source,
|
||||||
|
GSourceFunc func,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
InotifyKernelSource *iks = (InotifyKernelSource *) source;
|
||||||
|
void (*user_callback) (ik_event_t *event) = (void *) func;
|
||||||
|
gint64 now = g_source_get_time (source);
|
||||||
|
|
||||||
|
now = g_source_get_time (source);
|
||||||
|
|
||||||
|
/* Only try to fill the queue if we don't already have work to do. */
|
||||||
|
if (!ik_source_can_dispatch_now (iks, now) &&
|
||||||
|
g_source_query_unix_fd (source, iks->fd_tag))
|
||||||
|
{
|
||||||
|
gchar buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
|
||||||
|
gssize result;
|
||||||
|
gssize offset;
|
||||||
|
|
||||||
|
result = read (iks->fd, buffer, sizeof buffer);
|
||||||
|
|
||||||
|
if (result < 0)
|
||||||
|
g_error ("inotify error: %s\n", g_strerror (errno));
|
||||||
|
else if (result == 0)
|
||||||
|
g_error ("inotify unexpectedly hit eof");
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
while (offset < result)
|
||||||
|
{
|
||||||
|
struct inotify_event *event = (struct inotify_event *) (buffer + offset);
|
||||||
|
|
||||||
|
g_queue_push_tail (&iks->queue, ik_event_new (event, now));
|
||||||
|
|
||||||
|
offset += sizeof (struct inotify_event) + event->len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ik_source_can_dispatch_now (iks, now))
|
||||||
|
ik_source_try_to_pair_head (iks);
|
||||||
|
|
||||||
|
if (ik_source_can_dispatch_now (iks, now))
|
||||||
|
{
|
||||||
|
ik_event_t *event;
|
||||||
|
|
||||||
|
/* callback will free the event */
|
||||||
|
event = g_queue_pop_head (&iks->queue);
|
||||||
|
|
||||||
|
G_LOCK (inotify_lock);
|
||||||
|
(* user_callback) (event);
|
||||||
|
G_UNLOCK (inotify_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_source_set_ready_time (source, ik_source_get_ready_time (iks));
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static InotifyKernelSource *
|
||||||
|
ik_source_new (void (* callback) (ik_event_t *event))
|
||||||
|
{
|
||||||
|
static GSourceFuncs source_funcs = {
|
||||||
|
NULL, NULL,
|
||||||
|
ik_source_dispatch
|
||||||
|
/* should have a finalize, but it will never happen */
|
||||||
|
};
|
||||||
|
InotifyKernelSource *iks;
|
||||||
|
GSource *source;
|
||||||
|
|
||||||
|
source = g_source_new (&source_funcs, sizeof (InotifyKernelSource));
|
||||||
|
iks = (InotifyKernelSource *) source;
|
||||||
|
|
||||||
|
g_source_set_name (source, "inotify kernel source");
|
||||||
|
|
||||||
|
iks->fd = inotify_init1 (IN_CLOEXEC);
|
||||||
|
|
||||||
|
if (iks->fd < 0)
|
||||||
|
iks->fd = inotify_init ();
|
||||||
|
|
||||||
|
if (iks->fd >= 0)
|
||||||
|
iks->fd_tag = g_source_add_unix_fd (source, iks->fd, G_IO_IN);
|
||||||
|
|
||||||
|
g_source_set_callback (source, (GSourceFunc) callback, NULL, NULL);
|
||||||
|
|
||||||
|
g_source_attach (source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
|
||||||
|
|
||||||
|
return iks;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
_ik_startup (void (*cb)(ik_event_t *event))
|
||||||
|
{
|
||||||
|
if (g_once_init_enter (&inotify_source))
|
||||||
|
g_once_init_leave (&inotify_source, ik_source_new (cb));
|
||||||
|
|
||||||
|
return inotify_source->fd >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
gint32
|
gint32
|
||||||
_ik_watch (const char *path,
|
_ik_watch (const char *path,
|
||||||
guint32 mask,
|
guint32 mask,
|
||||||
@ -277,9 +259,9 @@ _ik_watch (const char *path,
|
|||||||
gint32 wd = -1;
|
gint32 wd = -1;
|
||||||
|
|
||||||
g_assert (path != NULL);
|
g_assert (path != NULL);
|
||||||
g_assert (inotify_instance_fd >= 0);
|
g_assert (inotify_source && inotify_source->fd >= 0);
|
||||||
|
|
||||||
wd = inotify_add_watch (inotify_instance_fd, path, mask);
|
wd = inotify_add_watch (inotify_source->fd, path, mask);
|
||||||
|
|
||||||
if (wd < 0)
|
if (wd < 0)
|
||||||
{
|
{
|
||||||
@ -299,9 +281,9 @@ _ik_ignore (const char *path,
|
|||||||
gint32 wd)
|
gint32 wd)
|
||||||
{
|
{
|
||||||
g_assert (wd >= 0);
|
g_assert (wd >= 0);
|
||||||
g_assert (inotify_instance_fd >= 0);
|
g_assert (inotify_source && inotify_source->fd >= 0);
|
||||||
|
|
||||||
if (inotify_rm_watch (inotify_instance_fd, wd) < 0)
|
if (inotify_rm_watch (inotify_source->fd, wd) < 0)
|
||||||
{
|
{
|
||||||
/* int e = errno; */
|
/* int e = errno; */
|
||||||
/* failed to rm watch */
|
/* failed to rm watch */
|
||||||
@ -310,280 +292,3 @@ _ik_ignore (const char *path,
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
ik_read_events (gsize *buffer_size_out,
|
|
||||||
gchar **buffer_out)
|
|
||||||
{
|
|
||||||
static gchar *buffer = NULL;
|
|
||||||
static gsize buffer_size;
|
|
||||||
|
|
||||||
/* Initialize the buffer on our first call */
|
|
||||||
if (buffer == NULL)
|
|
||||||
{
|
|
||||||
buffer_size = AVERAGE_EVENT_SIZE;
|
|
||||||
buffer_size *= MAX_QUEUED_EVENTS;
|
|
||||||
buffer = g_malloc (buffer_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
*buffer_size_out = 0;
|
|
||||||
*buffer_out = NULL;
|
|
||||||
|
|
||||||
memset (buffer, 0, buffer_size);
|
|
||||||
|
|
||||||
if (g_io_channel_read_chars (inotify_read_ioc, (char *)buffer, buffer_size, buffer_size_out, NULL) != G_IO_STATUS_NORMAL) {
|
|
||||||
/* error reading */
|
|
||||||
}
|
|
||||||
*buffer_out = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
ik_read_callback (gpointer user_data)
|
|
||||||
{
|
|
||||||
gchar *buffer;
|
|
||||||
gsize buffer_size, buffer_i, events;
|
|
||||||
|
|
||||||
G_LOCK (inotify_lock);
|
|
||||||
ik_read_events (&buffer_size, &buffer);
|
|
||||||
|
|
||||||
buffer_i = 0;
|
|
||||||
events = 0;
|
|
||||||
while (buffer_i < buffer_size)
|
|
||||||
{
|
|
||||||
struct inotify_event *event;
|
|
||||||
gsize event_size;
|
|
||||||
event = (struct inotify_event *)&buffer[buffer_i];
|
|
||||||
event_size = sizeof(struct inotify_event) + event->len;
|
|
||||||
g_queue_push_tail (events_to_process, ik_event_internal_new (ik_event_new (&buffer[buffer_i])));
|
|
||||||
buffer_i += event_size;
|
|
||||||
events++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the event process callback is off, turn it back on */
|
|
||||||
if (!process_eq_running && events)
|
|
||||||
{
|
|
||||||
GSource *timeout_source;
|
|
||||||
|
|
||||||
process_eq_running = TRUE;
|
|
||||||
timeout_source = g_timeout_source_new (PROCESS_EVENTS_TIME);
|
|
||||||
g_source_set_callback (timeout_source, ik_process_eq_callback, NULL, NULL);
|
|
||||||
g_source_attach (timeout_source, GLIB_PRIVATE_CALL (g_get_worker_context ()));
|
|
||||||
g_source_unref (timeout_source);
|
|
||||||
}
|
|
||||||
|
|
||||||
G_UNLOCK (inotify_lock);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
g_timeval_lt (GTimeVal *val1,
|
|
||||||
GTimeVal *val2)
|
|
||||||
{
|
|
||||||
if (val1->tv_sec < val2->tv_sec)
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
if (val1->tv_sec > val2->tv_sec)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* val1->tv_sec == val2->tv_sec */
|
|
||||||
if (val1->tv_usec < val2->tv_usec)
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
g_timeval_le (GTimeVal *val1,
|
|
||||||
GTimeVal *val2)
|
|
||||||
{
|
|
||||||
if (val1->tv_sec < val2->tv_sec)
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
if (val1->tv_sec > val2->tv_sec)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* val1->tv_sec == val2->tv_sec */
|
|
||||||
if (val1->tv_usec <= val2->tv_usec)
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ik_pair_events (ik_event_internal_t *event1,
|
|
||||||
ik_event_internal_t *event2)
|
|
||||||
{
|
|
||||||
g_assert (event1 && event2);
|
|
||||||
/* We should only be pairing events that have the same cookie */
|
|
||||||
g_assert (event1->event->cookie == event2->event->cookie);
|
|
||||||
/* We shouldn't pair an event that already is paired */
|
|
||||||
g_assert (event1->pair == NULL && event2->pair == NULL);
|
|
||||||
|
|
||||||
/* Pair the internal structures and the ik_event_t structures */
|
|
||||||
event1->pair = event2;
|
|
||||||
event1->event->pair = event2->event;
|
|
||||||
event2->event->is_second_in_pair = TRUE;
|
|
||||||
|
|
||||||
if (g_timeval_lt (&event1->hold_until, &event2->hold_until))
|
|
||||||
event1->hold_until = event2->hold_until;
|
|
||||||
|
|
||||||
event2->hold_until = event1->hold_until;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ik_event_add_microseconds (ik_event_internal_t *event,
|
|
||||||
glong ms)
|
|
||||||
{
|
|
||||||
g_assert (event);
|
|
||||||
g_time_val_add (&event->hold_until, ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
ik_event_ready (ik_event_internal_t *event)
|
|
||||||
{
|
|
||||||
GTimeVal tv;
|
|
||||||
g_assert (event);
|
|
||||||
|
|
||||||
g_get_current_time (&tv);
|
|
||||||
|
|
||||||
/* An event is ready if,
|
|
||||||
*
|
|
||||||
* it has no cookie -- there is nothing to be gained by holding it
|
|
||||||
* or, it is already paired -- we don't need to hold it anymore
|
|
||||||
* or, we have held it long enough
|
|
||||||
*/
|
|
||||||
return
|
|
||||||
event->event->cookie == 0 ||
|
|
||||||
event->pair != NULL ||
|
|
||||||
g_timeval_le (&event->hold_until, &tv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ik_pair_moves (gpointer data,
|
|
||||||
gpointer user_data)
|
|
||||||
{
|
|
||||||
ik_event_internal_t *event = (ik_event_internal_t *)data;
|
|
||||||
|
|
||||||
if (event->seen == TRUE || event->sent == TRUE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (event->event->cookie != 0)
|
|
||||||
{
|
|
||||||
/* When we get a MOVED_FROM event we delay sending the event by
|
|
||||||
* MOVE_HOLD_UNTIL_TIME microseconds. We need to do this because a
|
|
||||||
* MOVED_TO pair _might_ be coming in the near future */
|
|
||||||
if (event->event->mask & IN_MOVED_FROM)
|
|
||||||
{
|
|
||||||
g_hash_table_insert (cookie_hash, GINT_TO_POINTER (event->event->cookie), event);
|
|
||||||
/* because we don't deliver move events there is no point in waiting for the match right now. */
|
|
||||||
ik_event_add_microseconds (event, MOVE_HOLD_UNTIL_TIME);
|
|
||||||
}
|
|
||||||
else if (event->event->mask & IN_MOVED_TO)
|
|
||||||
{
|
|
||||||
/* We need to check if we are waiting for this MOVED_TO events cookie to pair it with
|
|
||||||
* a MOVED_FROM */
|
|
||||||
ik_event_internal_t *match = NULL;
|
|
||||||
match = g_hash_table_lookup (cookie_hash, GINT_TO_POINTER (event->event->cookie));
|
|
||||||
if (match)
|
|
||||||
{
|
|
||||||
g_hash_table_remove (cookie_hash, GINT_TO_POINTER (event->event->cookie));
|
|
||||||
ik_pair_events (match, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event->seen = TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ik_process_events (void)
|
|
||||||
{
|
|
||||||
g_queue_foreach (events_to_process, ik_pair_moves, NULL);
|
|
||||||
|
|
||||||
while (!g_queue_is_empty (events_to_process))
|
|
||||||
{
|
|
||||||
ik_event_internal_t *event = g_queue_peek_head (events_to_process);
|
|
||||||
|
|
||||||
/* This must have been sent as part of a MOVED_TO/MOVED_FROM */
|
|
||||||
if (event->sent)
|
|
||||||
{
|
|
||||||
/* Pop event */
|
|
||||||
g_queue_pop_head (events_to_process);
|
|
||||||
/* Free the internal event structure */
|
|
||||||
g_free (event);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The event isn't ready yet */
|
|
||||||
if (!ik_event_ready (event))
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* Pop it */
|
|
||||||
event = g_queue_pop_head (events_to_process);
|
|
||||||
|
|
||||||
/* Check if this is a MOVED_FROM that is also sitting in the cookie_hash */
|
|
||||||
if (event->event->cookie && event->pair == NULL &&
|
|
||||||
g_hash_table_lookup (cookie_hash, GINT_TO_POINTER (event->event->cookie)))
|
|
||||||
g_hash_table_remove (cookie_hash, GINT_TO_POINTER (event->event->cookie));
|
|
||||||
|
|
||||||
if (event->pair)
|
|
||||||
{
|
|
||||||
/* We send out paired MOVED_FROM/MOVED_TO events in the same event buffer */
|
|
||||||
/* g_assert (event->event->mask == IN_MOVED_FROM && event->pair->event->mask == IN_MOVED_TO); */
|
|
||||||
/* Copy the paired data */
|
|
||||||
event->pair->sent = TRUE;
|
|
||||||
event->sent = TRUE;
|
|
||||||
ik_move_matches++;
|
|
||||||
}
|
|
||||||
else if (event->event->cookie)
|
|
||||||
{
|
|
||||||
/* If we couldn't pair a MOVED_FROM and MOVED_TO together, we change
|
|
||||||
* the event masks */
|
|
||||||
/* Changeing MOVED_FROM to DELETE and MOVED_TO to create lets us make
|
|
||||||
* the gaurantee that you will never see a non-matched MOVE event */
|
|
||||||
event->event->original_mask = event->event->mask;
|
|
||||||
|
|
||||||
if (event->event->mask & IN_MOVED_FROM)
|
|
||||||
{
|
|
||||||
event->event->mask = IN_DELETE|(event->event->mask & IN_ISDIR);
|
|
||||||
ik_move_misses++; /* not super accurate, if we aren't watching the destination it still counts as a miss */
|
|
||||||
}
|
|
||||||
if (event->event->mask & IN_MOVED_TO)
|
|
||||||
event->event->mask = IN_CREATE|(event->event->mask & IN_ISDIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Push the ik_event_t onto the event queue */
|
|
||||||
g_queue_push_tail (event_queue, event->event);
|
|
||||||
/* Free the internal event structure */
|
|
||||||
g_free (event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
ik_process_eq_callback (gpointer user_data)
|
|
||||||
{
|
|
||||||
gboolean res;
|
|
||||||
|
|
||||||
/* Try and move as many events to the event queue */
|
|
||||||
G_LOCK (inotify_lock);
|
|
||||||
ik_process_events ();
|
|
||||||
|
|
||||||
while (!g_queue_is_empty (event_queue))
|
|
||||||
{
|
|
||||||
ik_event_t *event = g_queue_pop_head (event_queue);
|
|
||||||
|
|
||||||
user_cb (event);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = TRUE;
|
|
||||||
|
|
||||||
if (g_queue_get_length (events_to_process) == 0)
|
|
||||||
{
|
|
||||||
process_eq_running = FALSE;
|
|
||||||
res = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
G_UNLOCK (inotify_lock);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
@ -37,6 +37,7 @@ typedef struct ik_event_s {
|
|||||||
* then event1->pair == event2 and event2->pair == NULL.
|
* then event1->pair == event2 and event2->pair == NULL.
|
||||||
* It will result also in event1->pair->is_second_in_pair == TRUE */
|
* It will result also in event1->pair->is_second_in_pair == TRUE */
|
||||||
struct ik_event_s *pair;
|
struct ik_event_s *pair;
|
||||||
|
gint64 timestamp; /* monotonic time that this was created */
|
||||||
} ik_event_t;
|
} ik_event_t;
|
||||||
|
|
||||||
gboolean _ik_startup (void (*cb) (ik_event_t *event));
|
gboolean _ik_startup (void (*cb) (ik_event_t *event));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user