@@ -56,6 +56,9 @@ typedef struct php_io_poll_watcher_object {
5656 zval data ;
5757 bool active ;
5858 bool closed ; /* Deactivated because its stream was closed */
59+ bool disarmed ; /* Fired one-shot, until modifyEvents() */
60+ uint32_t wait_gen ; /* Last wait() that reported it */
61+ const php_poll_handle_ext_ops * ext_ops ;
5962 php_io_poll_context_object * context ; /* Back reference to Context object */
6063 php_socket_t fd ; /* Registered fd, SOCK_ERR when inactive */
6164 php_stream * stream ; /* Watched stream, NULL when not registered in its watcher list */
@@ -66,6 +69,8 @@ typedef struct php_io_poll_watcher_object {
6669struct php_io_poll_context_object {
6770 php_poll_ctx * ctx ;
6871 HashTable * watchers ; /* Maps fd -> watcher object */
72+ uint32_t wait_gen ; /* Incremented per wait() */
73+ bool buffered_readers ; /* Some watcher can report buffered data */
6974 zend_object std ;
7075};
7176
@@ -239,6 +244,17 @@ static php_poll_handle_ops php_stream_poll_handle_ops = {
239244 .cleanup = php_stream_poll_handle_cleanup
240245};
241246
247+ /* The stream buffer only, without any IO */
248+ static bool php_stream_poll_handle_has_buffered_data (php_poll_handle_object * handle )
249+ {
250+ php_stream * stream = php_stream_poll_handle_get_stream (handle );
251+ return stream && stream -> writepos > stream -> readpos ;
252+ }
253+
254+ static const php_poll_handle_ext_ops php_stream_poll_handle_ext_ops = {
255+ .has_buffered_data = php_stream_poll_handle_has_buffered_data
256+ };
257+
242258/* Handle interface internal only */
243259static int php_stream_poll_handle_implement_interface (zend_class_entry * interface , zend_class_entry * implementor )
244260{
@@ -271,6 +287,9 @@ static zend_object *php_io_poll_watcher_create_object(zend_class_entry *ce)
271287 intern -> triggered_events = 0 ;
272288 intern -> active = false;
273289 intern -> closed = false;
290+ intern -> disarmed = false;
291+ intern -> wait_gen = 0 ;
292+ intern -> ext_ops = NULL ;
274293 intern -> context = NULL ;
275294 intern -> fd = SOCK_ERR ;
276295 intern -> stream = NULL ;
@@ -288,6 +307,8 @@ static zend_object *php_io_poll_context_create_object(zend_class_entry *ce)
288307
289308 intern -> ctx = NULL ;
290309 intern -> watchers = NULL ;
310+ intern -> wait_gen = 0 ;
311+ intern -> buffered_readers = false;
291312
292313 return & intern -> std ;
293314}
@@ -300,6 +321,13 @@ static zend_always_inline zend_ulong php_io_poll_compute_ptr_key(void *ptr)
300321 return (key >> 3 ) | (key << ((sizeof (key ) * 8 ) - 3 ));
301322}
302323
324+ /* Level Read watcher whose handle can have buffered data */
325+ static zend_always_inline bool php_io_poll_watcher_reads_buffered (php_io_poll_watcher_object * watcher )
326+ {
327+ return watcher -> ext_ops && watcher -> ext_ops -> has_buffered_data
328+ && (watcher -> watched_events & (PHP_POLL_READ | PHP_POLL_ET )) == PHP_POLL_READ ;
329+ }
330+
303331static zend_always_inline void php_io_poll_watcher_deactivate (php_io_poll_watcher_object * watcher )
304332{
305333 watcher -> active = false;
@@ -461,6 +489,10 @@ static zend_result php_io_poll_watcher_modify_events(
461489
462490 /* Update watcher state */
463491 watcher -> watched_events = events ;
492+ watcher -> disarmed = false;
493+ if (php_io_poll_watcher_reads_buffered (watcher )) {
494+ watcher -> context -> buffered_readers = true;
495+ }
464496
465497 return SUCCESS ;
466498}
@@ -841,6 +873,10 @@ PHP_METHOD(Io_Poll_Context, add)
841873 watcher -> active = true;
842874 watcher -> context = intern ;
843875 watcher -> fd = fd ;
876+ watcher -> ext_ops = php_poll_handle_get_ext_ops (handle -> ops );
877+ if (php_io_poll_watcher_reads_buffered (watcher )) {
878+ intern -> buffered_readers = true;
879+ }
844880
845881 if (handle -> ops == & php_stream_poll_handle_ops ) {
846882 php_stream * stream = php_stream_poll_handle_get_stream (handle );
@@ -889,33 +925,95 @@ PHP_METHOD(Io_Poll_Context, wait)
889925 RETURN_THROWS ();
890926 }
891927
892- php_poll_event * events = safe_emalloc ((size_t ) max_events , sizeof (* events ), 0 );
893- int num_events = php_poll_wait (intern -> ctx , events , (int ) max_events , timeout ? & timeout_ts : NULL );
928+ /* Buffered watchers go first, then a non-blocking backend check */
929+ php_io_poll_watcher_object * * buffered = NULL ;
930+ int n_buffered = 0 ;
931+ if (intern -> buffered_readers ) {
932+ ZEND_HASH_FOREACH_VAL (intern -> watchers , zval * zv ) {
933+ php_io_poll_watcher_object * watcher = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ (Z_OBJ_P (zv ));
934+ if (watcher -> disarmed || !php_io_poll_watcher_reads_buffered (watcher )
935+ || !watcher -> ext_ops -> has_buffered_data (watcher -> handle )) {
936+ continue ;
937+ }
938+ if (!buffered ) {
939+ buffered = safe_emalloc ((size_t ) max_events , sizeof (* buffered ), 0 );
940+ }
941+ buffered [n_buffered ++ ] = watcher ;
942+ if (n_buffered == max_events ) {
943+ break ;
944+ }
945+ } ZEND_HASH_FOREACH_END ();
946+ }
894947
895- if (num_events < 0 ) {
896- php_poll_error err = php_poll_get_error (intern -> ctx );
897- efree (events );
898- php_io_poll_throw_failed_operation (
899- php_io_poll_failed_wait_class_entry , "Poll wait failed" , err );
900- RETURN_THROWS ();
948+ php_poll_event * events = safe_emalloc ((size_t ) max_events , sizeof (* events ), 0 );
949+ int num_events = 0 ;
950+ if (n_buffered < max_events ) {
951+ struct timespec zero_ts = {0 , 0 };
952+ num_events = php_poll_wait (intern -> ctx , events , (int ) max_events - n_buffered ,
953+ n_buffered ? & zero_ts : (timeout ? & timeout_ts : NULL ));
954+ if (num_events < 0 ) {
955+ php_poll_error err = php_poll_get_error (intern -> ctx );
956+ if (n_buffered == 0 || err != PHP_POLL_ERR_INTERRUPTED ) {
957+ efree (events );
958+ if (buffered ) {
959+ efree (buffered );
960+ }
961+ php_io_poll_throw_failed_operation (
962+ php_io_poll_failed_wait_class_entry , "Poll wait failed" , err );
963+ RETURN_THROWS ();
964+ }
965+ /* The buffered data is the result */
966+ num_events = 0 ;
967+ }
901968 }
902969
903- array_init (return_value );
970+ uint32_t gen = ++ intern -> wait_gen ;
971+ array_init_size (return_value , (uint32_t ) (n_buffered + num_events ));
972+
973+ for (int i = 0 ; i < n_buffered ; i ++ ) {
974+ php_io_poll_watcher_object * watcher = buffered [i ];
975+ watcher -> triggered_events = PHP_POLL_READ ;
976+ watcher -> wait_gen = gen ;
977+
978+ zval watcher_zv ;
979+ ZVAL_OBJ_COPY (& watcher_zv , & watcher -> std );
980+ add_next_index_zval (return_value , & watcher_zv );
981+ }
904982
905983 for (int i = 0 ; i < num_events ; i ++ ) {
906984 php_io_poll_watcher_object * watcher = (php_io_poll_watcher_object * ) events [i ].data ;
907- if (watcher ) {
908- watcher -> triggered_events = events [i ].revents ;
985+ if (!watcher ) {
986+ continue ;
987+ }
988+ if (watcher -> watched_events & PHP_POLL_ONESHOT ) {
989+ watcher -> disarmed = true;
990+ }
991+ if (watcher -> wait_gen == gen ) {
992+ /* Already reported from its buffer */
993+ watcher -> triggered_events |= events [i ].revents ;
994+ continue ;
995+ }
996+ watcher -> triggered_events = events [i ].revents ;
997+ watcher -> wait_gen = gen ;
909998
910- zval watcher_zv ;
911- ZVAL_OBJ (& watcher_zv , & watcher -> std );
912- GC_ADDREF (& watcher -> std );
999+ zval watcher_zv ;
1000+ ZVAL_OBJ_COPY (& watcher_zv , & watcher -> std );
1001+ add_next_index_zval (return_value , & watcher_zv );
1002+ }
9131003
914- add_next_index_zval (return_value , & watcher_zv );
1004+ /* Buffer-only one-shot is still armed in the backend */
1005+ for (int i = 0 ; i < n_buffered ; i ++ ) {
1006+ php_io_poll_watcher_object * watcher = buffered [i ];
1007+ if ((watcher -> watched_events & PHP_POLL_ONESHOT ) && !watcher -> disarmed ) {
1008+ watcher -> disarmed = true;
1009+ php_poll_remove (intern -> ctx , (int ) watcher -> fd );
9151010 }
9161011 }
9171012
9181013 efree (events );
1014+ if (buffered ) {
1015+ efree (buffered );
1016+ }
9191017}
9201018
9211019PHP_METHOD (Io_Poll_Context , getBackend )
@@ -1015,6 +1113,7 @@ PHP_MINIT_FUNCTION(poll)
10151113
10161114 /* Initialize polling backends */
10171115 php_poll_register_backends ();
1116+ php_poll_handle_register_ext_ops (& php_stream_poll_handle_ops , & php_stream_poll_handle_ext_ops );
10181117
10191118 return SUCCESS ;
10201119}
0 commit comments