unix, windows: add stat() based file watcher

Monitors a file path for changes. Supersedes ev_stat.
This commit is contained in:
Ben Noordhuis 2012-06-14 07:30:08 +02:00
parent 9a3dff35c0
commit cc7c8542a5
15 changed files with 448 additions and 20 deletions

View File

@ -37,8 +37,8 @@ RUNNER_LINKFLAGS=$(LINKFLAGS)
RUNNER_LIBS=-lws2_32 -lpsapi -liphlpapi
RUNNER_SRC=test/runner-win.c
uv.a: $(WIN_OBJS) src/cares.o src/uv-common.o $(CARES_OBJS)
$(AR) rcs uv.a $(WIN_OBJS) src/cares.o src/uv-common.o $(CARES_OBJS)
uv.a: $(WIN_OBJS) src/cares.o src/fs-poll.o src/uv-common.o $(CARES_OBJS)
$(AR) rcs uv.a $^
src/%.o: src/%.c include/uv.h include/uv-private/uv-win.h
$(CC) $(CFLAGS) -c $< -o $@

View File

@ -129,8 +129,8 @@ endif
RUNNER_LIBS=
RUNNER_SRC=test/runner-unix.c
uv.a: $(OBJS) src/cares.o src/uv-common.o src/unix/ev/ev.o src/unix/uv-eio.o src/unix/eio/eio.o $(CARES_OBJS)
$(AR) rcs uv.a $(OBJS) src/cares.o src/uv-common.o src/unix/uv-eio.o src/unix/ev/ev.o src/unix/eio/eio.o $(CARES_OBJS)
uv.a: $(OBJS) src/cares.o src/fs-poll.o src/uv-common.o src/unix/ev/ev.o src/unix/uv-eio.o src/unix/eio/eio.o $(CARES_OBJS)
$(AR) rcs uv.a $^
src/%.o: src/%.c include/uv.h include/uv-private/uv-unix.h
$(CC) $(CSTDFLAG) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

View File

@ -249,6 +249,9 @@ struct uv__io_s {
struct stat statbuf; \
eio_req* eio;
#define UV_FS_POLL_PRIVATE_FIELDS \
struct stat statbuf;
#define UV_WORK_PRIVATE_FIELDS \
eio_req* eio;

View File

@ -487,6 +487,9 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s);
}; \
};
#define UV_FS_POLL_PRIVATE_FIELDS \
struct _stati64 statbuf;
#define UV_WORK_PRIVATE_FIELDS \
#define UV_FS_EVENT_PRIVATE_FIELDS \

View File

@ -140,6 +140,7 @@ typedef enum {
XX(ASYNC, async) \
XX(CHECK, check) \
XX(FS_EVENT, fs_event) \
XX(FS_POLL, fs_poll) \
XX(IDLE, idle) \
XX(NAMED_PIPE, pipe) \
XX(POLL, poll) \
@ -209,6 +210,7 @@ typedef struct uv_udp_send_s uv_udp_send_t;
typedef struct uv_fs_s uv_fs_t;
/* uv_fs_event_t is a subclass of uv_handle_t. */
typedef struct uv_fs_event_s uv_fs_event_t;
typedef struct uv_fs_poll_s uv_fs_poll_t;
typedef struct uv_work_s uv_work_t;
@ -295,6 +297,7 @@ typedef void (*uv_async_cb)(uv_async_t* handle, int status);
typedef void (*uv_prepare_cb)(uv_prepare_t* handle, int status);
typedef void (*uv_check_cb)(uv_check_t* handle, int status);
typedef void (*uv_idle_cb)(uv_idle_t* handle, int status);
typedef void (*uv_fs_poll_cb)(uv_fs_poll_t* handle, int status);
typedef void (*uv_getaddrinfo_cb)(uv_getaddrinfo_t* handle, int status,
struct addrinfo* res);
typedef void (*uv_exit_cb)(uv_process_t*, int exit_status, int term_signal);
@ -1510,6 +1513,41 @@ struct uv_fs_event_s {
};
/*
* uv_fs_stat() based polling file watcher.
*/
struct uv_fs_poll_s {
UV_HANDLE_FIELDS
/* Private, don't touch. */
int busy_polling; /* TODO(bnoordhuis) Fold into flags field. */
unsigned int interval;
uint64_t start_time;
char* path;
uv_fs_poll_cb poll_cb;
uv_timer_t timer_handle;
uv_fs_t* fs_req;
UV_FS_POLL_PRIVATE_FIELDS
};
UV_EXTERN int uv_fs_poll_init(uv_loop_t* loop, uv_fs_poll_t* handle);
/*
* Check the file at `path` for changes every `interval` milliseconds.
*
* Your callback gets invoked repeatedly with `status == -1` if `path`
* does not exist or is inaccessible. The watcher is *not* stopped. This
* lets you monitor a path until the resource becomes available (again).
*
* For maximum portability, use multi-second intervals. Sub-second intervals
* will not detect all changes on many file systems.
*/
UV_EXTERN int uv_fs_poll_start(uv_fs_poll_t* handle,
uv_fs_poll_cb poll_cb,
const char* path,
unsigned int interval);
UV_EXTERN int uv_fs_poll_stop(uv_fs_poll_t* handle);
/*
* Gets load avg
* See: http://en.wikipedia.org/wiki/Load_(computing)
@ -1683,22 +1721,23 @@ union uv_any_req {
struct uv_counters_s {
uint64_t async_init;
uint64_t check_init;
uint64_t eio_init;
uint64_t req_init;
uint64_t fs_event_init;
uint64_t fs_poll_init;
uint64_t handle_init;
uint64_t stream_init;
uint64_t tcp_init;
uint64_t udp_init;
uint64_t idle_init;
uint64_t pipe_init;
uint64_t tty_init;
uint64_t poll_init;
uint64_t prepare_init;
uint64_t check_init;
uint64_t idle_init;
uint64_t async_init;
uint64_t timer_init;
uint64_t process_init;
uint64_t fs_event_init;
uint64_t req_init;
uint64_t stream_init;
uint64_t tcp_init;
uint64_t timer_init;
uint64_t tty_init;
uint64_t udp_init;
};

221
src/fs-poll.c Normal file
View File

@ -0,0 +1,221 @@
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "uv.h"
#include "uv-common.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
typedef struct _stati64 uv__statbuf_t;
#else
typedef struct stat uv__statbuf_t;
#endif
static int statbuf_eq(const uv__statbuf_t* a, const uv__statbuf_t* b);
static void timer_cb(uv_timer_t* timer, int status);
static void poll_cb(uv_fs_t* req);
int uv_fs_poll_init(uv_loop_t* loop, uv_fs_poll_t* handle) {
/* TODO(bnoordhuis) Mark fs_req internal. */
uv__handle_init(loop, (uv_handle_t*)handle, UV_FS_POLL);
loop->counters.fs_poll_init++;
if (uv_timer_init(loop, &handle->timer_handle))
return -1;
handle->timer_handle.flags |= UV__HANDLE_INTERNAL;
uv__handle_unref(&handle->timer_handle);
return 0;
}
int uv_fs_poll_start(uv_fs_poll_t* handle,
uv_fs_poll_cb cb,
const char* path,
unsigned int interval) {
uv_fs_t* req;
size_t len;
if (uv__is_active(handle))
return 0;
len = strlen(path) + 1;
req = malloc(sizeof(*req) + len);
if (req == NULL)
return uv__set_artificial_error(handle->loop, UV_ENOMEM);
req->data = handle;
handle->path = memcpy(req + 1, path, len);
handle->fs_req = req;
handle->poll_cb = cb;
handle->interval = interval ? interval : 1;
handle->start_time = uv_now(handle->loop);
handle->busy_polling = 0;
if (uv_fs_stat(handle->loop, handle->fs_req, handle->path, poll_cb))
abort();
uv__handle_start(handle);
return 0;
}
int uv_fs_poll_stop(uv_fs_poll_t* handle) {
if (!uv__is_active(handle))
return 0;
/* Don't free the fs req if it's active. Signal poll_cb that it needs to free
* the req by removing the handle backlink.
*
* TODO(bnoordhuis) Have uv-unix postpone the close callback until the req
* finishes so we don't need this pointer / lifecycle hackery. The callback
* always runs on the next tick now.
*/
if (handle->fs_req->data)
handle->fs_req->data = NULL;
else
free(handle->fs_req);
handle->fs_req = NULL;
handle->path = NULL;
uv_timer_stop(&handle->timer_handle);
uv__handle_stop(handle);
return 0;
}
void uv__fs_poll_close(uv_fs_poll_t* handle) {
uv_fs_poll_stop(handle);
uv_close((uv_handle_t*)&handle->timer_handle, NULL);
}
static void timer_cb(uv_timer_t* timer, int status) {
uv_fs_poll_t* handle;
handle = container_of(timer, uv_fs_poll_t, timer_handle);
handle->start_time = uv_now(handle->loop);
handle->fs_req->data = handle;
if (uv_fs_stat(handle->loop, handle->fs_req, handle->path, poll_cb))
abort();
assert(uv__is_active(handle));
}
static void poll_cb(uv_fs_t* req) {
uv__statbuf_t* statbuf;
uv_fs_poll_t* handle;
uint64_t interval;
handle = req->data;
if (handle == NULL) /* Handle has been stopped or closed. */
goto out;
assert(req == handle->fs_req);
if (req->result != 0) {
/* TODO(bnoordhuis) Only signal the error the first time? What if the
* error reason changes?
*/
uv__set_artificial_error(handle->loop, req->errorno);
handle->poll_cb(handle, -1);
handle->busy_polling = -1;
goto out;
}
statbuf = req->ptr;
if (handle->busy_polling == 0) {
handle->statbuf = *statbuf;
handle->busy_polling = 1;
}
else if (handle->busy_polling == -1) {
handle->statbuf = *statbuf;
handle->busy_polling = 1;
handle->poll_cb(handle, 0); /* Error went away. */
}
else if (!statbuf_eq(statbuf, &handle->statbuf)) {
handle->statbuf = *statbuf;
handle->poll_cb(handle, 0);
}
out:
uv_fs_req_cleanup(req);
if (req->data == NULL) { /* Handle has been stopped or closed. */
free(req);
return;
}
req->data = NULL; /* Tell uv_fs_poll_stop() it's safe to free the req. */
/* Reschedule timer, subtract the delay from doing the stat(). */
interval = handle->interval;
interval -= (uv_now(handle->loop) - handle->start_time) % interval;
if (uv_timer_start(&handle->timer_handle, timer_cb, interval, 0))
abort();
}
static int statbuf_eq(const uv__statbuf_t* a, const uv__statbuf_t* b) {
#ifdef _WIN32
return a->st_mtime == b->st_mtime
&& a->st_size == b->st_size
&& a->st_mode == b->st_mode;
#else
return a->st_ctime == b->st_ctime
&& a->st_mtime == b->st_mtime
&& a->st_size == b->st_size
&& a->st_mode == b->st_mode
&& a->st_uid == b->st_uid
&& a->st_gid == b->st_gid
&& a->st_ino == b->st_ino
&& a->st_dev == b->st_dev;
#endif
}
#ifdef _WIN32
#include "win/internal.h"
#include "win/handle-inl.h"
void uv__fs_poll_endgame(uv_loop_t* loop, uv_fs_poll_t* handle) {
assert(handle->flags & UV_HANDLE_CLOSING);
assert(!(handle->flags & UV_HANDLE_CLOSED));
uv__handle_stop(handle);
uv__handle_close(handle);
}
#endif /* _WIN32 */

View File

@ -109,6 +109,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb close_cb) {
uv__poll_close((uv_poll_t*)handle);
break;
case UV_FS_POLL:
uv__fs_poll_close((uv_fs_poll_t*)handle);
break;
default:
assert(0);
}
@ -133,6 +137,9 @@ static void uv__finish_close(uv_handle_t* handle) {
case UV_ASYNC:
case UV_TIMER:
case UV_PROCESS:
case UV_FS_EVENT:
case UV_FS_POLL:
case UV_POLL:
break;
case UV_NAMED_PIPE:
@ -148,12 +155,6 @@ static void uv__finish_close(uv_handle_t* handle) {
uv__udp_finish_close((uv_udp_t*)handle);
break;
case UV_FS_EVENT:
break;
case UV_POLL:
break;
default:
assert(0);
break;

View File

@ -86,6 +86,8 @@ int uv__tcp_connect6(uv_connect_t* req,
struct sockaddr_in6 address,
uv_connect_cb cb);
void uv__fs_poll_close(uv_fs_poll_t* handle);
UNUSED static int uv__has_active_reqs(const uv_loop_t* loop) {
return !ngx_queue_empty(&loop->active_reqs);

View File

@ -131,6 +131,10 @@ INLINE static void uv_process_endgames(uv_loop_t* loop) {
uv_fs_event_endgame(loop, (uv_fs_event_t*) handle);
break;
case UV_FS_POLL:
uv__fs_poll_endgame(loop, (uv_fs_poll_t*) handle);
break;
default:
assert(0);
break;

View File

@ -132,6 +132,12 @@ void uv_close(uv_handle_t* handle, uv_close_cb cb) {
uv_fs_event_close(loop, (uv_fs_event_t*) handle);
return;
case UV_FS_POLL:
uv__fs_poll_close((uv_fs_poll_t*) handle);
uv__handle_start(handle);
uv_want_endgame(loop, handle);
return;
default:
/* Not supported */
abort();

View File

@ -268,6 +268,12 @@ void uv_fs_event_close(uv_loop_t* loop, uv_fs_event_t* handle);
void uv_fs_event_endgame(uv_loop_t* loop, uv_fs_event_t* handle);
/*
* Stat poller.
*/
void uv__fs_poll_endgame(uv_loop_t* loop, uv_fs_poll_t* handle);
/*
* Utilities.
*/

127
test/test-fs-poll.c Normal file
View File

@ -0,0 +1,127 @@
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "uv.h"
#include "task.h"
#include <sys/stat.h>
#include <fcntl.h>
#define FIXTURE "testfile"
static void poll_cb(uv_fs_poll_t* handle, int status);
static void timer_cb(uv_timer_t* handle, int status);
static void close_cb(uv_handle_t* handle);
static uv_fs_poll_t poll_handle;
static uv_timer_t timer_handle;
static uv_loop_t* loop;
static int poll_cb_called;
static int timer_cb_called;
static int close_cb_called;
static void touch_file(const char* path) {
static int count;
FILE* fp;
int i;
ASSERT((fp = fopen(FIXTURE, "w+")));
/* Need to change the file size because the poller may not pick up
* sub-second mtime changes.
*/
i = ++count;
while (i--)
fputc('*', fp);
fclose(fp);
}
static void close_cb(uv_handle_t* handle) {
close_cb_called++;
}
static void timer_cb(uv_timer_t* handle, int status) {
touch_file(FIXTURE);
timer_cb_called++;
}
static void poll_cb(uv_fs_poll_t* handle, int status) {
ASSERT(handle == &poll_handle);
ASSERT(uv_is_active((uv_handle_t*)handle));
switch (poll_cb_called++) {
case 0:
ASSERT(status == -1);
ASSERT(uv_last_error(loop).code == UV_ENOENT);
touch_file(FIXTURE);
break;
case 1:
ASSERT(status == 0);
ASSERT(0 == uv_timer_start(&timer_handle, timer_cb, 20, 0));
break;
case 2:
ASSERT(status == 0);
ASSERT(0 == uv_timer_start(&timer_handle, timer_cb, 200, 0));
break;
case 3:
ASSERT(status == 0);
remove(FIXTURE);
break;
case 4:
ASSERT(status == -1);
ASSERT(uv_last_error(loop).code == UV_ENOENT);
uv_close((uv_handle_t*)handle, close_cb);
break;
default:
ASSERT(0);
}
}
TEST_IMPL(fs_poll) {
loop = uv_default_loop();
remove(FIXTURE);
ASSERT(0 == uv_timer_init(loop, &timer_handle));
ASSERT(0 == uv_fs_poll_init(loop, &poll_handle));
ASSERT(0 == uv_fs_poll_start(&poll_handle, poll_cb, FIXTURE, 100));
ASSERT(0 == uv_run(loop));
ASSERT(poll_cb_called == 5);
ASSERT(timer_cb_called == 2);
ASSERT(close_cb_called == 1);
uv_loop_delete(loop);
return 0;
}

View File

@ -97,6 +97,7 @@ TEST_DECLARE (unref_in_prepare_cb)
TEST_DECLARE (timer_ref)
TEST_DECLARE (timer_ref2)
TEST_DECLARE (fs_event_ref)
TEST_DECLARE (fs_poll_ref)
TEST_DECLARE (tcp_ref)
TEST_DECLARE (tcp_ref2)
TEST_DECLARE (tcp_ref3)
@ -134,6 +135,7 @@ TEST_DECLARE (spawn_and_ping)
TEST_DECLARE (spawn_setuid_fails)
TEST_DECLARE (spawn_setgid_fails)
TEST_DECLARE (spawn_stdout_to_file)
TEST_DECLARE (fs_poll)
TEST_DECLARE (kill)
TEST_DECLARE (fs_file_noent)
TEST_DECLARE (fs_file_nametoolong)
@ -293,6 +295,7 @@ TASK_LIST_START
TEST_ENTRY (ref)
TEST_ENTRY (idle_ref)
TEST_ENTRY (fs_poll_ref)
TEST_ENTRY (async_ref)
TEST_ENTRY (prepare_ref)
TEST_ENTRY (check_ref)
@ -360,6 +363,7 @@ TASK_LIST_START
TEST_ENTRY (spawn_setuid_fails)
TEST_ENTRY (spawn_setgid_fails)
TEST_ENTRY (spawn_stdout_to_file)
TEST_ENTRY (fs_poll)
TEST_ENTRY (kill)
#ifdef _WIN32
TEST_ENTRY (spawn_detect_pipe_name_collisions_on_windows)

View File

@ -168,6 +168,16 @@ TEST_IMPL(fs_event_ref) {
}
TEST_IMPL(fs_poll_ref) {
uv_fs_poll_t h;
uv_fs_poll_init(uv_default_loop(), &h);
uv_fs_poll_start(&h, NULL, ".", 999);
uv_unref((uv_handle_t*)&h);
uv_run(uv_default_loop());
return 0;
}
TEST_IMPL(tcp_ref) {
uv_tcp_t h;
uv_tcp_init(uv_default_loop(), &h);

2
uv.gyp
View File

@ -50,6 +50,7 @@
'include/uv-private/ngx-queue.h',
'include/uv-private/tree.h',
'src/cares.c',
'src/fs-poll.c',
'src/uv-common.c',
'src/uv-common.h',
'src/ares/ares_cancel.c',
@ -339,6 +340,7 @@
'test/test-shutdown-close.c',
'test/test-shutdown-eof.c',
'test/test-spawn.c',
'test/test-fs-poll.c',
'test/test-stdio-over-pipes.c',
'test/test-tcp-bind-error.c',
'test/test-tcp-bind6-error.c',