Skip to content

Commit da42507

Browse files
committed
poll: fix and extend kqueue fd tracking
1 parent 45efc78 commit da42507

File tree

1 file changed

+110
-67
lines changed

1 file changed

+110
-67
lines changed

main/poll/poll_backend_kqueue.c

Lines changed: 110 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,20 @@
2020
#include <sys/event.h>
2121
#include <sys/time.h>
2222

23+
/* Flags for tracking FD state in single hash table */
24+
#define KQUEUE_FD_PRESENT (1 << 0) /* FD is registered */
25+
#define KQUEUE_FD_ONESHOT_COMPLETE (1 << 1) /* Has both read+write oneshot */
26+
#define KQUEUE_FD_GARBAGE_READ (1 << 2) /* Read filter fired, needs write cleanup */
27+
#define KQUEUE_FD_GARBAGE_WRITE (1 << 3) /* Write filter fired, needs read cleanup */
28+
#define KQUEUE_FD_HAS_GARBAGE (KQUEUE_FD_GARBAGE_READ | KQUEUE_FD_GARBAGE_WRITE)
29+
2330
typedef struct {
2431
int kqueue_fd;
2532
struct kevent *events;
2633
int events_capacity;
2734
int fd_count; /* Track number of unique FDs (not individual filters) */
2835
int filter_count; /* Track total number of filters for raw events */
29-
HashTable *complete_oneshot_fds; /* Track FDs with both read+write oneshot */
30-
HashTable *garbage_oneshot_fds; /* Pre-cached hash table for FDs to delete */
36+
HashTable *fd_tracking; /* Single hash table for all FD state tracking */
3137
} kqueue_backend_data_t;
3238

3339
static zend_result kqueue_backend_init(php_poll_ctx *ctx)
@@ -56,18 +62,15 @@ static zend_result kqueue_backend_init(php_poll_ctx *ctx)
5662
return FAILURE;
5763
}
5864
data->events_capacity = initial_capacity;
59-
data->fd_count = 0; /* Initialize FD counter */
60-
data->filter_count = 0; /* Initialize filter counter */
65+
data->fd_count = 0;
66+
data->filter_count = 0;
6167

62-
/* Only initialize oneshot related hash tables if not using raw events */
68+
/* Initialize single tracking hash table (only if not using raw events) */
6369
if (!ctx->raw_events) {
64-
data->complete_oneshot_fds = php_poll_malloc(sizeof(HashTable), ctx->persistent);
65-
zend_hash_init(data->complete_oneshot_fds, 8, NULL, NULL, ctx->persistent);
66-
data->garbage_oneshot_fds = php_poll_malloc(sizeof(HashTable), ctx->persistent);
67-
zend_hash_init(data->garbage_oneshot_fds, 8, NULL, NULL, ctx->persistent);
70+
data->fd_tracking = php_poll_malloc(sizeof(HashTable), ctx->persistent);
71+
zend_hash_init(data->fd_tracking, 8, NULL, NULL, ctx->persistent);
6872
} else {
69-
data->complete_oneshot_fds = NULL;
70-
data->garbage_oneshot_fds = NULL;
73+
data->fd_tracking = NULL;
7174
}
7275

7376
ctx->backend_data = data;
@@ -83,14 +86,10 @@ static void kqueue_backend_cleanup(php_poll_ctx *ctx)
8386
}
8487
pefree(data->events, ctx->persistent);
8588

86-
/* Only cleanup hash tables if they were initialized */
87-
if (data->complete_oneshot_fds) {
88-
zend_hash_destroy(data->complete_oneshot_fds);
89-
pefree(data->complete_oneshot_fds, ctx->persistent);
90-
}
91-
if (data->garbage_oneshot_fds) {
92-
zend_hash_destroy(data->garbage_oneshot_fds);
93-
pefree(data->garbage_oneshot_fds, ctx->persistent);
89+
/* Cleanup tracking hash table if initialized */
90+
if (data->fd_tracking) {
91+
zend_hash_destroy(data->fd_tracking);
92+
pefree(data->fd_tracking, ctx->persistent);
9493
}
9594

9695
pefree(data, ctx->persistent);
@@ -102,6 +101,15 @@ static zend_result kqueue_backend_add(php_poll_ctx *ctx, int fd, uint32_t events
102101
{
103102
kqueue_backend_data_t *backend_data = (kqueue_backend_data_t *) ctx->backend_data;
104103

104+
/* Check for duplicate in non-raw mode */
105+
if (!ctx->raw_events) {
106+
zval *existing = zend_hash_index_find(backend_data->fd_tracking, fd);
107+
if (existing && (Z_LVAL_P(existing) & KQUEUE_FD_PRESENT)) {
108+
php_poll_set_error(ctx, PHP_POLL_ERR_EXISTS);
109+
return FAILURE;
110+
}
111+
}
112+
105113
struct kevent changes[2]; /* Max 2 changes: read + write */
106114
int change_count = 0;
107115

@@ -130,16 +138,23 @@ static zend_result kqueue_backend_add(php_poll_ctx *ctx, int fd, uint32_t events
130138
return FAILURE;
131139
}
132140

133-
/* Increment FD count only once per unique FD */
141+
/* Increment counters */
134142
backend_data->fd_count++;
135-
/* Increment filter count by number of filters added */
136143
backend_data->filter_count += change_count;
137144

138-
/* Track oneshot only if not using raw events */
139-
if (!ctx->raw_events
140-
&& (events & (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT))
141-
== (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) {
142-
zend_hash_index_add_empty_element(backend_data->complete_oneshot_fds, fd);
145+
/* Track FD state in non-raw mode */
146+
if (!ctx->raw_events) {
147+
zend_long tracking_flags = KQUEUE_FD_PRESENT;
148+
149+
/* Mark as complete oneshot if both read+write with oneshot */
150+
if ((events & (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT))
151+
== (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) {
152+
tracking_flags |= KQUEUE_FD_ONESHOT_COMPLETE;
153+
}
154+
155+
zval tracking_zval;
156+
ZVAL_LONG(&tracking_zval, tracking_flags);
157+
zend_hash_index_update(backend_data->fd_tracking, fd, &tracking_zval);
143158
}
144159
}
145160

@@ -205,29 +220,33 @@ static zend_result kqueue_backend_modify(php_poll_ctx *ctx, int fd, uint32_t eve
205220
}
206221
}
207222

208-
/* Update counters and oneshot tracking */
223+
/* Update counters and tracking */
209224
if (successful_deletes > 0 && add_count == 0) {
210225
/* Removed all filters - FD is gone */
211226
backend_data->fd_count--;
212227
backend_data->filter_count -= successful_deletes;
213228
if (!ctx->raw_events) {
214-
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
229+
zend_hash_index_del(backend_data->fd_tracking, fd);
215230
}
216-
} else if (successful_deletes == 0 && add_count > 0) {
217-
/* Added filters to previously empty FD */
218-
backend_data->fd_count++;
219-
backend_data->filter_count += add_count;
220-
if (!ctx->raw_events
221-
&& (events & (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT))
222-
== (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) {
223-
zend_hash_index_add_empty_element(backend_data->complete_oneshot_fds, fd);
231+
} else if (add_count > 0) {
232+
if (successful_deletes == 0) {
233+
/* Added filters to previously empty FD */
234+
backend_data->fd_count++;
235+
backend_data->filter_count += add_count;
236+
} else {
237+
/* Mixed operation when successful_deletes > 0 - update filter count */
238+
backend_data->filter_count = backend_data->filter_count - successful_deletes + add_count;
224239
}
225-
} else if (successful_deletes > 0 || add_count > 0) {
226-
/* Mixed operation - update filter count */
227-
backend_data->filter_count = backend_data->filter_count - successful_deletes + add_count;
240+
228241
if (!ctx->raw_events) {
229-
/* One of the filters was deleted so remove from oneshot tracking */
230-
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
242+
zend_long tracking_flags = KQUEUE_FD_PRESENT;
243+
if ((events & (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT))
244+
== (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) {
245+
tracking_flags |= KQUEUE_FD_ONESHOT_COMPLETE;
246+
}
247+
zval tracking_zval;
248+
ZVAL_LONG(&tracking_zval, tracking_flags);
249+
zend_hash_index_update(backend_data->fd_tracking, fd, &tracking_zval);
231250
}
232251
}
233252

@@ -266,13 +285,13 @@ static zend_result kqueue_backend_remove(php_poll_ctx *ctx, int fd)
266285
return FAILURE;
267286
}
268287

269-
/* Update counters - we removed all filters for this FD */
288+
/* Update counters */
270289
backend_data->fd_count--;
271290
backend_data->filter_count -= successful_deletes;
272291

273-
/* Remove from complete oneshot tracking if not using raw events */
292+
/* Remove from tracking */
274293
if (!ctx->raw_events) {
275-
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
294+
zend_hash_index_del(backend_data->fd_tracking, fd);
276295
}
277296

278297
return SUCCESS;
@@ -312,7 +331,7 @@ static int kqueue_backend_wait(
312331
/* Raw events mode - direct 1:1 mapping, no grouping */
313332
for (int i = 0; i < nfds && i < max_events; i++) {
314333
events[i].fd = (int) backend_data->events[i].ident;
315-
events[i].events = 0; /* Not used in raw mode */
334+
events[i].events = 0;
316335
events[i].revents = 0;
317336
events[i].data = backend_data->events[i].udata;
318337

@@ -331,12 +350,10 @@ static int kqueue_backend_wait(
331350
events[i].revents |= PHP_POLL_ERROR;
332351
}
333352
}
334-
/* In raw mode, we might return fewer events than nfds if max_events < nfds */
335353
return nfds > max_events ? max_events : nfds;
336354
} else {
337-
/* Grouped events mode - existing complex logic */
338-
int unique_events = 0, fd;
339-
zend_hash_clean(backend_data->garbage_oneshot_fds);
355+
/* Grouped events mode with improved oneshot tracking */
356+
int unique_events = 0, garbage_events = 0, fd;
340357

341358
for (int i = 0; i < nfds; i++) {
342359
fd = (int) backend_data->events[i].ident;
@@ -378,29 +395,54 @@ static int kqueue_backend_wait(
378395
events[unique_events].data = data;
379396
unique_events++;
380397

381-
if (is_oneshot
382-
&& zend_hash_index_exists(backend_data->complete_oneshot_fds, fd)) {
383-
zval dummy;
384-
ZVAL_BOOL(&dummy, revents & PHP_POLL_READ);
385-
zend_hash_index_add(backend_data->garbage_oneshot_fds, fd, &dummy);
386-
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
387-
backend_data->fd_count--;
398+
/* Handle oneshot tracking */
399+
if (is_oneshot) {
400+
zval *tracking = zend_hash_index_find(backend_data->fd_tracking, fd);
401+
if (tracking && (Z_LVAL_P(tracking) & KQUEUE_FD_ONESHOT_COMPLETE)) {
402+
/* Mark which filter fired for garbage collection */
403+
zend_long flags = Z_LVAL_P(tracking);
404+
flags &= ~KQUEUE_FD_ONESHOT_COMPLETE; /* Clear complete flag */
405+
if (revents & PHP_POLL_READ) {
406+
flags |= KQUEUE_FD_GARBAGE_READ; /* Need to clean write */
407+
}
408+
if (revents & PHP_POLL_WRITE) {
409+
flags |= KQUEUE_FD_GARBAGE_WRITE; /* Need to clean read */
410+
}
411+
ZVAL_LONG(tracking, flags);
412+
backend_data->fd_count--;
413+
garbage_events++;
414+
}
388415
}
389416
} else if (is_oneshot) {
390-
zend_hash_index_del(backend_data->garbage_oneshot_fds, fd);
417+
/* Second filter for same FD fired - clear garbage flags */
418+
zval *tracking = zend_hash_index_find(backend_data->fd_tracking, fd);
419+
if (tracking) {
420+
/* Remove FD from tracking as it gets deleted from kqueue as well */
421+
zend_hash_index_del(backend_data->fd_tracking, fd);
422+
garbage_events--;
423+
}
391424
}
392425
}
393426

394-
/* Clean up all the same FD filters for other read or write side */
395-
zval *item;
396-
struct kevent cleanup_change;
397-
ZEND_HASH_FOREACH_NUM_KEY_VAL(backend_data->garbage_oneshot_fds, fd, item)
398-
{
399-
int filter = Z_TYPE_P(item) == IS_TRUE ? EVFILT_WRITE : EVFILT_READ;
400-
EV_SET(&cleanup_change, fd, filter, EV_DELETE, 0, 0, NULL);
401-
kevent(backend_data->kqueue_fd, &cleanup_change, 1, NULL, 0, NULL);
427+
if (garbage_events > 0) {
428+
/* Clean up orphaned filters from complete oneshot FDs */
429+
zend_ulong fd_key;
430+
zval *tracking;
431+
struct kevent cleanup_change;
432+
ZEND_HASH_FOREACH_NUM_KEY_VAL(backend_data->fd_tracking, fd_key, tracking)
433+
{
434+
zend_long flags = Z_LVAL_P(tracking);
435+
if (flags & KQUEUE_FD_HAS_GARBAGE) {
436+
int filter = (flags & KQUEUE_FD_GARBAGE_READ) ? EVFILT_WRITE : EVFILT_READ;
437+
EV_SET(&cleanup_change, fd_key, filter, EV_DELETE, 0, 0, NULL);
438+
kevent(backend_data->kqueue_fd, &cleanup_change, 1, NULL, 0, NULL);
439+
440+
/* Remove FD from tracking after cleanup */
441+
zend_hash_index_del(backend_data->fd_tracking, fd_key);
442+
}
443+
}
444+
ZEND_HASH_FOREACH_END();
402445
}
403-
ZEND_HASH_FOREACH_END();
404446

405447
return unique_events;
406448
}
@@ -449,7 +491,8 @@ const php_poll_backend_ops php_poll_backend_kqueue_ops = {
449491
.wait = kqueue_backend_wait,
450492
.is_available = kqueue_backend_is_available,
451493
.get_suitable_max_events = kqueue_backend_get_suitable_max_events,
452-
.supports_et = true /* kqueue supports EV_CLEAR for edge triggering */
494+
.supports_et = true
453495
};
454496

497+
455498
#endif /* HAVE_KQUEUE */

0 commit comments

Comments
 (0)