return, and watch_worker then issues another call to
ReadDirectoryChangesW. (Except when it does not, see below.)
- The WM_EMACS_FILENOTIFY message, posted to the message queue gets
- dispatched to the main Emacs window procedure, which queues it for
- processing by w32_read_socket. When w32_read_socket sees this
- message, it accesses the buffer with file notifications (using a
- critical section), extracts the information, converts it to a
- series of FILE_NOTIFY_EVENT events, and stuffs them into the input
- event queue to be processed by keyboard.c input machinery
- (read_char via a call to kbd_buffer_get_event). When the
- FILE_NOTIFY_EVENT event is processed by kbd_buffer_get_event, it is
- converted to a Lispy event that can be bound to a command. The
- default binding is w32notify-handle-event, defined on subr.el.
-
- After w32_read_socket is done processing the notifications, it
- resets a flag signaling to all watch worker threads that the
- notifications buffer is available for more input.
+ In a GUI session, The WM_EMACS_FILENOTIFY message, posted to the
+ message queue gets dispatched to the main Emacs window procedure,
+ which queues it for processing by w32_read_socket. When
+ w32_read_socket sees this message, it accesses the buffer with file
+ notifications (using a critical section), extracts the information,
+ converts it to a series of FILE_NOTIFY_EVENT events, and stuffs
+ them into the input event queue to be processed by keyboard.c input
+ machinery (read_char via a call to kbd_buffer_get_event).
+
+ In a non-GUI session, we send the WM_EMACS_FILENOTIFY message to
+ the main (a.k.a. "Lisp") thread instead, since there are no window
+ procedures in console programs. That message wakes up
+ MsgWaitForMultipleObjects inside sys_select, which then signals to
+ its caller that some keyboard input is available. This causes
+ w32_console_read_socket to be called, which accesses the buffer
+ with file notifications and stuffs them into the input event queue
+ for keyboard.c to process.
+
+ When the FILE_NOTIFY_EVENT event is processed by keyboard.c's
+ kbd_buffer_get_event, it is converted to a Lispy event that can be
+ bound to a command. The default binding is w32notify-handle-event,
+ defined on subr.el.
+
+ After w32_read_socket or w32_console_read_socket is done processing
+ the notifications, it resets a flag signaling to all watch worker
+ threads that the notifications buffer is available for more input.
When the watch is removed by a call to w32notify-rm-watch, the main
thread requests that the worker thread terminates by queuing an APC
#include "frame.h" /* needed by termhooks.h */
#include "termhooks.h" /* for FILE_NOTIFY_EVENT */
+#define DIRWATCH_SIGNATURE 0x01233210
+
struct notification {
BYTE *buf; /* buffer for ReadDirectoryChangesW */
OVERLAPPED *io_info; /* the OVERLAPPED structure for async I/O */
char *watchee; /* the file we are interested in */
HANDLE dir; /* handle to the watched directory */
HANDLE thr; /* handle to the thread that watches */
- int terminate; /* if non-zero, request for the thread to terminate */
+ volatile int terminate; /* if non-zero, request for the thread to terminate */
+ unsigned signature;
};
-/* FIXME: this needs to be changed to support more that one request at
- a time. */
-static struct notification dirwatch;
-
/* Used for communicating notifications to the main thread. */
-int notification_buffer_in_use;
+volatile int notification_buffer_in_use;
BYTE file_notifications[16384];
DWORD notifications_size;
-HANDLE notifications_desc;
+void *notifications_desc;
static Lisp_Object Qfile_name, Qdirectory_name, Qattributes, Qsize;
static Lisp_Object Qlast_write_time, Qlast_access_time, Qcreation_time;
/* Signal to the main thread that we have file notifications for it to
process. */
static void
-send_notifications (BYTE *info, DWORD info_size, HANDLE hdir, int *terminate)
+send_notifications (BYTE *info, DWORD info_size, void *desc,
+ volatile int *terminate)
{
int done = 0;
FRAME_PTR f = SELECTED_FRAME ();
- /* Too bad, but PostMessage will not work in non-GUI sessions.
- FIXME. */
- if (!FRAME_W32_P (f))
- return;
-
/* A single buffer is used to communicate all notifications to the
main thread. Since both the main thread and several watcher
threads could be active at the same time, we use a critical area
if (info_size)
memcpy (file_notifications, info, info_size);
notifications_size = info_size;
- notifications_desc = hdir;
+ notifications_desc = desc;
/* If PostMessage fails, the message queue is full. If that
happens, the last thing they will worry about is file
notifications. So we effectively discard the
notification in that case. */
- if (PostMessage (FRAME_W32_WINDOW (f), WM_EMACS_FILENOTIFY, 0, 0))
+ if ((FRAME_TERMCAP_P (f)
+ /* We send the message to the main (a.k.a. "Lisp")
+ thread, where it will wake up MsgWaitForMultipleObjects
+ inside sys_select, causing it to report that there's
+ some keyboard input available. This will in turn cause
+ w32_console_read_socket to be called, which will pick
+ up the file notifications. */
+ && PostThreadMessage (dwMainThreadId, WM_EMACS_FILENOTIFY, 0, 0))
+ || (FRAME_W32_P (f)
+ && PostMessage (FRAME_W32_WINDOW (f),
+ WM_EMACS_FILENOTIFY, 0, 0)))
notification_buffer_in_use = 1;
done = 1;
}
CancelIo on the directory we watch, and watch_end did so.
The directory handle is already closed. We should clean up
and exit, signalling to the thread worker routine not to
- issue another call to ReadDirectoryChangesW. */
+ issue another call to ReadDirectoryChangesW. Note that we
+ don't free the dirwatch object itself; this is done by the
+ main thread in remove_watch. */
xfree (dirwatch->buf);
dirwatch->buf = NULL;
xfree (dirwatch->io_info);
else
{
/* Tell the main thread we have notifications for it. */
- send_notifications (dirwatch->buf, bytes_ret, dirwatch->dir,
+ send_notifications (dirwatch->buf, bytes_ret, dirwatch,
&dirwatch->terminate);
}
}
if (!status)
{
DebPrint (("watch_worker, abnormal exit: %lu\n", GetLastError ()));
+ /* We cannot remove the dirwatch object from watch_list,
+ because we are in a separate thread. So we free and
+ zero out all the pointers in the object, but do not
+ free the object itself. We also don't touch the
+ signature. This way, remove_watch can still identify
+ the object, remove it, and free its memory. */
xfree (dirwatch->buf);
dirwatch->buf = NULL;
xfree (dirwatch->io_info);
/* Launch a thread to watch changes to FILE in a directory open on
handle HDIR. */
-static int
+static struct notification *
start_watching (const char *file, HANDLE hdir, BOOL subdirs, DWORD flags)
{
- dirwatch.buf = xmalloc (16384);
- dirwatch.io_info = xzalloc (sizeof(OVERLAPPED));
+ struct notification *dirwatch = xzalloc (sizeof (struct notification));
+ HANDLE thr;
+
+ dirwatch->signature = DIRWATCH_SIGNATURE;
+ dirwatch->buf = xmalloc (16384);
+ dirwatch->io_info = xzalloc (sizeof(OVERLAPPED));
/* Stash a pointer to dirwatch structure for use by the completion
routine. According to MSDN documentation of ReadDirectoryChangesW:
"The hEvent member of the OVERLAPPED structure is not used by the
system, so you can use it yourself." */
- dirwatch.io_info->hEvent = &dirwatch;
- dirwatch.subtree = subdirs;
- dirwatch.filter = flags;
- dirwatch.watchee = xstrdup (file);
- dirwatch.terminate = 0;
- dirwatch.dir = hdir;
+ dirwatch->io_info->hEvent = dirwatch;
+ dirwatch->subtree = subdirs;
+ dirwatch->filter = flags;
+ dirwatch->watchee = xstrdup (file);
+ dirwatch->terminate = 0;
+ dirwatch->dir = hdir;
/* See w32proc.c where it calls CreateThread for the story behind
the 2nd and 5th argument in the call to CreateThread. */
- dirwatch.thr = CreateThread (NULL, 64 * 1024, watch_worker,
- (void *)&dirwatch, 0x00010000, NULL);
+ dirwatch->thr = CreateThread (NULL, 64 * 1024, watch_worker, (void *)dirwatch,
+ 0x00010000, NULL);
- if (!dirwatch.thr)
+ if (!dirwatch->thr)
{
- dirwatch.terminate = 1;
- xfree (dirwatch.buf);
- dirwatch.buf = NULL;
- xfree (dirwatch.io_info);
- dirwatch.io_info = NULL;
- xfree (dirwatch.watchee);
- dirwatch.watchee = NULL;
- dirwatch.dir = NULL;
- return -1;
+ xfree (dirwatch->buf);
+ xfree (dirwatch->io_info);
+ xfree (dirwatch->watchee);
+ xfree (dirwatch);
+ dirwatch = NULL;
}
- return 0;
+ return dirwatch;
}
/* Called from the main thread to start watching FILE in PARENT_DIR,
subject to FLAGS. If SUBDIRS is TRUE, watch the subdirectories of
- PARENT_DIR as well. Value is the handle on which the directory is
- open. */
-static HANDLE *
+ PARENT_DIR as well. Value is a pointer to 'struct notification'
+ used by the thread that watches the changes. */
+static struct notification *
add_watch (const char *parent_dir, const char *file, BOOL subdirs, DWORD flags)
{
HANDLE hdir;
+ struct notification *dirwatch = NULL;
if (!file || !*file)
return NULL;
if (hdir == INVALID_HANDLE_VALUE)
return NULL;
- if (start_watching (file, hdir, subdirs, flags) == 0)
- return hdir;
+ if ((dirwatch = start_watching (file, hdir, subdirs, flags)) == NULL)
+ CloseHandle (hdir);
- CloseHandle (hdir);
- return NULL;
+ return dirwatch;
}
-/* Stop watching a directory specified by its handle HDIR. */
+/* Stop watching a directory specified by a pointer to its dirwatch object. */
static int
-remove_watch (HANDLE hdir)
+remove_watch (struct notification *dirwatch)
{
- if (hdir == dirwatch.dir)
+ if (dirwatch && dirwatch->signature == DIRWATCH_SIGNATURE)
{
int i;
BOOL status;
CancelIo on it. (CancelIoEx is available only since Vista.)
So we need to queue an APC for the worker thread telling it
to terminate. */
- if (!QueueUserAPC (watch_end, dirwatch.thr, (ULONG_PTR)dirwatch.dir))
+ if (!QueueUserAPC (watch_end, dirwatch->thr, (ULONG_PTR)dirwatch->dir))
DebPrint (("QueueUserAPC failed (%lu)!\n", GetLastError ()));
/* We also set the terminate flag, for when the thread is
waiting on the critical section that never gets acquired.
FIXME: is there a cleaner method? Using SleepEx there is a
no-no, as that will lead to recursive APC invocations and
stack overflow. */
- dirwatch.terminate = 1;
+ dirwatch->terminate = 1;
/* Wait for the thread to exit. FIXME: is there a better method
that is not overly complex? */
for (i = 0; i < 50; i++)
{
- if (!((status = GetExitCodeThread (dirwatch.thr, &exit_code))
+ if (!((status = GetExitCodeThread (dirwatch->thr, &exit_code))
&& exit_code == STILL_ACTIVE))
break;
Sleep (10);
|| exit_code == STILL_ACTIVE)
{
if (!(status == FALSE && err == ERROR_INVALID_HANDLE))
- TerminateThread (dirwatch.thr, 0);
+ {
+ TerminateThread (dirwatch->thr, 0);
+ if (dirwatch->dir)
+ CloseHandle (dirwatch->dir);
+ }
}
/* Clean up. */
- if (dirwatch.thr)
+ if (dirwatch->thr)
{
- CloseHandle (dirwatch.thr);
- dirwatch.thr = NULL;
+ CloseHandle (dirwatch->thr);
+ dirwatch->thr = NULL;
}
- return 0;
- }
- else if (!dirwatch.dir)
- {
- DebPrint (("Directory handle already closed!\n"));
+ xfree (dirwatch->buf);
+ xfree (dirwatch->io_info);
+ xfree (dirwatch->watchee);
+ xfree (dirwatch);
+
return 0;
}
else
{
- DebPrint (("Unknown directory handle!\n"));
+ DebPrint (("Unknown dirwatch object!\n"));
return -1;
}
}
Lisp_Object encoded_file, watch_object, watch_descriptor;
char parent_dir[MAX_PATH], *basename;
size_t fn_len;
- HANDLE hdir;
DWORD flags;
BOOL subdirs = FALSE;
+ struct notification *dirwatch = NULL;
Lisp_Object lisp_errstr;
char *errstr;
Qnil);
}
- if (dirwatch.dir)
- error ("File watch already active");
-
- /* We needa full absolute file name of FILE, and we need to remove
+ /* We need a full absolute file name of FILE, and we need to remove
any trailing slashes from it, so that GetFullPathName below gets
the basename part correctly. */
file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
flags = filter_list_to_flags (filter);
- hdir = add_watch (parent_dir, basename, subdirs, flags);
- if (!hdir)
+ dirwatch = add_watch (parent_dir, basename, subdirs, flags);
+ if (!dirwatch)
{
DWORD err = GetLastError ();
report_file_error ("Cannot watch file", Fcons (file, Qnil));
}
/* Store watch object in watch list. */
- watch_descriptor = make_number (hdir);
+ watch_descriptor = XIL ((EMACS_INT)dirwatch);
watch_object = Fcons (watch_descriptor, callback);
watch_list = Fcons (watch_object, watch_list);
(Lisp_Object watch_descriptor)
{
Lisp_Object watch_object;
- HANDLE hdir = (HANDLE)XINT (watch_descriptor);
-
- if (remove_watch (hdir) == -1)
- report_file_error ("Could not remove watch", Fcons (watch_descriptor,
- Qnil));
+ struct notification *dirwatch;
+ int status = -1;
- /* Remove watch descriptor from watch list. */
+ /* Remove the watch object from watch list. Do this before freeing
+ the object, do that even if we fail to free it, watch_list is
+ kept free of junk. */
watch_object = Fassoc (watch_descriptor, watch_list);
if (!NILP (watch_object))
- watch_list = Fdelete (watch_object, watch_list);
+ {
+ watch_list = Fdelete (watch_object, watch_list);
+ dirwatch = (struct notification *)XLI (watch_descriptor);
+ if (w32_valid_pointer_p (dirwatch, sizeof(struct notification)))
+ status = remove_watch (dirwatch);
+ }
+
+ if (status == -1)
+ report_file_error ("Invalid watch descriptor", Fcons (watch_descriptor,
+ Qnil));
return Qnil;
}
Lisp_Object
-get_watch_object (Lisp_Object desc)
+w32_get_watch_object (void *desc)
+{
+ Lisp_Object descriptor = XIL ((EMACS_INT)desc);
+
+ /* This is called from the input queue handling code, inside a
+ critical section, so we cannot possibly QUIT if watch_list is not
+ in the right condition. */
+ return NILP (watch_list) ? Qnil : assoc_no_quit (descriptor, watch_list);
+}
+
+void
+globals_of_w32notify (void)
{
- return Fassoc (desc, watch_list);
+ watch_list = Qnil;
}
void