cool.io 1.10.1 → 1.11.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.
@@ -0,0 +1,414 @@
1
+ /* kqueue (EVFILT_VNODE) notification support for ev_stat. Included by ev.c.
2
+ * Copyright (C) 2026. Distributed under the same terms as ev.c.
3
+ *
4
+ * Each loop has a private kqueue; each registration has an O_EVTONLY fd.
5
+ * Events carry the ev_stat pointer as udata and trigger a stat comparison.
6
+ * The ordinary polling timer remains enabled for path changes that vnode
7
+ * notifications miss. Registration state belongs to the event loop.
8
+ */
9
+
10
+ #include <sys/stat.h>
11
+ #include <sys/event.h>
12
+
13
+ struct ev_kq_stat_slot
14
+ {
15
+ ev_stat *watcher;
16
+ int fd; /* descriptor registered in ctx->fd; -1 while polling only */
17
+ int is_dir; /* fd names a directory used as a creation/replacement hint */
18
+ };
19
+
20
+ struct ev_kq_stat
21
+ {
22
+ int fd; /* the private kqueue collecting vnode events */
23
+ pid_t owner_pid;
24
+ ev_io io; /* loop watcher making ctx->fd's events observable */
25
+ struct ev_kq_stat_slot **slots;
26
+ int slotcnt, slotmax;
27
+ };
28
+
29
+ /* Process one bounded batch per callback so other watchers can run. */
30
+ #define EV_KQ_EVENT_BUFSIZE 32
31
+
32
+ static void kq_stat_add (EV_P_ ev_stat *w);
33
+
34
+ static unsigned int
35
+ kq_stat_file_flags (void)
36
+ {
37
+ return NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | NOTE_DELETE;
38
+ }
39
+
40
+ static unsigned int
41
+ kq_stat_dir_flags (void)
42
+ {
43
+ /* NOTE_WRITE is the hint for directory entry changes; DELETE/RENAME
44
+ * indicate that the hint directory itself may have moved or disappeared. */
45
+ return NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME | NOTE_DELETE;
46
+ }
47
+
48
+ /* ctx->io watches the private kqueue for readability when events are pending. */
49
+ static int
50
+ kq_stat_register (struct ev_kq_stat *ctx, struct ev_kq_stat_slot *slot, int fd, int is_dir, unsigned int nflags)
51
+ {
52
+ struct kevent kev;
53
+
54
+ EV_SET (&kev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, nflags, 0, slot->watcher);
55
+ if (kevent (ctx->fd, &kev, 1, 0, 0, 0) < 0)
56
+ return 0;
57
+ slot->fd = fd;
58
+ slot->is_dir = is_dir;
59
+ return 1;
60
+ }
61
+
62
+ /* Closing the descriptor removes its kernel registration. Events already
63
+ * copied into a callback batch are unaffected. */
64
+ static void
65
+ kq_stat_close_slot (struct ev_kq_stat *ctx, struct ev_kq_stat_slot *slot)
66
+ {
67
+ (void)ctx;
68
+ if (slot->fd >= 0)
69
+ close (slot->fd);
70
+ slot->fd = -1;
71
+ slot->is_dir = 0;
72
+ }
73
+
74
+ /* Reuse a watcher's slot across registration retries; w->wd is its index. */
75
+ static struct ev_kq_stat_slot *
76
+ kq_stat_slot_get (EV_P_ ev_stat *w)
77
+ {
78
+ struct ev_kq_stat *ctx = kq_stat;
79
+ struct ev_kq_stat_slot *slot;
80
+
81
+ if (w->wd >= 0)
82
+ return ctx->slots [w->wd];
83
+
84
+ if (ctx->slotcnt == ctx->slotmax)
85
+ {
86
+ int max = ctx->slotmax ? ctx->slotmax * 2 : 16;
87
+ struct ev_kq_stat_slot **slots
88
+ = (struct ev_kq_stat_slot **)realloc (ctx->slots, max * sizeof (*slots));
89
+ if (!slots)
90
+ return 0;
91
+ ctx->slots = slots;
92
+ ctx->slotmax = max;
93
+ }
94
+ slot = (struct ev_kq_stat_slot *)calloc (1, sizeof (*slot));
95
+ if (!slot)
96
+ return 0;
97
+ slot->watcher = w;
98
+ slot->fd = -1;
99
+ w->wd = ctx->slotcnt;
100
+ ctx->slots [ctx->slotcnt++] = slot;
101
+ return slot;
102
+ }
103
+
104
+ /* The index may be unset after an allocation failure or watcher stop. */
105
+ static struct ev_kq_stat_slot *
106
+ kq_stat_slot_valid (struct ev_kq_stat *ctx, ev_stat *w)
107
+ {
108
+ return w->wd >= 0 && w->wd < ctx->slotcnt && ctx->slots [w->wd]->watcher == w
109
+ ? ctx->slots [w->wd] : 0;
110
+ }
111
+
112
+ /* Register notifications without changing the polling timer. O_NOFOLLOW
113
+ * avoids watching a symlink's target; parent directories can provide
114
+ * creation hints when the path cannot be opened. Reopen the path on retries
115
+ * to detect replacement, retaining the current registration if it still
116
+ * refers to the same vnode. */
117
+ static void
118
+ kq_stat_add (EV_P_ ev_stat *w)
119
+ {
120
+ struct ev_kq_stat *ctx = kq_stat;
121
+ struct ev_kq_stat_slot *slot;
122
+ int fd = -1, is_dir = 0;
123
+ unsigned int flags = 0;
124
+ struct stat st;
125
+
126
+ slot = kq_stat_slot_get (EV_A_ w);
127
+ if (!slot)
128
+ return;
129
+
130
+ /* Even a nonblocking FIFO open can wake a waiting writer. Keep known
131
+ * special files on polling; retain the checks below for replacement races. */
132
+ if (lstat (w->path, &st) == 0)
133
+ {
134
+ if (S_ISREG (st.st_mode) || S_ISDIR (st.st_mode))
135
+ fd = open (w->path, O_RDONLY | O_EVTONLY | O_NONBLOCK | O_NOFOLLOW | O_CLOEXEC);
136
+ else if (S_ISLNK (st.st_mode))
137
+ errno = ELOOP;
138
+ else
139
+ {
140
+ kq_stat_close_slot (ctx, slot);
141
+ return;
142
+ }
143
+ }
144
+ if (fd >= 0)
145
+ {
146
+ if (fstat (fd, &st) == 0 && (S_ISREG (st.st_mode) || S_ISDIR (st.st_mode)))
147
+ flags = kq_stat_file_flags ();
148
+ else
149
+ close (fd), fd = -1;
150
+ }
151
+ else if ((errno == ENOENT || errno == ENOTDIR || errno == ELOOP || errno == EACCES)
152
+ && strlen (w->path) < 4096)
153
+ {
154
+ /* Parent hints only: they select paths to re-stat, so following a
155
+ * symlinked directory here cannot change what the stat reports. */
156
+ char path [4096];
157
+ strcpy (path, w->path);
158
+ do
159
+ {
160
+ char *pend = strrchr (path, '/');
161
+ if (!pend || pend == path)
162
+ break;
163
+ *pend = 0;
164
+ if (stat (path, &st) == 0)
165
+ {
166
+ if (S_ISDIR (st.st_mode))
167
+ fd = open (path, O_RDONLY | O_EVTONLY | O_NONBLOCK | O_CLOEXEC);
168
+ else
169
+ errno = ENOTDIR;
170
+ }
171
+ }
172
+ while (fd < 0 && (errno == ENOENT || errno == ENOTDIR || errno == ELOOP || errno == EACCES));
173
+
174
+ if (fd >= 0)
175
+ {
176
+ if (fstat (fd, &st) == 0 && S_ISDIR (st.st_mode))
177
+ is_dir = 1, flags = kq_stat_dir_flags ();
178
+ else
179
+ close (fd), fd = -1;
180
+ }
181
+ }
182
+
183
+ if (fd < 0)
184
+ {
185
+ /* Keep polling when no descriptor can be opened. */
186
+ kq_stat_close_slot (ctx, slot);
187
+ return;
188
+ }
189
+
190
+ if (slot->fd >= 0 && slot->is_dir == is_dir)
191
+ {
192
+ struct stat o, n;
193
+ if (fstat (slot->fd, &o) == 0 && fstat (fd, &n) == 0
194
+ && o.st_dev == n.st_dev && o.st_ino == n.st_ino)
195
+ {
196
+ close (fd); /* same target as before: keep the existing kevent */
197
+ return;
198
+ }
199
+ }
200
+
201
+ kq_stat_close_slot (ctx, slot);
202
+ if (!kq_stat_register (ctx, slot, fd, is_dir, flags))
203
+ close (fd);
204
+ }
205
+
206
+ static void
207
+ kq_stat_cb (EV_P_ ev_io *w_, int revents)
208
+ {
209
+ struct ev_kq_stat *ctx = kq_stat;
210
+ struct kevent evs [EV_KQ_EVENT_BUFSIZE];
211
+ struct timespec ts;
212
+ int n, i;
213
+
214
+ (void)w_;
215
+ if (revents & EV_ERROR)
216
+ {
217
+ /* fd_kill stops the unreferenced I/O watcher before feeding EV_ERROR. */
218
+ ev_ref (EV_A);
219
+ ev_io_stop (EV_A_ &ctx->io);
220
+ if (ctx->fd >= 0 && ctx->owner_pid == getpid () && fd_valid (ctx->fd))
221
+ close (ctx->fd);
222
+ ctx->fd = -1;
223
+ for (i = 0; i < ctx->slotcnt; ++i)
224
+ kq_stat_close_slot (ctx, ctx->slots [i]);
225
+ return;
226
+ }
227
+
228
+ ts.tv_sec = 0;
229
+ ts.tv_nsec = 0;
230
+ n = kevent (ctx->fd, 0, 0, evs, EV_KQ_EVENT_BUFSIZE, &ts);
231
+ for (i = 0; i < n; ++i)
232
+ {
233
+ ev_stat *w = (ev_stat *)evs [i].udata;
234
+ struct ev_kq_stat_slot *slot = kq_stat_slot_valid (ctx, w);
235
+
236
+ if (!slot)
237
+ continue;
238
+
239
+ /* Follow the path when the monitored vnode is removed or renamed. */
240
+ if (evs [i].fflags & (NOTE_DELETE | NOTE_RENAME))
241
+ kq_stat_add (EV_A_ w);
242
+ /* A parent hint may mean that a deeper path component now exists. */
243
+ else if (slot->is_dir)
244
+ kq_stat_add (EV_A_ w);
245
+
246
+ stat_timer_cb (EV_A_ &w->timer, 0);
247
+ }
248
+ }
249
+
250
+ /* Retry kqueue creation before walking slots, so persistent failure does
251
+ * not rescan every watcher on each polling tick. */
252
+ static int
253
+ kq_stat_recreate (EV_P)
254
+ {
255
+ struct ev_kq_stat *ctx = kq_stat;
256
+ int i;
257
+
258
+ ctx->fd = kqueue ();
259
+ if (ctx->fd < 0)
260
+ return 0;
261
+
262
+ ctx->owner_pid = getpid ();
263
+ fd_intern (ctx->fd);
264
+ ev_io_set (&ctx->io, ctx->fd, EV_READ);
265
+ ev_io_start (EV_A_ &ctx->io);
266
+ ev_unref (EV_A);
267
+
268
+ for (i = 0; i < ctx->slotcnt; ++i)
269
+ kq_stat_add (EV_A_ ctx->slots [i]->watcher);
270
+ return 1;
271
+ }
272
+
273
+ static int
274
+ kq_stat_init (EV_P)
275
+ {
276
+ struct ev_kq_stat *ctx;
277
+
278
+ if (kq_stat_disabled)
279
+ return 0;
280
+ if (kq_stat)
281
+ return kq_stat->fd >= 0 || kq_stat_recreate (EV_A);
282
+
283
+ ctx = (struct ev_kq_stat *)calloc (1, sizeof (*ctx));
284
+ if (!ctx)
285
+ return 0;
286
+ ctx->fd = kqueue ();
287
+ if (ctx->fd < 0)
288
+ {
289
+ free (ctx);
290
+ return 0;
291
+ }
292
+ ctx->owner_pid = getpid ();
293
+ fd_intern (ctx->fd);
294
+ ev_io_init (&ctx->io, kq_stat_cb, ctx->fd, EV_READ);
295
+ ev_set_priority (&ctx->io, EV_MAXPRI);
296
+ ctx->io.data = ctx;
297
+ ev_io_start (EV_A_ &ctx->io);
298
+ ev_unref (EV_A); /* internal watcher must not keep the loop running */
299
+ kq_stat = ctx;
300
+ return 1;
301
+ }
302
+
303
+ /* The ordinary stat timer remains the backstop when notification setup fails. */
304
+ static void
305
+ kq_stat_start (EV_P_ ev_stat *w)
306
+ {
307
+ if (kq_stat_init (EV_A))
308
+ {
309
+ kq_stat_add (EV_A_ w);
310
+ stat_timer_cb (EV_A_ &w->timer, 0); /* close the stat/registration race */
311
+ }
312
+ }
313
+
314
+ static void
315
+ kq_stat_poll (EV_P_ ev_stat *w, int revents)
316
+ {
317
+ if ((revents & EV_TIMER) && ev_is_active (w) && kq_stat_init (EV_A))
318
+ {
319
+ struct ev_kq_stat_slot *slot = kq_stat_slot_valid (kq_stat, w);
320
+ /* An active watcher can lack a slot after an allocation or initial
321
+ * kqueue failure. Retry it along with failed and parent registrations. */
322
+ if (!slot || slot->fd < 0 || slot->is_dir)
323
+ kq_stat_add (EV_A_ w);
324
+ }
325
+ }
326
+
327
+ static void
328
+ kq_stat_refresh (EV_P_ ev_stat *w)
329
+ {
330
+ if (ev_is_active (w) && kq_stat && kq_stat->fd >= 0
331
+ && (!w->prev.st_nlink || !w->attr.st_nlink
332
+ || (w->prev.st_mode & S_IFMT) != (w->attr.st_mode & S_IFMT)
333
+ || w->prev.st_dev != w->attr.st_dev || w->prev.st_ino != w->attr.st_ino))
334
+ {
335
+ kq_stat_add (EV_A_ w);
336
+ ev_stat_stat (EV_A_ w); /* close the stat/registration race */
337
+ }
338
+ }
339
+
340
+ static void
341
+ kq_stat_del (EV_P_ ev_stat *w)
342
+ {
343
+ struct ev_kq_stat *ctx = kq_stat;
344
+ struct ev_kq_stat_slot *slot, *last;
345
+
346
+ if (!ctx || w->wd < 0)
347
+ return;
348
+ slot = ctx->slots [w->wd];
349
+ last = ctx->slots [--ctx->slotcnt];
350
+ kq_stat_close_slot (ctx, slot);
351
+ free (slot);
352
+ ctx->slots [w->wd] = last;
353
+ if (last != slot)
354
+ last->watcher->wd = w->wd;
355
+ w->wd = -1;
356
+ }
357
+
358
+ /* Recreate the private kqueue after fork and re-register existing watchers. */
359
+ static void
360
+ kq_stat_fork (EV_P)
361
+ {
362
+ struct ev_kq_stat *ctx = kq_stat;
363
+ int i;
364
+
365
+ if (!ctx)
366
+ return;
367
+
368
+ if (ev_is_active (&ctx->io))
369
+ {
370
+ ev_ref (EV_A);
371
+ ev_io_stop (EV_A_ &ctx->io);
372
+ }
373
+ /* The child may have reused the parent's non-inherited kqueue fd number. */
374
+ if (ctx->fd >= 0 && ctx->owner_pid == getpid ())
375
+ close (ctx->fd);
376
+ ctx->fd = -1;
377
+
378
+ for (i = 0; i < ctx->slotcnt; ++i)
379
+ {
380
+ struct ev_kq_stat_slot *slot = ctx->slots [i];
381
+ /* drop the inherited descriptors first: the new kqueue holds no
382
+ * registrations, and kq_stat_add would otherwise keep them */
383
+ kq_stat_close_slot (ctx, slot);
384
+ }
385
+ kq_stat_recreate (EV_A);
386
+ }
387
+
388
+ static void
389
+ kq_stat_destroy (EV_P)
390
+ {
391
+ struct ev_kq_stat *ctx = kq_stat;
392
+
393
+ if (!ctx)
394
+ return;
395
+
396
+ /* Ruby GC may have freed the watchers already; release only loop-owned data. */
397
+ while (ctx->slotcnt)
398
+ {
399
+ struct ev_kq_stat_slot *slot = ctx->slots [ctx->slotcnt - 1];
400
+ kq_stat_close_slot (ctx, slot);
401
+ free (slot);
402
+ --ctx->slotcnt;
403
+ }
404
+ free (ctx->slots);
405
+ if (ev_is_active (&ctx->io))
406
+ {
407
+ ev_ref (EV_A);
408
+ ev_io_stop (EV_A_ &ctx->io);
409
+ }
410
+ if (ctx->fd >= 0 && ctx->owner_pid == getpid ())
411
+ close (ctx->fd);
412
+ kq_stat = 0;
413
+ free (ctx);
414
+ }
data/ext/libev/ev_vars.h CHANGED
@@ -185,6 +185,10 @@ VARx(EV_ATOMIC_T, sig_pending)
185
185
  VARx(struct ev_win32_fs *, win32_fs)
186
186
  VARx(int, win32_fs_disabled)
187
187
  #endif
188
+ #if EV_USE_KQUEUE_STAT || EV_GENWRAP
189
+ VARx(struct ev_kq_stat *, kq_stat)
190
+ VARx(int, kq_stat_disabled)
191
+ #endif
188
192
  #if EV_USE_SIGNALFD || EV_GENWRAP
189
193
  VARx(int, sigfd)
190
194
  VARx(ev_io, sigfd_w)
data/ext/libev/ev_wrap.h CHANGED
@@ -50,6 +50,8 @@
50
50
  #define kqueue_eventmax ((loop)->kqueue_eventmax)
51
51
  #define kqueue_events ((loop)->kqueue_events)
52
52
  #define kqueue_fd_pid ((loop)->kqueue_fd_pid)
53
+ #define kq_stat ((loop)->kq_stat)
54
+ #define kq_stat_disabled ((loop)->kq_stat_disabled)
53
55
  #define loop_count ((loop)->loop_count)
54
56
  #define loop_depth ((loop)->loop_depth)
55
57
  #define loop_done ((loop)->loop_done)
@@ -151,6 +153,8 @@
151
153
  #undef kqueue_eventmax
152
154
  #undef kqueue_events
153
155
  #undef kqueue_fd_pid
156
+ #undef kq_stat
157
+ #undef kq_stat_disabled
154
158
  #undef loop_count
155
159
  #undef loop_depth
156
160
  #undef loop_done
@@ -1,5 +1,5 @@
1
1
  module Coolio
2
- VERSION = "1.10.1"
2
+ VERSION = "1.11.0"
3
3
 
4
4
  def self.version
5
5
  VERSION