2121#include <stdlib.h>
2222#include <string.h>
2323#include <errno.h>
24- #include <poll.h>
2524
2625typedef struct {
2726 int port_fd ;
2827 port_event_t * events ;
2928 int events_capacity ;
30- int active_associations ;
3129 php_poll_fd_table * fd_table ;
3230} eventport_backend_data_t ;
3331
32+ /* We use last_revents field to track if fd needs (re)association */
33+ #define EVENTPORT_NEEDS_ASSOC 1
34+ #define EVENTPORT_IS_ASSOCIATED 0
35+
3436/* Convert our event flags to event port flags */
3537static int eventport_events_to_native (uint32_t events )
3638{
@@ -93,8 +95,6 @@ static zend_result eventport_backend_init(php_poll_ctx *ctx)
9395 return FAILURE ;
9496 }
9597
96- data -> active_associations = 0 ;
97-
9898 /* Use hint for initial allocation if provided, otherwise start with reasonable default */
9999 int initial_capacity = ctx -> max_events_hint > 0 ? ctx -> max_events_hint : 64 ;
100100 data -> events = php_poll_calloc (initial_capacity , sizeof (port_event_t ), ctx -> persistent );
@@ -135,7 +135,7 @@ static void eventport_backend_cleanup(php_poll_ctx *ctx)
135135 }
136136}
137137
138- /* Add file descriptor to event port */
138+ /* Add file descriptor to event port - just store in table */
139139static zend_result eventport_backend_add (
140140 php_poll_ctx * ctx , int fd , uint32_t events , void * user_data )
141141{
@@ -154,35 +154,12 @@ static zend_result eventport_backend_add(
154154
155155 entry -> events = events ;
156156 entry -> data = user_data ;
157+ entry -> last_revents = EVENTPORT_NEEDS_ASSOC ; /* Mark as needing association */
157158
158- int native_events = eventport_events_to_native (events );
159-
160- /* Associate file descriptor with event port */
161- if (port_associate (backend_data -> port_fd , PORT_SOURCE_FD , fd , native_events , user_data ) == -1 ) {
162- php_poll_fd_table_remove (backend_data -> fd_table , fd );
163- switch (errno ) {
164- case EEXIST :
165- php_poll_set_error (ctx , PHP_POLL_ERR_EXISTS );
166- break ;
167- case ENOMEM :
168- php_poll_set_error (ctx , PHP_POLL_ERR_NOMEM );
169- break ;
170- case EBADF :
171- case EINVAL :
172- php_poll_set_error (ctx , PHP_POLL_ERR_INVALID );
173- break ;
174- default :
175- php_poll_set_error (ctx , PHP_POLL_ERR_SYSTEM );
176- break ;
177- }
178- return FAILURE ;
179- }
180-
181- backend_data -> active_associations ++ ;
182159 return SUCCESS ;
183160}
184161
185- /* Modify file descriptor in event port */
162+ /* Modify file descriptor in event port - just update table */
186163static zend_result eventport_backend_modify (
187164 php_poll_ctx * ctx , int fd , uint32_t events , void * user_data )
188165{
@@ -198,27 +175,13 @@ static zend_result eventport_backend_modify(
198175 entry -> events = events ;
199176 entry -> data = user_data ;
200177
201- /* For event ports, we need to dissociate and re-associate */
202- /* Note: dissociate might fail if the fd was already fired and auto-dissociated */
203- port_dissociate (backend_data -> port_fd , PORT_SOURCE_FD , fd );
204-
205- int native_events = eventport_events_to_native (events );
206- if (port_associate (backend_data -> port_fd , PORT_SOURCE_FD , fd , native_events , user_data ) == -1 ) {
207- switch (errno ) {
208- case ENOMEM :
209- php_poll_set_error (ctx , PHP_POLL_ERR_NOMEM );
210- break ;
211- case EBADF :
212- case EINVAL :
213- php_poll_set_error (ctx , PHP_POLL_ERR_INVALID );
214- break ;
215- default :
216- php_poll_set_error (ctx , PHP_POLL_ERR_SYSTEM );
217- break ;
218- }
219- return FAILURE ;
178+ /* If currently associated, dissociate so we can re-associate with new events */
179+ if (entry -> last_revents == EVENTPORT_IS_ASSOCIATED ) {
180+ port_dissociate (backend_data -> port_fd , PORT_SOURCE_FD , fd );
220181 }
221182
183+ entry -> last_revents = EVENTPORT_NEEDS_ASSOC ; /* Mark as needing re-association */
184+
222185 return SUCCESS ;
223186}
224187
@@ -227,33 +190,79 @@ static zend_result eventport_backend_remove(php_poll_ctx *ctx, int fd)
227190{
228191 eventport_backend_data_t * backend_data = (eventport_backend_data_t * ) ctx -> backend_data ;
229192
230- /* Check if exists using helper */
231- if (!php_poll_fd_table_find ( backend_data -> fd_table , fd ) ) {
193+ php_poll_fd_entry * entry = php_poll_fd_table_find ( backend_data -> fd_table , fd );
194+ if (!entry ) {
232195 php_poll_set_error (ctx , PHP_POLL_ERR_NOTFOUND );
233196 return FAILURE ;
234197 }
235198
236- if (port_dissociate (backend_data -> port_fd , PORT_SOURCE_FD , fd ) == -1 ) {
237- /* Only fail if it's not ENOENT (might already be dissociated) */
238- if (!php_poll_is_not_found_error ()) {
239- php_poll_set_current_errno_error (ctx );
240- return FAILURE ;
199+ /* Only dissociate if it was actually associated */
200+ if (entry -> last_revents == EVENTPORT_IS_ASSOCIATED ) {
201+ if (port_dissociate (backend_data -> port_fd , PORT_SOURCE_FD , fd ) == -1 ) {
202+ /* Only fail if it's not ENOENT (might already be auto-dissociated) */
203+ if (!php_poll_is_not_found_error ()) {
204+ php_poll_set_current_errno_error (ctx );
205+ return FAILURE ;
206+ }
241207 }
242208 }
243209
244210 php_poll_fd_table_remove (backend_data -> fd_table , fd );
245- backend_data -> active_associations -- ;
246211 return SUCCESS ;
247212}
248213
214+ /* Callback context for associating fds */
215+ typedef struct {
216+ eventport_backend_data_t * backend_data ;
217+ php_poll_ctx * ctx ;
218+ bool has_error ;
219+ } eventport_associate_ctx ;
220+
221+ /* Callback to associate fds that need association */
222+ static bool eventport_associate_callback (int fd , php_poll_fd_entry * entry , void * user_data )
223+ {
224+ eventport_associate_ctx * assoc_ctx = (eventport_associate_ctx * ) user_data ;
225+
226+ /* Only associate if marked as needing association */
227+ if (entry -> last_revents == EVENTPORT_NEEDS_ASSOC ) {
228+ int native_events = eventport_events_to_native (entry -> events );
229+
230+ if (port_associate (assoc_ctx -> backend_data -> port_fd , PORT_SOURCE_FD , fd , native_events ,
231+ entry -> data )
232+ == -1 ) {
233+ /* Association failed - could set error here if needed */
234+ switch (errno ) {
235+ case ENOMEM :
236+ php_poll_set_error (assoc_ctx -> ctx , PHP_POLL_ERR_NOMEM );
237+ break ;
238+ case EBADF :
239+ case EINVAL :
240+ php_poll_set_error (assoc_ctx -> ctx , PHP_POLL_ERR_INVALID );
241+ break ;
242+ default :
243+ php_poll_set_error (assoc_ctx -> ctx , PHP_POLL_ERR_SYSTEM );
244+ break ;
245+ }
246+ assoc_ctx -> has_error = true;
247+ return false; /* Stop iteration */
248+ }
249+
250+ /* Mark as associated */
251+ entry -> last_revents = EVENTPORT_IS_ASSOCIATED ;
252+ }
253+
254+ return true; /* Continue iteration */
255+ }
256+
249257/* Wait for events using event port */
250258static int eventport_backend_wait (
251259 php_poll_ctx * ctx , php_poll_event * events , int max_events , int timeout )
252260{
253261 eventport_backend_data_t * backend_data = (eventport_backend_data_t * ) ctx -> backend_data ;
254262
255- if (backend_data -> active_associations == 0 ) {
256- /* No active associations, but we still need to respect timeout */
263+ int fd_count = php_poll_fd_table_count (backend_data -> fd_table );
264+ if (fd_count == 0 ) {
265+ /* No fds to monitor, but we still need to respect timeout */
257266 if (timeout > 0 ) {
258267 struct timespec ts ;
259268 ts .tv_sec = timeout / 1000 ;
@@ -263,6 +272,16 @@ static int eventport_backend_wait(
263272 return 0 ;
264273 }
265274
275+ /* First: associate all fds that need association */
276+ eventport_associate_ctx assoc_ctx
277+ = { .backend_data = backend_data , .ctx = ctx , .has_error = false };
278+
279+ php_poll_fd_table_foreach (backend_data -> fd_table , eventport_associate_callback , & assoc_ctx );
280+
281+ if (assoc_ctx .has_error ) {
282+ return -1 ;
283+ }
284+
266285 /* Ensure we have enough space for the requested events */
267286 if (max_events > backend_data -> events_capacity ) {
268287 port_event_t * new_events = php_poll_realloc (
@@ -296,49 +315,29 @@ static int eventport_backend_wait(
296315 }
297316
298317 int nfds = (int ) nget ;
299- int check_count = 0 ;
300318
301- /* First pass: process events, identify unfired events, and re-associate */
319+ /* Process events */
302320 for (int i = 0 ; i < nfds ; i ++ ) {
303321 port_event_t * port_event = & backend_data -> events [i ];
304322
305323 /* Only handle PORT_SOURCE_FD events */
306324 if (port_event -> portev_source == PORT_SOURCE_FD ) {
307325 int fd = (int ) port_event -> portev_object ;
308- uint32_t fired = eventport_events_from_native (port_event -> portev_events );
309326
310327 events [i ].fd = fd ;
311328 events [i ].events = 0 ;
312- events [i ].revents = fired ;
329+ events [i ].revents = eventport_events_from_native ( port_event -> portev_events ) ;
313330 events [i ].data = port_event -> portev_user ;
314331
315- /* Get entry and handle re- association */
332+ /* After event fires, the association is automatically removed by event ports */
316333 php_poll_fd_entry * entry = php_poll_fd_table_find (backend_data -> fd_table , fd );
317334 if (entry ) {
318- /* Check if there are other events we're monitoring */
319- uint32_t monitored = entry -> events & (PHP_POLL_READ | PHP_POLL_WRITE );
320- uint32_t unfired = monitored & ~fired ;
321-
322- if (unfired ) {
323- /* Store unfired events for potential second-round check */
324- events [i ].events = unfired ;
325- check_count ++ ;
326- }
327-
328335 if (entry -> events & PHP_POLL_ONESHOT ) {
329- /* Oneshot: remove from tracking */
336+ /* Oneshot: remove from tracking completely */
330337 php_poll_fd_table_remove (backend_data -> fd_table , fd );
331- backend_data -> active_associations -- ;
332338 } else {
333- /* Re-associate immediately with all originally registered events */
334- int native_events = eventport_events_to_native (entry -> events );
335- if (port_associate (backend_data -> port_fd , PORT_SOURCE_FD , fd , native_events ,
336- entry -> data )
337- != 0 ) {
338- /* Re-association failed - remove from tracking */
339- php_poll_fd_table_remove (backend_data -> fd_table , fd );
340- backend_data -> active_associations -- ;
341- }
339+ /* Mark for re-association on next wait() call */
340+ entry -> last_revents = EVENTPORT_NEEDS_ASSOC ;
342341 }
343342 }
344343 } else {
@@ -350,46 +349,6 @@ static int eventport_backend_wait(
350349 }
351350 }
352351
353- /* Second pass: if we have unfired events, check them with poll() */
354- if (check_count > 0 ) {
355- struct pollfd * check_fds
356- = php_poll_calloc (check_count , sizeof (struct pollfd ), ctx -> persistent );
357- int * check_indices = php_poll_calloc (check_count , sizeof (int ), ctx -> persistent );
358-
359- if (check_fds && check_indices ) {
360- int check_idx = 0 ;
361- for (int i = 0 ; i < nfds ; i ++ ) {
362- if (events [i ].events != 0 && events [i ].fd >= 0 ) {
363- check_fds [check_idx ].fd = events [i ].fd ;
364- check_fds [check_idx ].events = eventport_events_to_native (events [i ].events );
365- check_fds [check_idx ].revents = 0 ;
366- check_indices [check_idx ] = i ;
367- check_idx ++ ;
368- events [i ].events = 0 ; /* Clear it as it was just temporary */
369- }
370- }
371-
372- /* Non-blocking poll to check if other events are ready */
373- if (poll (check_fds , check_count , 0 ) > 0 ) {
374- for (int j = 0 ; j < check_count ; j ++ ) {
375- if (check_fds [j ].revents != 0 ) {
376- int evt_idx = check_indices [j ];
377- uint32_t additional = eventport_events_from_native (check_fds [j ].revents );
378- /* Add the additional ready events to revents */
379- events [evt_idx ].revents |= additional ;
380- }
381- }
382- }
383- }
384-
385- if (check_fds ) {
386- pefree (check_fds , ctx -> persistent );
387- }
388- if (check_indices ) {
389- pefree (check_indices , ctx -> persistent );
390- }
391- }
392-
393352 return nfds ;
394353}
395354
@@ -412,16 +371,15 @@ static int eventport_backend_get_suitable_max_events(php_poll_ctx *ctx)
412371 return -1 ;
413372 }
414373
415- /* For event ports, we track exactly how many FD associations are active */
416- int active_associations = backend_data -> active_associations ;
374+ int fd_count = php_poll_fd_table_count (backend_data -> fd_table );
417375
418- if (active_associations == 0 ) {
376+ if (fd_count == 0 ) {
419377 return 1 ;
420378 }
421379
422380 /* Event ports can return exactly one event per association,
423- * so the suitable max_events is exactly the number of active associations */
424- return active_associations ;
381+ * so the suitable max_events is exactly the number of tracked fds */
382+ return fd_count ;
425383}
426384
427385/* Event port backend operations structure */
0 commit comments