diff --git a/racket/src/racket/include/schthread.h b/racket/src/racket/include/schthread.h index fa14ec7fd6..6b834357c5 100644 --- a/racket/src/racket/include/schthread.h +++ b/racket/src/racket/include/schthread.h @@ -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_) /* **************************************** */ diff --git a/racket/src/racket/src/Makefile.in b/racket/src/racket/src/Makefile.in index 670e0205e4..472e94af17 100644 --- a/racket/src/racket/src/Makefile.in +++ b/racket/src/racket/src/Makefile.in @@ -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 \ diff --git a/racket/src/racket/src/env.c b/racket/src/racket/src/env.c index c60bb7c27c..6fcc351c72 100644 --- a/racket/src/racket/src/env.c +++ b/racket/src/racket/src/env.c @@ -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) diff --git a/racket/src/racket/src/inotify.inc b/racket/src/racket/src/inotify.inc new file mode 100644 index 0000000000..cbb1460db5 --- /dev/null +++ b/racket/src/racket/src/inotify.inc @@ -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 diff --git a/racket/src/racket/src/port.c b/racket/src/racket/src/port.c index 2423bfab79..01770688b2 100644 --- a/racket/src/racket/src/port.c +++ b/racket/src/racket/src/port.c @@ -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"); diff --git a/racket/src/racket/src/schpriv.h b/racket/src/racket/src/schpriv.h index 32011c5eea..8d83da009b 100644 --- a/racket/src/racket/src/schpriv.h +++ b/racket/src/racket/src/schpriv.h @@ -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);