Skip to content

Commit 98202e3

Browse files
committed
poll: add actually kqueue implementation for raw_events
1 parent 4f75383 commit 98202e3

File tree

1 file changed

+149
-93
lines changed

1 file changed

+149
-93
lines changed

main/poll/poll_backend_kqueue.c

Lines changed: 149 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ typedef struct {
2525
struct kevent *events;
2626
int events_capacity;
2727
int fd_count; /* Track number of unique FDs (not individual filters) */
28+
int filter_count; /* Track total number of filters for raw events */
2829
HashTable *complete_oneshot_fds; /* Track FDs with both read+write oneshot */
2930
HashTable *garbage_oneshot_fds; /* Pre-cached hash table for FDs to delete */
3031
} kqueue_backend_data_t;
@@ -56,12 +57,18 @@ static zend_result kqueue_backend_init(php_poll_ctx *ctx)
5657
}
5758
data->events_capacity = initial_capacity;
5859
data->fd_count = 0; /* Initialize FD counter */
59-
60-
/* Initialize oneshot related hash tables */
61-
data->complete_oneshot_fds = php_poll_malloc(sizeof(HashTable), ctx->persistent);
62-
zend_hash_init(data->complete_oneshot_fds, 8, NULL, NULL, ctx->persistent);
63-
data->garbage_oneshot_fds = php_poll_malloc(sizeof(HashTable), ctx->persistent);
64-
zend_hash_init(data->garbage_oneshot_fds, 8, NULL, NULL, ctx->persistent);
60+
data->filter_count = 0; /* Initialize filter counter */
61+
62+
/* Only initialize oneshot related hash tables if not using raw events */
63+
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);
68+
} else {
69+
data->complete_oneshot_fds = NULL;
70+
data->garbage_oneshot_fds = NULL;
71+
}
6572

6673
ctx->backend_data = data;
6774
return SUCCESS;
@@ -75,10 +82,17 @@ static void kqueue_backend_cleanup(php_poll_ctx *ctx)
7582
close(data->kqueue_fd);
7683
}
7784
pefree(data->events, ctx->persistent);
78-
zend_hash_destroy(data->complete_oneshot_fds);
79-
pefree(data->complete_oneshot_fds, ctx->persistent);
80-
zend_hash_destroy(data->garbage_oneshot_fds);
81-
pefree(data->garbage_oneshot_fds, ctx->persistent);
85+
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);
94+
}
95+
8296
pefree(data, ctx->persistent);
8397
ctx->backend_data = NULL;
8498
}
@@ -118,10 +132,13 @@ static zend_result kqueue_backend_add(php_poll_ctx *ctx, int fd, uint32_t events
118132

119133
/* Increment FD count only once per unique FD */
120134
backend_data->fd_count++;
135+
/* Increment filter count by number of filters added */
136+
backend_data->filter_count += change_count;
121137

122-
/* Track if this FD has both read+write oneshot */
123-
if ((events & (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT))
124-
== (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) {
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)) {
125142
zend_hash_index_add_empty_element(backend_data->complete_oneshot_fds, fd);
126143
}
127144
}
@@ -188,21 +205,30 @@ static zend_result kqueue_backend_modify(php_poll_ctx *ctx, int fd, uint32_t eve
188205
}
189206
}
190207

191-
/* Update FD count and oneshot tracking */
208+
/* Update counters and oneshot tracking */
192209
if (successful_deletes > 0 && add_count == 0) {
193210
/* Removed all filters - FD is gone */
194211
backend_data->fd_count--;
195-
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
212+
backend_data->filter_count -= successful_deletes;
213+
if (!ctx->raw_events) {
214+
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
215+
}
196216
} else if (successful_deletes == 0 && add_count > 0) {
197217
/* Added filters to previously empty FD */
198218
backend_data->fd_count++;
199-
if ((events & (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT))
200-
== (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) {
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)) {
201223
zend_hash_index_add_empty_element(backend_data->complete_oneshot_fds, fd);
202224
}
203225
} else if (successful_deletes > 0 || add_count > 0) {
204-
/* One of the filter was deleted so remove from oneshot tracking */
205-
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
226+
/* Mixed operation - update filter count */
227+
backend_data->filter_count = backend_data->filter_count - successful_deletes + add_count;
228+
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);
231+
}
206232
}
207233

208234
return SUCCESS;
@@ -240,11 +266,14 @@ static zend_result kqueue_backend_remove(php_poll_ctx *ctx, int fd)
240266
return FAILURE;
241267
}
242268

243-
/* Update FD count - we removed all filters for this FD */
269+
/* Update counters - we removed all filters for this FD */
244270
backend_data->fd_count--;
271+
backend_data->filter_count -= successful_deletes;
245272

246-
/* Remove from complete oneshot tracking */
247-
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
273+
/* Remove from complete oneshot tracking if not using raw events */
274+
if (!ctx->raw_events) {
275+
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
276+
}
248277

249278
return SUCCESS;
250279
}
@@ -254,9 +283,9 @@ static int kqueue_backend_wait(
254283
{
255284
kqueue_backend_data_t *backend_data = (kqueue_backend_data_t *) ctx->backend_data;
256285

257-
/* Ensure we have enough space for the requested events as kqueue can return up to 2 raw events
258-
* per FD (read + write), we need capacity for potentially 2x max_events. */
259-
int required_capacity = max_events * 2;
286+
/* For raw events, we need capacity for max_events.
287+
* For grouped events, kqueue can return up to 2 events per FD, so we need 2x capacity. */
288+
int required_capacity = ctx->raw_events ? max_events : (max_events * 2);
260289
if (required_capacity > backend_data->events_capacity) {
261290
struct kevent *new_events = php_poll_realloc(
262291
backend_data->events, required_capacity * sizeof(struct kevent), ctx->persistent);
@@ -279,74 +308,102 @@ static int kqueue_backend_wait(
279308
backend_data->kqueue_fd, NULL, 0, backend_data->events, required_capacity, tsp);
280309

281310
if (nfds > 0) {
282-
/* Group events by FD and combine read/write events */
283-
int unique_events = 0, fd;
284-
zend_hash_clean(backend_data->garbage_oneshot_fds);
285-
286-
for (int i = 0; i < nfds; i++) {
287-
fd = (int) backend_data->events[i].ident;
288-
uint32_t revents = 0;
289-
void *data = backend_data->events[i].udata;
290-
bool is_oneshot = (backend_data->events[i].flags & EV_ONESHOT) != 0;
291-
292-
/* Convert this event */
293-
if (backend_data->events[i].filter == EVFILT_READ) {
294-
revents |= PHP_POLL_READ;
295-
} else if (backend_data->events[i].filter == EVFILT_WRITE) {
296-
revents |= PHP_POLL_WRITE;
297-
}
311+
if (ctx->raw_events) {
312+
/* Raw events mode - direct 1:1 mapping, no grouping */
313+
for (int i = 0; i < nfds && i < max_events; i++) {
314+
events[i].fd = (int) backend_data->events[i].ident;
315+
events[i].events = 0; /* Not used in raw mode */
316+
events[i].revents = 0;
317+
events[i].data = backend_data->events[i].udata;
318+
319+
/* Convert kqueue filter to poll event */
320+
if (backend_data->events[i].filter == EVFILT_READ) {
321+
events[i].revents |= PHP_POLL_READ;
322+
} else if (backend_data->events[i].filter == EVFILT_WRITE) {
323+
events[i].revents |= PHP_POLL_WRITE;
324+
}
298325

299-
if (backend_data->events[i].flags & EV_EOF) {
300-
revents |= PHP_POLL_HUP;
301-
}
302-
if (backend_data->events[i].flags & EV_ERROR) {
303-
revents |= PHP_POLL_ERROR;
326+
/* Convert kqueue flags to poll events */
327+
if (backend_data->events[i].flags & EV_EOF) {
328+
events[i].revents |= PHP_POLL_HUP;
329+
}
330+
if (backend_data->events[i].flags & EV_ERROR) {
331+
events[i].revents |= PHP_POLL_ERROR;
332+
}
304333
}
334+
/* In raw mode, we might return fewer events than nfds if max_events < nfds */
335+
return nfds > max_events ? max_events : nfds;
336+
} else {
337+
/* Grouped events mode - existing complex logic */
338+
int unique_events = 0, fd;
339+
zend_hash_clean(backend_data->garbage_oneshot_fds);
340+
341+
for (int i = 0; i < nfds; i++) {
342+
fd = (int) backend_data->events[i].ident;
343+
uint32_t revents = 0;
344+
void *data = backend_data->events[i].udata;
345+
bool is_oneshot = (backend_data->events[i].flags & EV_ONESHOT) != 0;
346+
347+
/* Convert this event */
348+
if (backend_data->events[i].filter == EVFILT_READ) {
349+
revents |= PHP_POLL_READ;
350+
} else if (backend_data->events[i].filter == EVFILT_WRITE) {
351+
revents |= PHP_POLL_WRITE;
352+
}
305353

306-
/* Look for existing event for this FD */
307-
bool found = false;
308-
for (int j = 0; j < unique_events; j++) {
309-
if (events[j].fd == fd) {
310-
/* Combine with existing event */
311-
events[j].revents |= revents;
312-
found = true;
313-
break;
354+
if (backend_data->events[i].flags & EV_EOF) {
355+
revents |= PHP_POLL_HUP;
356+
}
357+
if (backend_data->events[i].flags & EV_ERROR) {
358+
revents |= PHP_POLL_ERROR;
359+
}
360+
361+
/* Look for existing event for this FD */
362+
bool found = false;
363+
for (int j = 0; j < unique_events; j++) {
364+
if (events[j].fd == fd) {
365+
/* Combine with existing event */
366+
events[j].revents |= revents;
367+
found = true;
368+
break;
369+
}
314370
}
315-
}
316371

317-
if (!found) {
318-
/* New FD, create new event */
319-
ZEND_ASSERT(unique_events < max_events);
320-
events[unique_events].fd = fd;
321-
events[unique_events].events = 0;
322-
events[unique_events].revents = revents;
323-
events[unique_events].data = data;
324-
unique_events++;
325-
326-
if (is_oneshot && zend_hash_index_exists(backend_data->complete_oneshot_fds, fd)) {
327-
zval dummy;
328-
ZVAL_BOOL(&dummy, revents & PHP_POLL_READ);
329-
zend_hash_index_add(backend_data->garbage_oneshot_fds, fd, &dummy);
330-
zend_hash_index_del(backend_data->complete_oneshot_fds, fd);
331-
backend_data->fd_count--;
372+
if (!found) {
373+
/* New FD, create new event */
374+
ZEND_ASSERT(unique_events < max_events);
375+
events[unique_events].fd = fd;
376+
events[unique_events].events = 0;
377+
events[unique_events].revents = revents;
378+
events[unique_events].data = data;
379+
unique_events++;
380+
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--;
388+
}
389+
} else if (is_oneshot) {
390+
zend_hash_index_del(backend_data->garbage_oneshot_fds, fd);
332391
}
333-
} else if (is_oneshot) {
334-
zend_hash_index_del(backend_data->garbage_oneshot_fds, fd);
335392
}
336-
}
337393

338-
/* Clean up all the same FD filters for other read or write side */
339-
zval *item;
340-
struct kevent cleanup_change;
341-
ZEND_HASH_FOREACH_NUM_KEY_VAL(backend_data->garbage_oneshot_fds, fd, item)
342-
{
343-
int filter = Z_TYPE_P(item) == IS_TRUE ? EVFILT_WRITE : EVFILT_READ;
344-
EV_SET(&cleanup_change, fd, filter, EV_DELETE, 0, 0, NULL);
345-
kevent(backend_data->kqueue_fd, &cleanup_change, 1, NULL, 0, NULL);
346-
}
347-
ZEND_HASH_FOREACH_END();
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);
402+
}
403+
ZEND_HASH_FOREACH_END();
348404

349-
return unique_events;
405+
return unique_events;
406+
}
350407
}
351408

352409
return nfds;
@@ -370,16 +427,15 @@ static int kqueue_backend_get_suitable_max_events(php_poll_ctx *ctx)
370427
return -1;
371428
}
372429

373-
/* For kqueue, we now track exactly how many unique FDs are registered */
374-
int active_fds = backend_data->fd_count;
375-
376-
if (active_fds == 0) {
377-
return 1;
430+
if (ctx->raw_events) {
431+
/* For raw events, return the total number of filters */
432+
int active_filters = backend_data->filter_count;
433+
return active_filters == 0 ? 1 : active_filters;
434+
} else {
435+
/* For grouped events, return the number of unique FDs */
436+
int active_fds = backend_data->fd_count;
437+
return active_fds == 0 ? 1 : active_fds;
378438
}
379-
380-
/* Kqueue backend will return one grouped event per FD (like epoll),
381-
* so the suitable max_events is exactly the number of registered FDs */
382-
return active_fds;
383439
}
384440

385441
const php_poll_backend_ops php_poll_backend_kqueue_ops = {

0 commit comments

Comments
 (0)