# endif
# endif
+# ifndef EV_USE_INOTIFY
+# if HAVE_INOTIFY_INIT && HAVE_SYS_INOTIFY_H
+# define EV_USE_INOTIFY 1
+# else
+# define EV_USE_INOTIFY 0
+# endif
+# endif
+
#endif
#include <math.h>
#include <signal.h>
+#ifdef EV_H
+# include EV_H
+#else
+# include "ev.h"
+#endif
+
#ifndef _WIN32
# include <sys/time.h>
# include <sys/wait.h>
# define EV_USE_PORT 0
#endif
+#ifndef EV_USE_INOTIFY
+# define EV_USE_INOTIFY 0
+#endif
+
#ifndef EV_PID_HASHSIZE
# if EV_MINIMAL
# define EV_PID_HASHSIZE 1
# endif
#endif
+#ifndef EV_INOTIFY_HASHSIZE
+# if EV_MINIMAL
+# define EV_INOTIFY_HASHSIZE 1
+# else
+# define EV_INOTIFY_HASHSIZE 16
+# endif
+#endif
+
/**/
#ifndef CLOCK_MONOTONIC
# include <winsock.h>
#endif
+#if !EV_STAT_ENABLE
+# define EV_USE_INOTIFY 0
+#endif
+
+#if EV_USE_INOTIFY
+# include <sys/inotify.h>
+#endif
+
/**/
#define MIN_TIMEJUMP 1. /* minimum timejump that gets detected (if monotonic clock available) */
#define MAX_BLOCKTIME 59.743 /* never wait longer than this time (to detect time jumps) */
/*#define CLEANUP_INTERVAL (MAX_BLOCKTIME * 5.) /* how often to try to free memory and re-check fds */
-#ifdef EV_H
-# include EV_H
-#else
-# include "ev.h"
-#endif
-
#if __GNUC__ >= 3
# define expect(expr,value) __builtin_expect ((expr),(value))
# define inline_size static inline /* inline for codesize */
int events;
} ANPENDING;
+typedef struct
+{
+#if EV_USE_INOTIFY
+ WL head;
+#endif
+} ANFS;
+
#if EV_MULTIPLICITY
struct ev_loop
flags |= ev_recommended_backends ();
backend = 0;
+ backend_fd = -1;
+#if EV_USE_INOTIFY
+ fs_fd = -2;
+#endif
+
#if EV_USE_PORT
if (!backend && (flags & EVBACKEND_PORT )) backend = port_init (EV_A_ flags);
#endif
{
int i;
+#if EV_USE_INOTIFY
+ if (fs_fd >= 0)
+ close (fs_fd);
+#endif
+
+ if (backend_fd >= 0)
+ close (backend_fd);
+
#if EV_USE_PORT
if (backend == EVBACKEND_PORT ) port_destroy (EV_A);
#endif
#define DEF_STAT_INTERVAL 5.0074891
#define MIN_STAT_INTERVAL 0.1074891
+void noinline stat_timer_cb (EV_P_ ev_timer *w_, int revents);
+
+#if EV_USE_INOTIFY
+# define EV_INOTIFY_BUFSIZE ((PATH_MAX + sizeof (struct inotify_event)) + 2048)
+
+static void noinline
+infy_add (EV_P_ ev_stat *w)
+{
+ w->wd = inotify_add_watch (fs_fd, w->path, IN_ATTRIB | IN_DELETE_SELF | IN_MOVE_SELF | IN_MODIFY | IN_DONT_FOLLOW | IN_MASK_ADD);
+
+ if (w->wd < 0)
+ {
+ ev_timer_start (EV_A_ &w->timer); /* this is not race-free, so we still need to recheck periodically */
+
+ /* monitor some parent directory for speedup hints */
+ if (errno == ENOENT || errno == EACCES)
+ {
+ char path [PATH_MAX];
+ strcpy (path, w->path);
+
+ do
+ {
+ int mask = IN_MASK_ADD | IN_DELETE_SELF | IN_MOVE_SELF
+ | (errno == EACCES ? IN_ATTRIB : IN_CREATE | IN_MOVED_TO);
+
+ char *pend = strrchr (path, '/');
+
+ if (!pend)
+ break; /* whoops, no '/', complain to your admin */
+
+ *pend = 0;
+ w->wd = inotify_add_watch (fs_fd, path, IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO | IN_MASK_ADD);
+ }
+ while (w->wd < 0 && (errno == ENOENT || errno == EACCES));
+ }
+ }
+ else
+ ev_timer_stop (EV_A_ &w->timer); /* we can watch this in a race-free way */
+
+ if (w->wd >= 0)
+ wlist_add (&fs_hash [w->wd & (EV_INOTIFY_HASHSIZE - 1)].head, (WL)w);
+}
+
+static void noinline
+infy_del (EV_P_ ev_stat *w)
+{
+ WL w_;
+ int slot;
+ int wd = w->wd;
+
+ if (wd < 0)
+ return;
+
+ w->wd = -2;
+ slot = wd & (EV_INOTIFY_HASHSIZE - 1);
+ wlist_del (&fs_hash [slot].head, (WL)w);
+
+ /* remove this watcher, if others are watching it, they will rearm */
+ inotify_rm_watch (fs_fd, wd);
+}
+
+static void noinline
+infy_wd (EV_P_ int slot, int wd, struct inotify_event *ev)
+{
+ if (slot < 0)
+ /* overflow, need to check for all hahs slots */
+ for (slot = 0; slot < EV_INOTIFY_HASHSIZE; ++slot)
+ infy_wd (EV_A_ slot, wd, ev);
+ else
+ {
+ WL w_;
+
+ for (w_ = fs_hash [slot & (EV_INOTIFY_HASHSIZE - 1)].head; w_; )
+ {
+ ev_stat *w = (ev_stat *)w_;
+ w_ = w_->next; /* lets us remove this watcher and all before it */
+
+ if (w->wd == wd || wd == -1)
+ {
+ if (ev->mask & (IN_IGNORED | IN_UNMOUNT | IN_DELETE_SELF))
+ {
+ w->wd = -1;
+ infy_add (EV_A_ w); /* re-add, no matter what */
+ }
+
+ stat_timer_cb (EV_P_ &w->timer, 0);
+ }
+ }
+ }
+}
+
+static void
+infy_cb (EV_P_ ev_io *w, int revents)
+{
+ char buf [EV_INOTIFY_BUFSIZE];
+ struct inotify_event *ev = (struct inotify_event *)buf;
+ int ofs;
+ int len = read (fs_fd, buf, sizeof (buf));
+
+ for (ofs = 0; ofs < len; ofs += sizeof (struct inotify_event) + ev->len)
+ infy_wd (EV_A_ ev->wd, ev->wd, ev);
+}
+
+void inline_size
+infy_init (EV_P)
+{
+ if (fs_fd != -2)
+ return;
+
+ fs_fd = inotify_init ();
+
+ if (fs_fd >= 0)
+ {
+ ev_io_init (&fs_w, infy_cb, fs_fd, EV_READ);
+ ev_set_priority (&fs_w, EV_MAXPRI);
+ ev_io_start (EV_A_ &fs_w);
+ }
+}
+
+#endif
+
void
ev_stat_stat (EV_P_ ev_stat *w)
{
w->attr.st_nlink = 1;
}
-static void
+void noinline
stat_timer_cb (EV_P_ ev_timer *w_, int revents)
{
ev_stat *w = (ev_stat *)(((char *)w_) - offsetof (ev_stat, timer));
ev_stat_stat (EV_A_ w);
if (memcmp (&w->prev, &w->attr, sizeof (ev_statdata)))
- ev_feed_event (EV_A_ w, EV_STAT);
+ {
+ #if EV_USE_INOTIFY
+ infy_del (EV_A_ w);
+ infy_add (EV_A_ w);
+ ev_stat_stat (EV_A_ w); /* avoid race... */
+ #endif
+
+ ev_feed_event (EV_A_ w, EV_STAT);
+ }
}
void
ev_timer_init (&w->timer, stat_timer_cb, w->interval, w->interval);
ev_set_priority (&w->timer, ev_priority (w));
- ev_timer_start (EV_A_ &w->timer);
+
+#if EV_USE_INOTIFY
+ infy_init (EV_A);
+
+ if (fs_fd >= 0)
+ infy_add (EV_A_ w);
+ else
+#endif
+ ev_timer_start (EV_A_ &w->timer);
ev_start (EV_A_ (W)w, 1);
}
if (expect_false (!ev_is_active (w)))
return;
+#if EV_USE_INOTIFY
+ infy_del (EV_A_ w);
+#endif
ev_timer_stop (EV_A_ &w->timer);
ev_stop (EV_A_ (W)w);
#if EV_STAT_ENABLE
/* st_nlink = 0 means missing file or other error */
-#ifdef _WIN32
+# ifdef _WIN32
typedef struct _stati64 ev_statdata;
-#else
+# else
typedef struct stat ev_statdata;
-#endif
+# endif
/* invoked each time the stat data changes for a given path */
/* revent EV_STAT */
typedef struct ev_stat
{
- EV_WATCHER (ev_stat)
+ EV_WATCHER_LIST (ev_stat)
ev_timer timer; /* private */
ev_tstamp interval; /* ro */
const char *path; /* ro */
ev_statdata prev; /* ro */
ev_statdata attr; /* ro */
+
+ int wd; /* wd for inotify, fd for kqueue */
} ev_stat;
#endif
#define ev_periodic_set(ev,at_,ival_,res_) do { (ev)->at = (at_); (ev)->interval = (ival_); (ev)->reschedule_cb= (res_); } while (0)
#define ev_signal_set(ev,signum_) do { (ev)->signum = (signum_); } while (0)
#define ev_child_set(ev,pid_) do { (ev)->pid = (pid_); } while (0)
-#define ev_stat_set(ev,path_,interval_) do { (ev)->path = (path_); (ev)->interval = (interval_); } while (0)
+#define ev_stat_set(ev,path_,interval_) do { (ev)->path = (path_); (ev)->interval = (interval_); (ev)->wd = -2; } while (0)
#define ev_idle_set(ev) /* nop, yes, this is a serious in-joke */
#define ev_prepare_set(ev) /* nop, yes, this is a serious in-joke */
#define ev_check_set(ev) /* nop, yes, this is a serious in-joke */
reserved for future expansion, works like the USE symbols above.
+=item EV_USE_INOTIFY
+
+If defined to be C<1>, libev will compile in support for the Linux inotify
+interface to speed up C<ev_stat> watchers. Its actual availability will
+be detected at runtime.
+
=item EV_H
The name of the F<ev.h> header file used to include it. The default if
C<ev_child> watchers use a small hash table to distribute workload by
pid. The default size is C<16> (or C<1> with C<EV_MINIMAL>), usually more
than enough. If you need to manage thousands of children you might want to
-increase this value.
+increase this value (I<must> be a power of two).
+
+=item EV_INOTIFY_HASHSIZE
+
+C<ev_staz> watchers use a small hash table to distribute workload by
+inotify watch id. The default size is C<16> (or C<1> with C<EV_MINIMAL>),
+usually more than enough. If you need to manage thousands of C<ev_stat>
+watchers you might want to increase this value (I<must> be a power of
+two).
=item EV_COMMON
=item Stopping check/prepare/idle watchers: O(1)
-=item Stopping an io/signal/child watcher: O(number_of_watchers_for_this_(fd/signal/pid % 16))
+=item Stopping an io/signal/child watcher: O(number_of_watchers_for_this_(fd/signal/pid % EV_PID_HASHSIZE))
=item Finding the next timer per loop iteration: O(1)