2121#include <stdlib.h>
2222#include <string.h>
2323#include <errno.h>
24+ #include <poll.h>
2425
2526typedef struct {
2627 int port_fd ;
@@ -245,39 +246,6 @@ static zend_result eventport_backend_remove(php_poll_ctx *ctx, int fd)
245246 return SUCCESS ;
246247}
247248
248- /* Handle re-association after event */
249- static void eventport_handle_reassociation (
250- eventport_backend_data_t * backend_data , int fd , uint32_t fired_events )
251- {
252- php_poll_fd_entry * entry = php_poll_fd_table_find (backend_data -> fd_table , fd );
253- if (!entry ) {
254- return ;
255- }
256-
257- if (entry -> events & PHP_POLL_ONESHOT ) {
258- /* Oneshot: remove from tracking */
259- php_poll_fd_table_remove (backend_data -> fd_table , fd );
260- backend_data -> active_associations -- ;
261- return ;
262- }
263-
264- /* Re-associate for continued monitoring */
265- int native_events = eventport_events_to_native (entry -> events );
266- if (native_events == 0 ) {
267- /* Nothing meaningful to watch - stop tracking */
268- php_poll_fd_table_remove (backend_data -> fd_table , fd );
269- backend_data -> active_associations -- ;
270- return ;
271- }
272-
273- if (port_associate (backend_data -> port_fd , PORT_SOURCE_FD , fd , native_events , entry -> data )
274- != 0 ) {
275- /* Re-association failed - might be due to fd being closed */
276- php_poll_fd_table_remove (backend_data -> fd_table , fd );
277- backend_data -> active_associations -- ;
278- }
279- }
280-
281249/* Wait for events using event port */
282250static int eventport_backend_wait (
283251 php_poll_ctx * ctx , php_poll_event * events , int max_events , int timeout )
@@ -321,24 +289,55 @@ static int eventport_backend_wait(
321289
322290 if (result == -1 ) {
323291 php_poll_set_current_errno_error (ctx );
292+ return -1 ;
324293 }
325294
326295 int nfds = (int ) nget ;
296+ int check_count = 0 ;
327297
328- /* Process the events and handle re-association */
298+ /* First pass: process events, identify unfired events, and re-associate */
329299 for (int i = 0 ; i < nfds ; i ++ ) {
330300 port_event_t * port_event = & backend_data -> events [i ];
331301
332302 /* Only handle PORT_SOURCE_FD events */
333303 if (port_event -> portev_source == PORT_SOURCE_FD ) {
334304 int fd = (int ) port_event -> portev_object ;
305+ uint32_t fired = eventport_events_from_native (port_event -> portev_events );
306+
335307 events [i ].fd = fd ;
336- events [i ].events = 0 ; /* Not used in results */
337- events [i ].revents = eventport_events_from_native ( port_event -> portev_events ) ;
308+ events [i ].events = 0 ;
309+ events [i ].revents = fired ;
338310 events [i ].data = port_event -> portev_user ;
339311
340- /* Handle re-association based on event type */
341- eventport_handle_reassociation (backend_data , fd , events [i ].revents );
312+ /* Get entry and handle re-association */
313+ php_poll_fd_entry * entry = php_poll_fd_table_find (backend_data -> fd_table , fd );
314+ if (entry ) {
315+ if (entry -> events & PHP_POLL_ONESHOT ) {
316+ /* Oneshot: remove from tracking */
317+ php_poll_fd_table_remove (backend_data -> fd_table , fd );
318+ backend_data -> active_associations -- ;
319+ } else {
320+ /* Check if there are other events we're monitoring */
321+ uint32_t monitored = entry -> events & (PHP_POLL_READ | PHP_POLL_WRITE );
322+ uint32_t unfired = monitored & ~fired ;
323+
324+ if (unfired ) {
325+ /* Store unfired events for potential second-round check */
326+ events [i ].events = unfired ;
327+ check_count ++ ;
328+ }
329+
330+ /* Re-associate immediately with all originally registered events */
331+ int native_events = eventport_events_to_native (entry -> events );
332+ if (port_associate (backend_data -> port_fd , PORT_SOURCE_FD , fd , native_events ,
333+ entry -> data )
334+ != 0 ) {
335+ /* Re-association failed - remove from tracking */
336+ php_poll_fd_table_remove (backend_data -> fd_table , fd );
337+ backend_data -> active_associations -- ;
338+ }
339+ }
340+ }
342341 } else {
343342 /* Handle other event sources if needed (timers, user events, etc.) */
344343 events [i ].fd = -1 ;
@@ -348,6 +347,46 @@ static int eventport_backend_wait(
348347 }
349348 }
350349
350+ /* Second pass: if we have unfired events, check them with poll() */
351+ if (check_count > 0 ) {
352+ struct pollfd * check_fds
353+ = php_poll_calloc (check_count , sizeof (struct pollfd ), ctx -> persistent );
354+ int * check_indices = php_poll_calloc (check_count , sizeof (int ), ctx -> persistent );
355+
356+ if (check_fds && check_indices ) {
357+ int check_idx = 0 ;
358+ for (int i = 0 ; i < nfds ; i ++ ) {
359+ if (events [i ].events != 0 && events [i ].fd >= 0 ) {
360+ check_fds [check_idx ].fd = events [i ].fd ;
361+ check_fds [check_idx ].events = eventport_events_to_native (events [i ].events );
362+ check_fds [check_idx ].revents = 0 ;
363+ check_indices [check_idx ] = i ;
364+ check_idx ++ ;
365+ events [i ].events = 0 ; /* Clear it as it was just temporary */
366+ }
367+ }
368+
369+ /* Non-blocking poll to check if other events are ready */
370+ if (poll (check_fds , check_count , 0 ) > 0 ) {
371+ for (int j = 0 ; j < check_count ; j ++ ) {
372+ if (check_fds [j ].revents != 0 ) {
373+ int evt_idx = check_indices [j ];
374+ uint32_t additional = eventport_events_from_native (check_fds [j ].revents );
375+ /* Add the additional ready events to revents */
376+ events [evt_idx ].revents |= additional ;
377+ }
378+ }
379+ }
380+ }
381+
382+ if (check_fds ) {
383+ pefree (check_fds , ctx -> persistent );
384+ }
385+ if (check_indices ) {
386+ pefree (check_indices , ctx -> persistent );
387+ }
388+ }
389+
351390 return nfds ;
352391}
353392
@@ -394,8 +433,7 @@ const php_poll_backend_ops php_poll_backend_eventport_ops = {
394433 .wait = eventport_backend_wait ,
395434 .is_available = eventport_backend_is_available ,
396435 .get_suitable_max_events = eventport_backend_get_suitable_max_events ,
397- .supports_et = false
436+ .supports_et = false /* Event ports are level-triggered only */
398437};
399438
400439#endif /* HAVE_EVENT_PORTS */
401-
0 commit comments