cool.io 1.9.4 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yaml +1 -0
- data/.github/workflows/valgrind.yaml +41 -0
- data/Rakefile +7 -19
- data/cool.io.gemspec +1 -3
- data/ext/cool.io/buffer.c +77 -52
- data/ext/cool.io/cool.io.h +6 -0
- data/ext/cool.io/ev_wrap.h +1 -0
- data/ext/cool.io/extconf.rb +0 -1
- data/ext/cool.io/iowatcher.c +18 -1
- data/ext/cool.io/loop.c +21 -19
- data/ext/cool.io/stat_watcher.c +44 -19
- data/ext/cool.io/timer_watcher.c +1 -1
- data/ext/cool.io/watcher.c +13 -1
- data/ext/cool.io/watcher.h +0 -17
- data/ext/libev/ev.c +80 -12
- data/ext/libev/ev_select.c +10 -0
- data/ext/libev/ev_vars.h +4 -0
- data/ext/libev/ev_win32_stat.c +851 -0
- data/ext/libev/ev_wrap.h +4 -0
- data/gems.rb +4 -0
- data/lib/cool.io/dns_resolver.rb +53 -8
- data/lib/cool.io/loop.rb +5 -1
- data/lib/cool.io/version.rb +2 -2
- data/libev_win_select.diff +25 -9
- data/spec/async_watcher_spec.rb +3 -1
- data/spec/detach_race_condition_spec.rb +59 -25
- data/spec/dns_spec.rb +197 -0
- data/spec/fixtures/windows_stat_native.c +1305 -0
- data/spec/iobuffer_spec.rb +88 -1
- data/spec/loop_spec.rb +31 -0
- data/spec/stat_watcher_spec.rb +86 -1
- data/spec/tcp_server_spec.rb +34 -10
- data/spec/watcher_reattach_spec.rb +54 -0
- data/spec/windows_stat_native_spec.rb +193 -0
- data/spec/windows_stat_watcher_spec.rb +363 -0
- metadata +14 -2
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
/* Windows notification support for ev_stat. Included by ev.c.
|
|
2
|
+
* Copyright (C) 2026. Distributed under the same terms as ev.c.
|
|
3
|
+
*
|
|
4
|
+
* Only the loop thread touches ev_stat or runs callbacks. The IOCP thread
|
|
5
|
+
* rearms directory reads and publishes dirty flags under a critical section.
|
|
6
|
+
* Directory events (including overflow) are hints to re-stat, not callbacks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
struct ev_win32_subscription
|
|
10
|
+
{
|
|
11
|
+
struct ev_win32_subscription *next, **prev;
|
|
12
|
+
struct ev_win32_dir *dir;
|
|
13
|
+
struct ev_win32_watch *watch;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
struct ev_win32_name
|
|
17
|
+
{
|
|
18
|
+
struct ev_win32_name *next;
|
|
19
|
+
struct ev_win32_watch *watch;
|
|
20
|
+
wchar_t *key;
|
|
21
|
+
size_t length;
|
|
22
|
+
unsigned int hash;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
struct ev_win32_dir
|
|
26
|
+
{
|
|
27
|
+
struct ev_win32_dir *next, **prev, *hash_next;
|
|
28
|
+
HANDLE handle;
|
|
29
|
+
OVERLAPPED overlapped;
|
|
30
|
+
wchar_t *path;
|
|
31
|
+
unsigned int refs, hash, namecnt, namemask;
|
|
32
|
+
ev_tstamp checked_at;
|
|
33
|
+
int pending, retired, failed, dirty;
|
|
34
|
+
struct ev_win32_subscription *files, *children;
|
|
35
|
+
struct ev_win32_name **names;
|
|
36
|
+
DWORD buffer[4096]; /* DWORD aligned; below the network 64 KiB limit */
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
struct ev_win32_watch
|
|
40
|
+
{
|
|
41
|
+
struct ev_win32_watch *dirty_next, **dirty_prev;
|
|
42
|
+
struct ev_win32_subscription parent, children;
|
|
43
|
+
struct ev_win32_name keys[3];
|
|
44
|
+
wchar_t *path;
|
|
45
|
+
const wchar_t *name;
|
|
46
|
+
ev_stat *watcher;
|
|
47
|
+
int dirty, names_dirty, names_failed;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
struct ev_win32_fs
|
|
51
|
+
{
|
|
52
|
+
HANDLE port, thread;
|
|
53
|
+
CRITICAL_SECTION lock;
|
|
54
|
+
ev_async async;
|
|
55
|
+
struct ev_win32_dir *dirs, **dirtab;
|
|
56
|
+
unsigned int dircnt, dirmask;
|
|
57
|
+
struct ev_win32_watch **watches;
|
|
58
|
+
int watchcnt, watchmax;
|
|
59
|
+
struct ev_win32_watch *dirty;
|
|
60
|
+
int stopping;
|
|
61
|
+
#if EV_MULTIPLICITY
|
|
62
|
+
struct ev_loop *owner;
|
|
63
|
+
#endif
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
static void win32_fs_add_locked (EV_P_ ev_stat *w, int polling);
|
|
67
|
+
static int win32_dir_matches (struct ev_win32_dir *dir, const wchar_t *parent);
|
|
68
|
+
static size_t win32_fs_root (const wchar_t *path);
|
|
69
|
+
|
|
70
|
+
static unsigned int
|
|
71
|
+
win32_hash (const wchar_t *key, size_t length)
|
|
72
|
+
{
|
|
73
|
+
unsigned int hash = 2166136261u;
|
|
74
|
+
size_t i;
|
|
75
|
+
for (i = 0; i < length; ++i)
|
|
76
|
+
hash = (hash ^ (key[i] >= L'a' && key[i] <= L'z' ? key[i] - (L'a' - L'A') : key[i])) * 16777619u;
|
|
77
|
+
return hash;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static int
|
|
81
|
+
win32_fold (const wchar_t *name, size_t length, wchar_t *key)
|
|
82
|
+
{
|
|
83
|
+
return length && LCMapStringEx (LOCALE_NAME_INVARIANT, LCMAP_UPPERCASE, name, (int)length,
|
|
84
|
+
key, (int)length, 0, 0, 0) == (int)length;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static void
|
|
88
|
+
win32_name_link (struct ev_win32_dir *dir, struct ev_win32_name *node)
|
|
89
|
+
{
|
|
90
|
+
struct ev_win32_name **bucket = &dir->names[node->hash & dir->namemask];
|
|
91
|
+
node->next = *bucket;
|
|
92
|
+
*bucket = node;
|
|
93
|
+
if (++dir->namecnt > dir->namemask + 1)
|
|
94
|
+
{
|
|
95
|
+
unsigned int mask = dir->namemask * 2 + 1, i;
|
|
96
|
+
struct ev_win32_name **names = (struct ev_win32_name **)calloc (mask + 1, sizeof (*names));
|
|
97
|
+
if (!names)
|
|
98
|
+
return;
|
|
99
|
+
for (i = 0; i <= dir->namemask; ++i)
|
|
100
|
+
while ((node = dir->names[i]))
|
|
101
|
+
{
|
|
102
|
+
dir->names[i] = node->next;
|
|
103
|
+
node->next = names[node->hash & mask];
|
|
104
|
+
names[node->hash & mask] = node;
|
|
105
|
+
}
|
|
106
|
+
free (dir->names);
|
|
107
|
+
dir->names = names;
|
|
108
|
+
dir->namemask = mask;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static void
|
|
113
|
+
win32_name_unlink (struct ev_win32_dir *dir, struct ev_win32_name *node)
|
|
114
|
+
{
|
|
115
|
+
struct ev_win32_name **link = &dir->names[node->hash & dir->namemask];
|
|
116
|
+
while (*link != node)
|
|
117
|
+
link = &(*link)->next;
|
|
118
|
+
*link = node->next;
|
|
119
|
+
--dir->namecnt;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static int
|
|
123
|
+
win32_name_key (struct ev_win32_watch *watch, int index, const wchar_t *name)
|
|
124
|
+
{
|
|
125
|
+
struct ev_win32_name *node = &watch->keys[index];
|
|
126
|
+
int i;
|
|
127
|
+
node->watch = watch;
|
|
128
|
+
node->length = name ? wcslen (name) : 0;
|
|
129
|
+
node->key = node->length ? (wchar_t *)malloc (node->length * sizeof (wchar_t)) : 0;
|
|
130
|
+
if (!node->key)
|
|
131
|
+
return !node->length;
|
|
132
|
+
if (!win32_fold (name, node->length, node->key))
|
|
133
|
+
{ free (node->key); node->key = 0; return 0; }
|
|
134
|
+
node->hash = win32_hash (node->key, node->length);
|
|
135
|
+
for (i = 0; i < index; ++i)
|
|
136
|
+
if (watch->keys[i].key && watch->keys[i].length == node->length
|
|
137
|
+
&& !memcmp (watch->keys[i].key, node->key, node->length * sizeof (wchar_t)))
|
|
138
|
+
{
|
|
139
|
+
free (node->key);
|
|
140
|
+
node->key = 0;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static void
|
|
147
|
+
win32_watch_names (struct ev_win32_watch *watch)
|
|
148
|
+
{
|
|
149
|
+
struct ev_win32_dir *dir = watch->parent.dir;
|
|
150
|
+
WIN32_FIND_DATAW data;
|
|
151
|
+
HANDLE handle;
|
|
152
|
+
int i, keyed = 1;
|
|
153
|
+
for (i = 1; i < 3; ++i)
|
|
154
|
+
if (watch->keys[i].key)
|
|
155
|
+
{
|
|
156
|
+
if (dir)
|
|
157
|
+
win32_name_unlink (dir, &watch->keys[i]);
|
|
158
|
+
free (watch->keys[i].key);
|
|
159
|
+
watch->keys[i].key = 0;
|
|
160
|
+
}
|
|
161
|
+
watch->names_dirty = 0;
|
|
162
|
+
if (win32_fs_root (watch->path) < wcslen (watch->path) && !wcspbrk (watch->name, L"*?<>\""))
|
|
163
|
+
{
|
|
164
|
+
handle = FindFirstFileExW (watch->path, FindExInfoStandard, &data, FindExSearchNameMatch, 0, 0);
|
|
165
|
+
if (handle == INVALID_HANDLE_VALUE)
|
|
166
|
+
keyed = 0;
|
|
167
|
+
else
|
|
168
|
+
{
|
|
169
|
+
FindClose (handle);
|
|
170
|
+
keyed = win32_name_key (watch, 1, *data.cAlternateFileName ? data.cAlternateFileName : 0);
|
|
171
|
+
keyed &= win32_name_key (watch, 2, data.cFileName);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (dir)
|
|
175
|
+
for (i = 1; i < 3; ++i)
|
|
176
|
+
if (watch->keys[i].key)
|
|
177
|
+
win32_name_link (dir, &watch->keys[i]);
|
|
178
|
+
watch->names_failed = !keyed;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Called with ctx->lock held. Queue each watcher at most once per batch. */
|
|
182
|
+
static void
|
|
183
|
+
win32_watch_dirty (struct ev_win32_fs *ctx, struct ev_win32_watch *watch)
|
|
184
|
+
{
|
|
185
|
+
if (!watch->dirty)
|
|
186
|
+
{
|
|
187
|
+
watch->dirty = 1;
|
|
188
|
+
watch->dirty_next = ctx->dirty;
|
|
189
|
+
watch->dirty_prev = &ctx->dirty;
|
|
190
|
+
if (ctx->dirty)
|
|
191
|
+
ctx->dirty->dirty_prev = &watch->dirty_next;
|
|
192
|
+
ctx->dirty = watch;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Name records only select paths to re-stat. Missing or malformed records
|
|
197
|
+
* (including buffer overflow) invalidate every subscriber to this directory. */
|
|
198
|
+
static void
|
|
199
|
+
win32_dir_notify (struct ev_win32_fs *ctx, struct ev_win32_dir *dir, DWORD bytes)
|
|
200
|
+
{
|
|
201
|
+
struct ev_win32_subscription *sub;
|
|
202
|
+
struct ev_win32_name *node;
|
|
203
|
+
wchar_t key[MAX_PATH];
|
|
204
|
+
DWORD offset = 0;
|
|
205
|
+
dir->dirty = 1;
|
|
206
|
+
for (sub = dir->children; sub; sub = sub->next)
|
|
207
|
+
win32_watch_dirty (ctx, sub->watch);
|
|
208
|
+
while (bytes && offset < bytes)
|
|
209
|
+
{
|
|
210
|
+
FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION *)((char *)dir->buffer + offset);
|
|
211
|
+
DWORD remaining = bytes - offset;
|
|
212
|
+
DWORD header = offsetof (FILE_NOTIFY_INFORMATION, FileName);
|
|
213
|
+
size_t length;
|
|
214
|
+
unsigned int hash;
|
|
215
|
+
if (remaining < header || event->FileNameLength % sizeof (wchar_t)
|
|
216
|
+
|| event->FileNameLength > remaining - header)
|
|
217
|
+
break;
|
|
218
|
+
length = event->FileNameLength / sizeof (wchar_t);
|
|
219
|
+
if (length && dir->namecnt)
|
|
220
|
+
{
|
|
221
|
+
if (length > MAX_PATH || !win32_fold (event->FileName, length, key))
|
|
222
|
+
break;
|
|
223
|
+
hash = win32_hash (key, length);
|
|
224
|
+
for (node = dir->names[hash & dir->namemask]; node; node = node->next)
|
|
225
|
+
if (node->hash == hash && node->length == length
|
|
226
|
+
&& !memcmp (node->key, key, length * sizeof (wchar_t)))
|
|
227
|
+
{
|
|
228
|
+
/* Replacement can change aliases even with equal stat data. */
|
|
229
|
+
if (event->Action != FILE_ACTION_MODIFIED)
|
|
230
|
+
{
|
|
231
|
+
node->watch->names_dirty = 1;
|
|
232
|
+
if (node->watch->children.dir)
|
|
233
|
+
node->watch->children.dir->dirty = 1;
|
|
234
|
+
}
|
|
235
|
+
win32_watch_dirty (ctx, node->watch);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!event->NextEntryOffset)
|
|
239
|
+
return;
|
|
240
|
+
if (event->NextEntryOffset < header + event->FileNameLength
|
|
241
|
+
|| event->NextEntryOffset >= remaining || event->NextEntryOffset % sizeof (DWORD))
|
|
242
|
+
break;
|
|
243
|
+
offset += event->NextEntryOffset;
|
|
244
|
+
}
|
|
245
|
+
for (sub = dir->files; sub; sub = sub->next)
|
|
246
|
+
{
|
|
247
|
+
sub->watch->names_dirty = 1; /* Overflow may have hidden a replacement. */
|
|
248
|
+
if (sub->watch->children.dir)
|
|
249
|
+
sub->watch->children.dir->dirty = 1;
|
|
250
|
+
win32_watch_dirty (ctx, sub->watch);
|
|
251
|
+
}
|
|
252
|
+
for (sub = dir->children; sub; sub = sub->next)
|
|
253
|
+
sub->watch->names_dirty = 1;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
static int
|
|
257
|
+
win32_dir_read (struct ev_win32_dir *dir)
|
|
258
|
+
{
|
|
259
|
+
memset (&dir->overlapped, 0, sizeof (dir->overlapped));
|
|
260
|
+
dir->pending = ReadDirectoryChangesW (dir->handle, dir->buffer, sizeof (dir->buffer), FALSE,
|
|
261
|
+
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME
|
|
262
|
+
| FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE
|
|
263
|
+
| FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS
|
|
264
|
+
| FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY,
|
|
265
|
+
0, &dir->overlapped, 0) != 0;
|
|
266
|
+
return dir->pending;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Must hold ctx->lock. A retired directory is freed only after its completion
|
|
270
|
+
* packet has been dequeued: closing/cancelling a HANDLE alone is not enough. */
|
|
271
|
+
static void
|
|
272
|
+
win32_dir_free (struct ev_win32_fs *ctx, struct ev_win32_dir *dir)
|
|
273
|
+
{
|
|
274
|
+
struct ev_win32_dir **link = &ctx->dirtab[dir->hash & ctx->dirmask];
|
|
275
|
+
while (*link != dir)
|
|
276
|
+
link = &(*link)->hash_next;
|
|
277
|
+
*link = dir->hash_next;
|
|
278
|
+
--ctx->dircnt;
|
|
279
|
+
*dir->prev = dir->next;
|
|
280
|
+
if (dir->next)
|
|
281
|
+
dir->next->prev = dir->prev;
|
|
282
|
+
free (dir->names);
|
|
283
|
+
free (dir->path);
|
|
284
|
+
free (dir);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
static void
|
|
288
|
+
win32_dir_retire (struct ev_win32_fs *ctx, struct ev_win32_dir *dir)
|
|
289
|
+
{
|
|
290
|
+
dir->retired = 1;
|
|
291
|
+
CancelIoEx (dir->handle, &dir->overlapped);
|
|
292
|
+
CloseHandle (dir->handle);
|
|
293
|
+
if (!dir->pending)
|
|
294
|
+
win32_dir_free (ctx, dir);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
static unsigned int __stdcall
|
|
298
|
+
win32_fs_thread (void *arg)
|
|
299
|
+
{
|
|
300
|
+
struct ev_win32_fs *ctx = (struct ev_win32_fs *)arg;
|
|
301
|
+
for (;;)
|
|
302
|
+
{
|
|
303
|
+
DWORD bytes;
|
|
304
|
+
ULONG_PTR key;
|
|
305
|
+
OVERLAPPED *overlapped;
|
|
306
|
+
BOOL ok = GetQueuedCompletionStatus (ctx->port, &bytes, &key, &overlapped, INFINITE);
|
|
307
|
+
DWORD error = ok ? ERROR_SUCCESS : GetLastError ();
|
|
308
|
+
|
|
309
|
+
EnterCriticalSection (&ctx->lock);
|
|
310
|
+
if (overlapped)
|
|
311
|
+
{
|
|
312
|
+
struct ev_win32_dir *dir = (struct ev_win32_dir *)key;
|
|
313
|
+
dir->pending = 0;
|
|
314
|
+
if (dir->retired)
|
|
315
|
+
win32_dir_free (ctx, dir);
|
|
316
|
+
else
|
|
317
|
+
{
|
|
318
|
+
win32_dir_notify (ctx, dir, ok && bytes <= sizeof (dir->buffer) ? bytes : 0);
|
|
319
|
+
|
|
320
|
+
if ((!ok && error != ERROR_NOTIFY_ENUM_DIR) || !win32_dir_read (dir))
|
|
321
|
+
{
|
|
322
|
+
dir->failed = 1;
|
|
323
|
+
win32_dir_notify (ctx, dir, 0);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#if EV_MULTIPLICITY
|
|
327
|
+
ev_async_send (ctx->owner, &ctx->async);
|
|
328
|
+
#else
|
|
329
|
+
ev_async_send (&ctx->async);
|
|
330
|
+
#endif
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (ctx->stopping && !ctx->dirs)
|
|
335
|
+
{
|
|
336
|
+
LeaveCriticalSection (&ctx->lock);
|
|
337
|
+
CloseHandle (ctx->thread);
|
|
338
|
+
CloseHandle (ctx->port);
|
|
339
|
+
DeleteCriticalSection (&ctx->lock);
|
|
340
|
+
free (ctx->dirtab);
|
|
341
|
+
free (ctx);
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
LeaveCriticalSection (&ctx->lock);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
static void
|
|
349
|
+
win32_fs_cb (EV_P_ ev_async *async, int revents)
|
|
350
|
+
{
|
|
351
|
+
struct ev_win32_fs *ctx = (struct ev_win32_fs *)async->data;
|
|
352
|
+
struct ev_win32_dir *dir;
|
|
353
|
+
struct ev_win32_watch *watch;
|
|
354
|
+
struct win32_stat_batch
|
|
355
|
+
{
|
|
356
|
+
ev_stat *watcher;
|
|
357
|
+
int refresh;
|
|
358
|
+
} *batch;
|
|
359
|
+
size_t count = 0, i = 0;
|
|
360
|
+
int refresh;
|
|
361
|
+
|
|
362
|
+
EnterCriticalSection (&ctx->lock);
|
|
363
|
+
/* Check identity once per directory per batch, rather than once for every
|
|
364
|
+
* subscriber. A HANDLE can still refer to a directory moved elsewhere. */
|
|
365
|
+
for (dir = ctx->dirs; dir; dir = dir->next)
|
|
366
|
+
if (dir->dirty && !dir->retired)
|
|
367
|
+
{
|
|
368
|
+
dir->dirty = 0;
|
|
369
|
+
if (dir->failed || !win32_dir_matches (dir, dir->path))
|
|
370
|
+
{
|
|
371
|
+
dir->failed = 1;
|
|
372
|
+
win32_dir_notify (ctx, dir, 0);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
for (watch = ctx->dirty; watch; watch = watch->dirty_next)
|
|
376
|
+
++count;
|
|
377
|
+
batch = count ? (struct win32_stat_batch *)malloc (count * sizeof (*batch)) : 0;
|
|
378
|
+
if (batch)
|
|
379
|
+
while ((watch = ctx->dirty))
|
|
380
|
+
{
|
|
381
|
+
ctx->dirty = watch->dirty_next;
|
|
382
|
+
if (ctx->dirty)
|
|
383
|
+
ctx->dirty->dirty_prev = &ctx->dirty;
|
|
384
|
+
watch->dirty = 0;
|
|
385
|
+
refresh = watch->parent.dir->failed
|
|
386
|
+
|| (watch->children.dir && watch->children.dir->failed)
|
|
387
|
+
|| (watch->names_dirty && !watch->children.dir && watch->watcher->attr.st_nlink
|
|
388
|
+
&& (watch->watcher->attr.st_mode & S_IFMT) == S_IFDIR);
|
|
389
|
+
if (watch->names_dirty && !refresh)
|
|
390
|
+
win32_watch_names (watch);
|
|
391
|
+
batch[i].watcher = watch->watcher;
|
|
392
|
+
batch[i++].refresh = refresh;
|
|
393
|
+
}
|
|
394
|
+
LeaveCriticalSection (&ctx->lock);
|
|
395
|
+
|
|
396
|
+
/* stat_timer_cb only queues user callbacks, so these ev_stat pointers stay
|
|
397
|
+
* valid for this batch. Notifications arriving meanwhile form the next one. */
|
|
398
|
+
if (batch)
|
|
399
|
+
for (i = 0; i < count; ++i)
|
|
400
|
+
{
|
|
401
|
+
if (batch[i].refresh)
|
|
402
|
+
{
|
|
403
|
+
EnterCriticalSection (&ctx->lock);
|
|
404
|
+
win32_fs_add_locked (EV_A_ batch[i].watcher, 0);
|
|
405
|
+
LeaveCriticalSection (&ctx->lock);
|
|
406
|
+
}
|
|
407
|
+
stat_timer_cb (EV_A_ &batch[i].watcher->timer, EV_STAT);
|
|
408
|
+
}
|
|
409
|
+
free (batch);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
static int
|
|
413
|
+
win32_fs_init (EV_P)
|
|
414
|
+
{
|
|
415
|
+
struct ev_win32_fs *ctx;
|
|
416
|
+
if (win32_fs)
|
|
417
|
+
return 1;
|
|
418
|
+
if (win32_fs_disabled)
|
|
419
|
+
return 0;
|
|
420
|
+
|
|
421
|
+
ctx = (struct ev_win32_fs *)calloc (1, sizeof (*ctx));
|
|
422
|
+
if (!ctx)
|
|
423
|
+
return 0;
|
|
424
|
+
ctx->port = CreateIoCompletionPort (INVALID_HANDLE_VALUE, 0, 0, 1);
|
|
425
|
+
if (!ctx->port)
|
|
426
|
+
{ free (ctx); return 0; }
|
|
427
|
+
/* Prepare the same pipe ev_async_start will use, with a recoverable failure
|
|
428
|
+
* path for optional notifications. Existing async/signal callers still retry. */
|
|
429
|
+
if (!evpipe_init_try (EV_A))
|
|
430
|
+
{ CloseHandle (ctx->port); free (ctx); return 0; }
|
|
431
|
+
InitializeCriticalSection (&ctx->lock);
|
|
432
|
+
#if EV_MULTIPLICITY
|
|
433
|
+
ctx->owner = EV_A;
|
|
434
|
+
#endif
|
|
435
|
+
ev_async_init (&ctx->async, win32_fs_cb);
|
|
436
|
+
ctx->async.data = ctx;
|
|
437
|
+
ev_async_start (EV_A_ &ctx->async);
|
|
438
|
+
ev_unref (EV_A); /* internal watcher must not keep the loop running */
|
|
439
|
+
ctx->thread = (HANDLE)_beginthreadex (0, 0, win32_fs_thread, ctx, 0, 0);
|
|
440
|
+
if (!ctx->thread)
|
|
441
|
+
{
|
|
442
|
+
ev_ref (EV_A);
|
|
443
|
+
ev_async_stop (EV_A_ &ctx->async);
|
|
444
|
+
DeleteCriticalSection (&ctx->lock);
|
|
445
|
+
CloseHandle (ctx->port);
|
|
446
|
+
free (ctx);
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
win32_fs = ctx;
|
|
450
|
+
return 1;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/* Include the separator in drive, UNC share and extended-length roots. */
|
|
454
|
+
static size_t
|
|
455
|
+
win32_fs_root (const wchar_t *path)
|
|
456
|
+
{
|
|
457
|
+
const wchar_t *start = path, *slash;
|
|
458
|
+
if (!wcsncmp (start, L"\\\\?\\", 4))
|
|
459
|
+
{
|
|
460
|
+
start += 4;
|
|
461
|
+
if (!_wcsnicmp (start, L"UNC\\", 4))
|
|
462
|
+
start += 4;
|
|
463
|
+
else
|
|
464
|
+
{
|
|
465
|
+
slash = wcschr (start, L'\\');
|
|
466
|
+
return slash ? slash - path + 1 : wcslen (path);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
else if (start[0] == L'\\' && start[1] == L'\\')
|
|
470
|
+
start += 2;
|
|
471
|
+
else
|
|
472
|
+
return start[0] && start[1] == L':' && start[2] == L'\\' ? 3 : 0;
|
|
473
|
+
|
|
474
|
+
slash = wcschr (start, L'\\');
|
|
475
|
+
if (slash)
|
|
476
|
+
slash = wcschr (slash + 1, L'\\');
|
|
477
|
+
return slash ? slash - path + 1 : wcslen (path);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/* Snapshot an absolute path at registration. The configured codepage matches
|
|
481
|
+
* the stat wrapper (UTF-8 for Ruby); all HANDLE operations are wide. */
|
|
482
|
+
static wchar_t *
|
|
483
|
+
win32_fs_path (const char *path, int parent)
|
|
484
|
+
{
|
|
485
|
+
wchar_t *input, *absolute, *slash;
|
|
486
|
+
int length = MultiByteToWideChar (EV_WIN32_STAT_CODEPAGE, 0, path, -1, 0, 0);
|
|
487
|
+
DWORD needed, written;
|
|
488
|
+
size_t root;
|
|
489
|
+
if (!length)
|
|
490
|
+
return 0;
|
|
491
|
+
input = (wchar_t *)malloc (length * sizeof (wchar_t));
|
|
492
|
+
if (!input)
|
|
493
|
+
return 0;
|
|
494
|
+
MultiByteToWideChar (EV_WIN32_STAT_CODEPAGE, 0, path, -1, input, length);
|
|
495
|
+
needed = GetFullPathNameW (input, 0, 0, 0);
|
|
496
|
+
absolute = needed ? (wchar_t *)malloc (needed * sizeof (wchar_t)) : 0;
|
|
497
|
+
written = absolute ? GetFullPathNameW (input, needed, absolute, 0) : 0;
|
|
498
|
+
if (!written || written >= needed)
|
|
499
|
+
{ free (input); free (absolute); return 0; }
|
|
500
|
+
free (input);
|
|
501
|
+
root = win32_fs_root (absolute);
|
|
502
|
+
length = (int)wcslen (absolute);
|
|
503
|
+
while ((size_t)length > root && (absolute[length - 1] == L'\\' || absolute[length - 1] == L'/'))
|
|
504
|
+
absolute[--length] = 0;
|
|
505
|
+
if (!parent || (size_t)length <= root)
|
|
506
|
+
return absolute;
|
|
507
|
+
slash = wcsrchr (absolute, L'\\');
|
|
508
|
+
if (!slash)
|
|
509
|
+
{ free (absolute); return 0; }
|
|
510
|
+
if ((size_t)(slash - absolute) < root)
|
|
511
|
+
absolute[root] = 0;
|
|
512
|
+
else
|
|
513
|
+
*slash = 0;
|
|
514
|
+
return absolute;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
static void
|
|
518
|
+
win32_subscription_add (struct ev_win32_subscription *sub, struct ev_win32_watch *watch,
|
|
519
|
+
struct ev_win32_dir *dir)
|
|
520
|
+
{
|
|
521
|
+
struct ev_win32_subscription **head;
|
|
522
|
+
int i;
|
|
523
|
+
sub->dir = dir;
|
|
524
|
+
sub->watch = watch;
|
|
525
|
+
if (!dir)
|
|
526
|
+
return;
|
|
527
|
+
head = sub == &watch->children ? &dir->children : &dir->files;
|
|
528
|
+
sub->next = *head;
|
|
529
|
+
sub->prev = head;
|
|
530
|
+
if (*head)
|
|
531
|
+
(*head)->prev = &sub->next;
|
|
532
|
+
*head = sub;
|
|
533
|
+
if (sub == &watch->parent)
|
|
534
|
+
for (i = 0; i < 3; ++i)
|
|
535
|
+
if (watch->keys[i].key)
|
|
536
|
+
win32_name_link (dir, &watch->keys[i]);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
static void
|
|
540
|
+
win32_subscription_del (struct ev_win32_fs *ctx, struct ev_win32_subscription *sub)
|
|
541
|
+
{
|
|
542
|
+
struct ev_win32_dir *dir = sub->dir;
|
|
543
|
+
int i;
|
|
544
|
+
if (!dir)
|
|
545
|
+
return;
|
|
546
|
+
if (sub == &sub->watch->parent)
|
|
547
|
+
for (i = 0; i < 3; ++i)
|
|
548
|
+
if (sub->watch->keys[i].key)
|
|
549
|
+
win32_name_unlink (dir, &sub->watch->keys[i]);
|
|
550
|
+
*sub->prev = sub->next;
|
|
551
|
+
if (sub->next)
|
|
552
|
+
sub->next->prev = sub->prev;
|
|
553
|
+
if (!--dir->refs)
|
|
554
|
+
win32_dir_retire (ctx, dir);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
static struct ev_win32_watch *
|
|
558
|
+
win32_fs_watch (struct ev_win32_fs *ctx, ev_stat *w)
|
|
559
|
+
{
|
|
560
|
+
if (w->wd >= 0 && w->wd < ctx->watchcnt && ctx->watches[w->wd]->watcher == w)
|
|
561
|
+
return ctx->watches[w->wd];
|
|
562
|
+
return 0;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
static void
|
|
566
|
+
win32_watch_free (struct ev_win32_fs *ctx, struct ev_win32_watch *watch)
|
|
567
|
+
{
|
|
568
|
+
int i;
|
|
569
|
+
if (watch->dirty)
|
|
570
|
+
{
|
|
571
|
+
*watch->dirty_prev = watch->dirty_next;
|
|
572
|
+
if (watch->dirty_next)
|
|
573
|
+
watch->dirty_next->dirty_prev = watch->dirty_prev;
|
|
574
|
+
}
|
|
575
|
+
win32_subscription_del (ctx, &watch->parent);
|
|
576
|
+
win32_subscription_del (ctx, &watch->children);
|
|
577
|
+
for (i = 0; i < 3; ++i)
|
|
578
|
+
free (watch->keys[i].key);
|
|
579
|
+
free (watch->path);
|
|
580
|
+
free (watch);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* A directory HANDLE follows renames. Periodically verify that it still names
|
|
584
|
+
* the directory at the watched path, including a changed current directory. */
|
|
585
|
+
static int
|
|
586
|
+
win32_dir_matches (struct ev_win32_dir *dir, const wchar_t *parent)
|
|
587
|
+
{
|
|
588
|
+
BY_HANDLE_FILE_INFORMATION previous, current;
|
|
589
|
+
HANDLE handle;
|
|
590
|
+
int same;
|
|
591
|
+
if (_wcsicmp (dir->path, parent))
|
|
592
|
+
return 0;
|
|
593
|
+
handle = CreateFileW (parent, FILE_READ_ATTRIBUTES,
|
|
594
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING,
|
|
595
|
+
FILE_FLAG_BACKUP_SEMANTICS, 0);
|
|
596
|
+
if (handle == INVALID_HANDLE_VALUE)
|
|
597
|
+
return 0;
|
|
598
|
+
same = GetFileInformationByHandle (dir->handle, &previous)
|
|
599
|
+
&& GetFileInformationByHandle (handle, ¤t)
|
|
600
|
+
&& previous.dwVolumeSerialNumber == current.dwVolumeSerialNumber
|
|
601
|
+
&& previous.nFileIndexHigh == current.nFileIndexHigh
|
|
602
|
+
&& previous.nFileIndexLow == current.nFileIndexLow;
|
|
603
|
+
CloseHandle (handle);
|
|
604
|
+
return same;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
static void
|
|
608
|
+
win32_dirtab_grow (struct ev_win32_fs *ctx)
|
|
609
|
+
{
|
|
610
|
+
unsigned int mask = ctx->dirmask * 2 + 1, i;
|
|
611
|
+
struct ev_win32_dir *dir, **table = (struct ev_win32_dir **)calloc (mask + 1, sizeof (*table));
|
|
612
|
+
if (!table)
|
|
613
|
+
return;
|
|
614
|
+
for (i = 0; i <= ctx->dirmask; ++i)
|
|
615
|
+
while ((dir = ctx->dirtab[i]))
|
|
616
|
+
{
|
|
617
|
+
ctx->dirtab[i] = dir->hash_next;
|
|
618
|
+
dir->hash_next = table[dir->hash & mask];
|
|
619
|
+
table[dir->hash & mask] = dir;
|
|
620
|
+
}
|
|
621
|
+
free (ctx->dirtab);
|
|
622
|
+
ctx->dirtab = table;
|
|
623
|
+
ctx->dirmask = mask;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/* Consumes path on success or failure. All subscriptions to a directory share
|
|
627
|
+
* one non-recursive read; directory watchers also subscribe to their own path. */
|
|
628
|
+
static struct ev_win32_dir *
|
|
629
|
+
win32_dir_acquire (struct ev_win32_fs *ctx, wchar_t *path)
|
|
630
|
+
{
|
|
631
|
+
struct ev_win32_dir *dir;
|
|
632
|
+
unsigned int hash;
|
|
633
|
+
if (!path)
|
|
634
|
+
return 0;
|
|
635
|
+
if (!ctx->dirtab)
|
|
636
|
+
{
|
|
637
|
+
ctx->dirtab = (struct ev_win32_dir **)calloc (16, sizeof (*ctx->dirtab));
|
|
638
|
+
if (!ctx->dirtab)
|
|
639
|
+
goto fail;
|
|
640
|
+
ctx->dirmask = 15;
|
|
641
|
+
}
|
|
642
|
+
hash = win32_hash (path, wcslen (path));
|
|
643
|
+
for (dir = ctx->dirtab[hash & ctx->dirmask]; dir; dir = dir->hash_next)
|
|
644
|
+
if (dir->hash == hash && !dir->retired && !dir->failed && !_wcsicmp (dir->path, path))
|
|
645
|
+
{
|
|
646
|
+
if (win32_dir_matches (dir, path))
|
|
647
|
+
break;
|
|
648
|
+
/* An open HANDLE follows a renamed directory, even after another
|
|
649
|
+
* directory has been created at the same path. */
|
|
650
|
+
dir->failed = 1;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (dir)
|
|
654
|
+
free (path);
|
|
655
|
+
else
|
|
656
|
+
{
|
|
657
|
+
dir = (struct ev_win32_dir *)calloc (1, sizeof (*dir));
|
|
658
|
+
if (dir)
|
|
659
|
+
dir->names = (struct ev_win32_name **)calloc (16, sizeof (*dir->names));
|
|
660
|
+
if (!dir || !dir->names)
|
|
661
|
+
{ free (dir); goto fail; }
|
|
662
|
+
dir->path = path;
|
|
663
|
+
dir->hash = hash;
|
|
664
|
+
dir->namemask = 15;
|
|
665
|
+
dir->handle = CreateFileW (path, FILE_LIST_DIRECTORY,
|
|
666
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING,
|
|
667
|
+
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);
|
|
668
|
+
if (dir->handle == INVALID_HANDLE_VALUE)
|
|
669
|
+
{ free (dir->names); free (dir); goto fail; }
|
|
670
|
+
if (!CreateIoCompletionPort (dir->handle, ctx->port, (ULONG_PTR)dir, 0)
|
|
671
|
+
|| !win32_dir_read (dir))
|
|
672
|
+
{ CloseHandle (dir->handle); free (dir->names); free (dir); goto fail; }
|
|
673
|
+
dir->next = ctx->dirs;
|
|
674
|
+
dir->prev = &ctx->dirs;
|
|
675
|
+
if (ctx->dirs)
|
|
676
|
+
ctx->dirs->prev = &dir->next;
|
|
677
|
+
ctx->dirs = dir;
|
|
678
|
+
dir->hash_next = ctx->dirtab[hash & ctx->dirmask];
|
|
679
|
+
ctx->dirtab[hash & ctx->dirmask] = dir;
|
|
680
|
+
if (++ctx->dircnt > ctx->dirmask + 1)
|
|
681
|
+
win32_dirtab_grow (ctx);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
++dir->refs;
|
|
685
|
+
return dir;
|
|
686
|
+
|
|
687
|
+
fail:
|
|
688
|
+
free (path);
|
|
689
|
+
return 0;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
static void
|
|
693
|
+
win32_fs_add_locked (EV_P_ ev_stat *w, int polling)
|
|
694
|
+
{
|
|
695
|
+
struct ev_win32_fs *ctx;
|
|
696
|
+
struct ev_win32_watch *old, *watch;
|
|
697
|
+
struct ev_win32_dir *dir;
|
|
698
|
+
wchar_t *parent, *children, *path;
|
|
699
|
+
if (!win32_fs_init (EV_A))
|
|
700
|
+
return;
|
|
701
|
+
ctx = win32_fs;
|
|
702
|
+
parent = win32_fs_path (w->path, 1);
|
|
703
|
+
children = (w->attr.st_nlink && (w->attr.st_mode & S_IFMT) == S_IFDIR)
|
|
704
|
+
? win32_fs_path (w->path, 0) : 0;
|
|
705
|
+
if (!parent)
|
|
706
|
+
{ free (children); return; }
|
|
707
|
+
old = win32_fs_watch (ctx, w);
|
|
708
|
+
if (old)
|
|
709
|
+
{
|
|
710
|
+
int same_parent;
|
|
711
|
+
int same_children = !children && !old->children.dir;
|
|
712
|
+
/* Timer callbacks in one loop turn share the same timestamp. Validate
|
|
713
|
+
* shared handles once per turn; new registrations still check afresh. */
|
|
714
|
+
if (polling)
|
|
715
|
+
{
|
|
716
|
+
struct ev_win32_dir *dirs[2];
|
|
717
|
+
int i;
|
|
718
|
+
dirs[0] = old->parent.dir;
|
|
719
|
+
dirs[1] = old->children.dir;
|
|
720
|
+
for (i = 0; i < 2; ++i)
|
|
721
|
+
if (dirs[i] && !dirs[i]->failed && dirs[i]->checked_at != ev_rt_now)
|
|
722
|
+
{
|
|
723
|
+
dirs[i]->checked_at = ev_rt_now;
|
|
724
|
+
if (!win32_dir_matches (dirs[i], dirs[i]->path))
|
|
725
|
+
dirs[i]->failed = 1;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
same_parent = !old->parent.dir->failed
|
|
729
|
+
&& (polling ? !_wcsicmp (old->parent.dir->path, parent)
|
|
730
|
+
: win32_dir_matches (old->parent.dir, parent));
|
|
731
|
+
if (children && old->children.dir)
|
|
732
|
+
same_children = !old->children.dir->failed
|
|
733
|
+
&& (polling ? !_wcsicmp (old->children.dir->path, children)
|
|
734
|
+
: win32_dir_matches (old->children.dir, children));
|
|
735
|
+
if (same_parent && same_children)
|
|
736
|
+
{
|
|
737
|
+
/* Appends leave aliases unchanged. Retry failures during polling,
|
|
738
|
+
* and refresh after name events or an explicit re-registration. */
|
|
739
|
+
if (!polling || old->names_dirty || old->names_failed)
|
|
740
|
+
win32_watch_names (old);
|
|
741
|
+
free (parent);
|
|
742
|
+
free (children);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (!same_parent && !_wcsicmp (old->parent.dir->path, parent))
|
|
746
|
+
old->parent.dir->failed = 1;
|
|
747
|
+
if (!same_children && children && old->children.dir
|
|
748
|
+
&& !_wcsicmp (old->children.dir->path, children))
|
|
749
|
+
old->children.dir->failed = 1;
|
|
750
|
+
}
|
|
751
|
+
else if (ctx->watchcnt == ctx->watchmax)
|
|
752
|
+
{
|
|
753
|
+
int max = ctx->watchmax ? ctx->watchmax * 2 : 16;
|
|
754
|
+
struct ev_win32_watch **watches
|
|
755
|
+
= (struct ev_win32_watch **)realloc (ctx->watches, max * sizeof (*watches));
|
|
756
|
+
if (!watches)
|
|
757
|
+
{ free (parent); free (children); return; }
|
|
758
|
+
ctx->watches = watches;
|
|
759
|
+
ctx->watchmax = max;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
path = win32_fs_path (w->path, 0);
|
|
763
|
+
watch = path ? (struct ev_win32_watch *)calloc (1, sizeof (*watch)) : 0;
|
|
764
|
+
if (watch && !win32_name_key (watch, 0, wcsrchr (path, L'\\') + 1))
|
|
765
|
+
{ free (watch); watch = 0; }
|
|
766
|
+
if (!watch)
|
|
767
|
+
{ free (parent); free (children); free (path); return; }
|
|
768
|
+
dir = win32_dir_acquire (ctx, parent);
|
|
769
|
+
if (!dir)
|
|
770
|
+
{ free (watch->keys[0].key); free (watch); free (children); free (path); return; }
|
|
771
|
+
watch->path = path;
|
|
772
|
+
watch->name = wcsrchr (path, L'\\') + 1;
|
|
773
|
+
win32_watch_names (watch);
|
|
774
|
+
win32_subscription_add (&watch->parent, watch, dir);
|
|
775
|
+
win32_subscription_add (&watch->children, watch, win32_dir_acquire (ctx, children));
|
|
776
|
+
watch->watcher = w;
|
|
777
|
+
/* Acquire the replacement first, retaining unchanged shared directory reads. */
|
|
778
|
+
if (old)
|
|
779
|
+
{
|
|
780
|
+
ctx->watches[w->wd] = watch;
|
|
781
|
+
win32_watch_free (ctx, old);
|
|
782
|
+
}
|
|
783
|
+
else
|
|
784
|
+
{
|
|
785
|
+
w->wd = ctx->watchcnt;
|
|
786
|
+
ctx->watches[ctx->watchcnt++] = watch;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
static void
|
|
791
|
+
win32_fs_add (EV_P_ ev_stat *w, int polling)
|
|
792
|
+
{
|
|
793
|
+
struct ev_win32_fs *ctx;
|
|
794
|
+
if (!win32_fs_init (EV_A))
|
|
795
|
+
return;
|
|
796
|
+
ctx = win32_fs;
|
|
797
|
+
EnterCriticalSection (&ctx->lock);
|
|
798
|
+
win32_fs_add_locked (EV_A_ w, polling);
|
|
799
|
+
LeaveCriticalSection (&ctx->lock);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
static void
|
|
803
|
+
win32_fs_del (EV_P_ ev_stat *w)
|
|
804
|
+
{
|
|
805
|
+
struct ev_win32_fs *ctx = win32_fs;
|
|
806
|
+
struct ev_win32_watch *watch;
|
|
807
|
+
if (!ctx)
|
|
808
|
+
return;
|
|
809
|
+
EnterCriticalSection (&ctx->lock);
|
|
810
|
+
watch = win32_fs_watch (ctx, w);
|
|
811
|
+
if (watch)
|
|
812
|
+
{
|
|
813
|
+
struct ev_win32_watch *last = ctx->watches[--ctx->watchcnt];
|
|
814
|
+
ctx->watches[w->wd] = last;
|
|
815
|
+
last->watcher->wd = w->wd;
|
|
816
|
+
w->wd = -1;
|
|
817
|
+
win32_watch_free (ctx, watch);
|
|
818
|
+
}
|
|
819
|
+
LeaveCriticalSection (&ctx->lock);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
static void
|
|
823
|
+
win32_fs_destroy (EV_P)
|
|
824
|
+
{
|
|
825
|
+
struct ev_win32_fs *ctx = win32_fs;
|
|
826
|
+
struct ev_win32_dir *dir, *next;
|
|
827
|
+
if (!ctx)
|
|
828
|
+
return;
|
|
829
|
+
EnterCriticalSection (&ctx->lock);
|
|
830
|
+
ctx->stopping = 1;
|
|
831
|
+
while (ctx->watchcnt)
|
|
832
|
+
win32_watch_free (ctx, ctx->watches[--ctx->watchcnt]);
|
|
833
|
+
free (ctx->watches);
|
|
834
|
+
ctx->watches = 0;
|
|
835
|
+
for (dir = ctx->dirs; dir; dir = next)
|
|
836
|
+
{
|
|
837
|
+
next = dir->next;
|
|
838
|
+
if (!dir->retired)
|
|
839
|
+
win32_dir_retire (ctx, dir);
|
|
840
|
+
}
|
|
841
|
+
ev_ref (EV_A);
|
|
842
|
+
ev_async_stop (EV_A_ &ctx->async);
|
|
843
|
+
win32_fs = 0;
|
|
844
|
+
/* The worker owns ctx after this handoff. Pending cancellation completions
|
|
845
|
+
* still reference directory buffers, so retain them until they arrive, but
|
|
846
|
+
* do not hold up loop destruction (which can run during Ruby GC). All
|
|
847
|
+
* subscriptions are gone and the worker can no longer signal this loop.
|
|
848
|
+
* A driver that never completes cancellation retains these resources. */
|
|
849
|
+
PostQueuedCompletionStatus (ctx->port, 0, 0, 0);
|
|
850
|
+
LeaveCriticalSection (&ctx->lock);
|
|
851
|
+
}
|