filesystem-notify-evt: change inotify() implementation

Create a single inotify() connection per place, which should reduce
the latency of operations on filesystem change events and make
them generally scale better on Linux.

Internally, add a filesystem-never-changes mode, which could be useful
for platforms with fixed filesystems, but it's also for experiments in
checking the cost of filesystem change events.
This commit is contained in:
Matthew Flatt 2013-07-14 09:10:24 -06:00
parent d695e8a15a
commit b3715ba36e
6 changed files with 331 additions and 29 deletions

View File

@ -355,6 +355,7 @@ typedef struct Thread_Local_Variables {
Scheme_On_Atomic_Timeout_Proc on_atomic_timeout_;
int atomic_timeout_auto_suspend_;
int atomic_timeout_atomic_level_;
void *scheme_inotify_server_;
} Thread_Local_Variables;
#if defined(IMPLEMENT_THREAD_LOCAL_VIA_PTHREADS)
@ -735,6 +736,7 @@ XFORM_GC_VARIABLE_STACK_THROUGH_THREAD_LOCAL;
#define on_atomic_timeout XOA (scheme_get_thread_local_variables()->on_atomic_timeout_)
#define atomic_timeout_auto_suspend XOA (scheme_get_thread_local_variables()->atomic_timeout_auto_suspend_)
#define atomic_timeout_atomic_level XOA (scheme_get_thread_local_variables()->atomic_timeout_atomic_level_)
#define scheme_inotify_server XOA (scheme_get_thread_local_variables()->scheme_inotify_server_)
/* **************************************** */

View File

@ -383,7 +383,8 @@ optimize.@LTO@: $(COMMON_HEADERS) \
place.@LTO@: $(COMMON_HEADERS) \
$(srcdir)/stypes.h $(srcdir)/schfd.h $(srcdir)/mzmark_place.inc
port.@LTO@: $(COMMON_HEADERS) \
$(srcdir)/stypes.h $(srcdir)/schfd.h $(srcdir)/mzmark_port.inc
$(srcdir)/stypes.h $(srcdir)/schfd.h $(srcdir)/mzmark_port.inc \
$(srcdir)/inotify.inc
portfun.@LTO@: $(COMMON_HEADERS) $(srcdir)/schvers.h \
$(srcdir)/stypes.h $(srcdir)/schfd.h $(srcdir)/mzmark_portfun.inc
print.@LTO@: $(COMMON_HEADERS) $(srcdir)/stypes.h $(srcdir)/schcpt.h \

View File

@ -624,6 +624,7 @@ void scheme_place_instance_destroy(int force)
scheme_free_all_code();
scheme_free_ghbn_data();
scheme_release_kqueue();
scheme_release_inotify();
}
static void make_kernel_env(void)

View File

@ -0,0 +1,250 @@
/* #included by "port.c" */
/* Multiplex multiple filesystem change events onto a single
inotify connection. That's almost as easy as using watch
descriptors in place of file descriptors, but using the
same filesystem path multiple times produces the same
watch descriptors, so reference-count it. Also, each watch
can be removed as soon as it fires, since filesystem
change events are single-shot.
The values returned by mz_inotify_add() are indices into an array
of watch descriptors. There's room for a better data structure if
the watch-descriptor-to-index mapping becomes too slow. */
#ifdef MZ_XFORM
START_XFORM_SUSPEND;
#endif
typedef struct mz_wd_t {
int wd;
int refcount;
int val;
} mz_wd_t;
typedef struct mz_inotify_state_t {
int ready, fd;
mz_wd_t *wds;
int size, count;
int got;
} mz_inotify_state_t;
static int mzi_find_wd(int wd, mz_wd_t *wds, int size)
{
int i;
for (i = 0; i < size; i++) {
if (wds[i].wd == wd) return i;
}
return -1;
}
static int mzi_add_wd(int wd, mz_wd_t *wds, int size)
{
int i;
for (i = 0; i < size; i++) {
if (wds[i].wd == wd) {
wds[i].refcount++;
return i;
}
}
for (i = 0; i < size; i++) {
if (!wds[i].refcount) {
wds[i].wd = wd;
wds[i].refcount = 1;
wds[i].val = 0;
return i;
}
}
abort();
return -1;
}
static int mzi_pull_events(int fd, mz_wd_t *wds, int size)
{
struct inotify_event _ev, *ev;
void *b = NULL;
int rc, p, got = 0;
int bsize;
struct pollfd pfd[1];
ev = &_ev;
bsize = sizeof(_ev);
pfd[0].fd = fd;
pfd[0].events = POLLIN;
while (poll(pfd, 1, 0)) {
rc = read(fd, ev, bsize);
if (rc > 0) {
p = mzi_find_wd(ev->wd, wds, size);
if (p != -1) {
got = 1;
wds[p].val = 1;
wds[p].wd = -1;
inotify_rm_watch(fd, ev->wd);
}
} else if (rc == -1) {
if (errno == EAGAIN)
break;
else if (errno == EINTR) {
/* try again */
} else if (errno == EINVAL) {
bsize *= 2;
if (b) free(b);
b = malloc(bsize);
ev = (struct inotify_event *)b;
} else
scheme_signal_error("inotify read failed on %d (%e)", fd, errno);
} else
break;
}
if (b)
free (b);
return got;
}
static void mz_inotify_start(mz_inotify_state_t *s)
{
int fd;
fd = inotify_init();
s->ready = 1;
s->fd = fd;
}
static void mz_inotify_end(mz_inotify_state_t *s)
{
int rc;
do {
rc = close(s->fd);
} while (rc == -1 && errno == EINTR);
if (s->wds) free(s->wds);
free(s);
}
static void mz_inotify_init()
{
if (!scheme_inotify_server) {
mz_inotify_state_t *s;
s = (mz_inotify_state_t *)malloc(sizeof(mz_inotify_state_t));
memset(s, 0, sizeof(mz_inotify_state_t));
mz_inotify_start(s);
scheme_inotify_server = s;
}
}
static int mz_inotify_ready()
{
mz_inotify_state_t *s = (mz_inotify_state_t *)scheme_inotify_server;
return s->ready;
}
/* Other functions are called only if mz_inotify_ready() returns 1. */
static int mz_inotify_add(char *filename)
{
mz_inotify_state_t *s = (mz_inotify_state_t *)scheme_inotify_server;
int wd;
if (s->count == s->size) {
int new_size = (s->size ? (2 * s->size) : 32);
mz_wd_t *new_wds;
int i;
new_wds = (mz_wd_t *)malloc(sizeof(mz_wd_t) * new_size);
memcpy(new_wds, s->wds, s->size * sizeof(mz_wd_t));
free(s->wds);
s->wds = new_wds;
s->size = new_size;
for (i = s->count; i < s->size; i++)
s->wds[i].wd = -1;
}
wd = inotify_add_watch(s->fd, filename,
(IN_CREATE | IN_DELETE | IN_DELETE_SELF
| IN_MODIFY | IN_MOVE_SELF | IN_MOVED_TO
| IN_ATTRIB | IN_ONESHOT));
if (wd == -1)
return -1;
else {
int p;
p = mzi_add_wd(wd, s->wds, s->size);
if (s->wds[p].refcount == 1)
s->count++;
return p+1;
}
}
static void mz_inotify_remove(int p2)
{
mz_inotify_state_t *s = (mz_inotify_state_t *)scheme_inotify_server;
int p = p2 - 1;
if (s->wds[p].refcount == 1) {
if (s->wds[p].wd != -1) {
inotify_rm_watch(s->fd, s->wds[p].wd);
s->wds[p].wd = -1;
/* in case the wd gets reused: */
if (mzi_pull_events(s->fd, s->wds, s->size))
s->got = 1;
}
--s->count;
}
s->wds[p].refcount -= 1;
}
static int mz_inotify_poll(int p2)
{
mz_inotify_state_t *s = (mz_inotify_state_t *)scheme_inotify_server;
int p = p2 - 1;
if (mzi_pull_events(s->fd, s->wds, s->size))
s->got = 1;
if (s->wds[p].val)
return 1;
else
return 0;
}
static void mz_inotify_stop()
{
mz_inotify_state_t *s = (mz_inotify_state_t *)scheme_inotify_server;
if (s) {
mz_inotify_end(s);
scheme_inotify_server = NULL;
}
}
static int mz_inotify_fd()
{
mz_inotify_state_t *s = (mz_inotify_state_t *)scheme_inotify_server;
if (s->got) {
/* In case we received something for Y in a poll for X */
s->got = 0;
return -2;
}
return s->fd;
}
#ifdef MZ_XFORM
END_XFORM_SUSPEND;
#endif

View File

@ -448,7 +448,7 @@ static int progress_evt_ready(Scheme_Object *rww, Scheme_Schedule_Info *sinfo);
static int closed_evt_ready(Scheme_Object *rww, Scheme_Schedule_Info *sinfo);
static int filesystem_change_evt_ready(Scheme_Object *evt, Scheme_Schedule_Info *sinfo);
#ifdef DOS_FILE_SYSTEM
#if defined(DOS_FILE_SYSTEM) || defined(HAVE_INOTIFY_SYSCALL)
static void filesystem_change_evt_need_wakeup (Scheme_Object *port, void *fds);
#else
# define filesystem_change_evt_need_wakeup NULL
@ -5976,14 +5976,20 @@ Scheme_Object *scheme_file_unlock(int argc, Scheme_Object **argv)
/* filesystem change events */
/*========================================================================*/
/* removed `defined(HAVE_INOTIFY_SYSCALL)' for now: */
#if defined(HAVE_KQUEUE_SYSCALL) || defined(DOS_FILE_SYSTEM)
#if defined(HAVE_KQUEUE_SYSCALL) \
|| defined(DOS_FILE_SYSTEM) \
|| defined(HAVE_INOTIFY_SYSCALL) \
|| defined(FILESYSTEM_NEVER_CHANGES)
# define HAVE_FILESYSTEM_CHANGE_EVTS
#else
# define NO_FILESYSTEM_CHANGE_EVTS
#endif
#ifndef NO_FILESYSTEM_CHANGE_EVTS
#if defined(HAVE_INOTIFY_SYSCALL)
# include "inotify.inc"
#endif
#if !defined(NO_FILESYSTEM_CHANGE_EVTS) && !defined(FILESYSTEM_NEVER_CHANGES)
static void filesystem_change_evt_fnl(void *fc, void *data)
{
scheme_filesystem_change_evt_cancel((Scheme_Object *)fc, NULL);
@ -6005,6 +6011,8 @@ Scheme_Object *scheme_filesystem_change_evt(Scheme_Object *path, int flags, int
#if defined(NO_FILESYSTEM_CHANGE_EVTS)
ok = 0;
errid = -1;
#elif defined(FILESYSTEM_NEVER_CHANGES)
ok = 1;
#elif defined(HAVE_KQUEUE_SYSCALL)
do {
fd = open(filename, flags | MZ_BINARY, 0666);
@ -6014,26 +6022,16 @@ Scheme_Object *scheme_filesystem_change_evt(Scheme_Object *path, int flags, int
else
ok = 1;
#elif defined(HAVE_INOTIFY_SYSCALL)
/* This implementation uses a file descriptor for every event,
instead of using a watch descriptor for every event. This could
be improved, but note that the kqueue() implementation needs a
file descriptor per event, anyway. */
fd = inotify_init();
if (fd == -1)
errid = errno;
/* see "inotify.inc" */
mz_inotify_init();
if (!mz_inotify_ready())
errid = EAGAIN;
else {
int wd;
wd = inotify_add_watch(fd, filename,
(IN_CREATE | IN_DELETE | IN_DELETE_SELF
| IN_MODIFY | IN_MOVE_SELF | IN_MOVED_TO
| IN_ATTRIB | IN_ONESHOT));
if (wd == -1) {
fd = mz_inotify_add(filename);
if (fd == -1)
errid = errno;
scheme_close_file_fd(fd);
} else {
else
ok = 1;
fcntl(fd, F_SETFL, MZ_NONBLOCKING);
}
}
#elif defined(DOS_FILE_SYSTEM)
{
@ -6091,7 +6089,16 @@ Scheme_Object *scheme_filesystem_change_evt(Scheme_Object *path, int flags, int
#if defined(NO_FILESYSTEM_CHANGE_EVTS)
return NULL;
#elif defined(DOS_FILE_SYSTEM)
#elif defined(FILESYSTEM_NEVER_CHANGES)
{
Scheme_Filesystem_Change_Evt *fc;
fc = MALLOC_ONE_TAGGED(Scheme_Filesystem_Change_Evt);
fc->so.type = scheme_filesystem_change_evt_type;
return (Scheme_Object *)fc;
}
#elif defined(DOS_FILE_SYSTEM) || defined(HAVE_INOTIFY_SYSCALL)
{
Scheme_Filesystem_Change_Evt *fc;
Scheme_Custodian_Reference *mref;
@ -6154,18 +6161,27 @@ void scheme_filesystem_change_evt_cancel(Scheme_Object *evt, void *ignored_data)
Scheme_Filesystem_Change_Evt *fc = (Scheme_Filesystem_Change_Evt *)evt;
if (fc->mref) {
# if defined(DOS_FILE_SYSTEM)
# if defined(FILESYSTEM_NEVER_CHANGES)
fc->mref = NULL;
# else
# if defined(DOS_FILE_SYSTEM)
if (fc->fd) {
FindCloseChangeNotification((HANDLE)fc->fd);
fc->fd = 0;
}
# else
# elif defined(HAVE_INOTIFY_SYSCALL)
if (fc->fd) {
mz_inotify_remove(fc->fd);
fc->fd = 0;
}
# else
(void)scheme_fd_to_semaphore(fc->fd, MZFD_REMOVE_VNODE, 0);
scheme_close_file_fd(fc->fd);
scheme_post_sema_all(fc->sema);
# endif
# endif
scheme_remove_managed(fc->mref, (Scheme_Object *)fc);
fc->mref = NULL;
# endif
}
#endif
}
@ -6182,6 +6198,15 @@ static int filesystem_change_evt_ready(Scheme_Object *evt, Scheme_Schedule_Info
}
return !fc->fd;
# elif defined(HAVE_INOTIFY_SYSCALL)
if (fc->fd) {
if (mz_inotify_poll(fc->fd))
scheme_filesystem_change_evt_cancel((Scheme_Object *)fc, NULL);
}
return !fc->fd;
# elif defined(FILESYSTEM_NEVER_CHANGES)
return fc->fd; /* = 0 */
# else
if (scheme_try_plain_sema(fc->sema))
scheme_filesystem_change_evt_cancel((Scheme_Object *)fc, NULL);
@ -6195,13 +6220,28 @@ static int filesystem_change_evt_ready(Scheme_Object *evt, Scheme_Schedule_Info
return 0;
}
#ifdef DOS_FILE_SYSTEM
#if defined(DOS_FILE_SYSTEM) || defined(HAVE_INOTIFY_SYSCALL)
static void filesystem_change_evt_need_wakeup (Scheme_Object *evt, void *fds)
{
Scheme_Filesystem_Change_Evt *fc = (Scheme_Filesystem_Change_Evt *)evt;
if (fc->fd)
if (fc->fd) {
#ifdef DOS_FILE_SYSTEM
scheme_add_fd_handle((void *)fc->fd, fds, 0);
#else
int fd;
fd = mz_inotify_fd();
if (fd >= 0) {
void *fds2;
fds2 = MZ_GET_FDSET(fds, 0);
MZ_FD_SET(fd, (fd_set *)fds2);
fds2 = MZ_GET_FDSET(fds, 2);
MZ_FD_SET(fd, (fd_set *)fds2);
} else if (fd == -2) {
scheme_cancel_sleep();
}
#endif
}
}
#endif
@ -6225,6 +6265,13 @@ int scheme_fd_regular_file(intptr_t fd, int dir_ok)
#endif
}
void scheme_release_inotify()
{
#ifdef HAVE_INOTIFY_SYSCALL
mz_inotify_stop();
#endif
}
/*========================================================================*/
/* FILE input ports */
/*========================================================================*/
@ -6378,7 +6425,7 @@ static CSI_proc get_csi(void)
static int tried_csi = 0;
static CSI_proc csi;
START_XFORM_SKIP;
START_XFORM_SKIP;
if (!tried_csi) {
HMODULE hm;
hm = LoadLibrary("kernel32.dll");

View File

@ -3821,6 +3821,7 @@ void scheme_release_file_descriptor(void);
void scheme_init_kqueue(void);
void scheme_release_kqueue(void);
void scheme_release_inotify(void);
THREAD_LOCAL_DECL(extern struct mz_fd_set *scheme_semaphore_fd_set);
THREAD_LOCAL_DECL(extern Scheme_Hash_Table *scheme_semaphore_fd_mapping);