@@ -667,7 +667,8 @@ ZEND_API int php_select_async(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, f
667667typedef struct async_stream_callback_s {
668668 zend_coroutine_event_callback_t callback ;
669669 io_descriptor_t fd ;
670- zval * stream ;
670+ php_stream * stream ;
671+ zend_async_poll_event_t * event ;
671672 async_poll_event events ;
672673 bool ready ;
673674 struct async_stream_callback_s * next ; // For linked list
@@ -712,14 +713,13 @@ static zend_always_inline bool process_stream_array(zval *streams, async_poll_ev
712713 return true;
713714 }
714715
715- zval * elem ;
716+ zval * z_stream ;
716717 php_stream * stream ;
717- php_netstream_data_t * sock ;
718718 int count = 0 ;
719719
720- ZEND_HASH_FOREACH_VAL (Z_ARR_P (streams ), elem ) {
721- ZVAL_DEREF (elem );
722- php_stream_from_zval_no_verify (stream , elem );
720+ ZEND_HASH_FOREACH_VAL (Z_ARR_P (streams ), z_stream ) {
721+ ZVAL_DEREF (z_stream );
722+ php_stream_from_zval_no_verify (stream , z_stream );
723723 if (stream == NULL ) continue ;
724724
725725 // Try to get async event handle from socket streams first
@@ -737,6 +737,8 @@ static zend_always_inline bool process_stream_array(zval *streams, async_poll_ev
737737 return false;
738738 }
739739
740+ io_descriptor .type = is_socket ? IO_DESCRIPTOR_SOCKET : IO_DESCRIPTOR_FD ;
741+
740742 // If stream has no event handle, create it.
741743 if (event_handle == NULL ) {
742744 if (io_descriptor .type == IO_DESCRIPTOR_SOCKET ) {
@@ -764,7 +766,8 @@ static zend_always_inline bool process_stream_array(zval *streams, async_poll_ev
764766 callback -> callback .coroutine = coroutine ;
765767 callback -> callback .base .ref_count = 1 ;
766768 callback -> callback .base .callback = async_stream_callback_resolve ;
767- callback -> stream = elem ;
769+ callback -> stream = stream ;
770+ callback -> event = event_handle ;
768771 callback -> fd = io_descriptor ;
769772 callback -> events = events ;
770773 callback -> ready = false;
@@ -813,7 +816,44 @@ static void modify_stream_array(zval *stream_array, HashTable *ready_streams) {
813816 ZVAL_ARR (stream_array , ht );
814817}
815818
816- ZEND_API int async_select (zval * read_streams , zval * write_streams , zval * except_streams , struct timeval * tv )
819+ /**
820+ * Asynchronous select() implementation for PHP stream arrays in coroutine contexts.
821+ *
822+ * This function provides an async-compatible version of the standard select()
823+ * system call, allowing coroutines to wait for I/O events on multiple PHP streams
824+ * without blocking the entire thread.
825+ *
826+ * @param read_streams Array of streams to monitor for read events, or NULL if
827+ * not monitoring for read events. Modified to indicate
828+ * which streams are ready for reading.
829+ * @param write_streams Array of streams to monitor for write events, or NULL
830+ * if not monitoring for write events. Modified to indicate
831+ * which streams are ready for writing.
832+ * @param except_streams Array of streams to monitor for exception events, or NULL
833+ * if not monitoring for exceptions. Modified to indicate
834+ * which streams have exceptions.
835+ * @param tv Timeout specification, or NULL for infinite timeout.
836+ * Specifies maximum time to wait for events.
837+ *
838+ * @return On success, returns the number of streams that are
839+ * ready for I/O. Returns 0 if the timeout expired with no
840+ * events. Returns -1 on error, with errno set appropriately:
841+ * - EINVAL: Not called from async context or invalid input
842+ * - ENOMEM: Memory allocation failure
843+ * - EINTR: Operation interrupted
844+ * - ECANCELED: Operation was cancelled
845+ * - ETIMEDOUT: Operation timed out
846+ *
847+ * @note This function can only be called from within an async
848+ * coroutine context. Calling from regular PHP code will
849+ * result in EINVAL error.
850+ * @note The function modifies the input stream arrays to
851+ * indicate which streams triggered events, similar to
852+ * the standard select() behavior.
853+ *
854+ * @see select(2), php_poll2_async(), php_select_async()
855+ */
856+ ZEND_API int php_stream_select_async (zval * read_streams , zval * write_streams , zval * except_streams , struct timeval * tv )
817857{
818858 zend_coroutine_t * coroutine = ZEND_ASYNC_CURRENT_COROUTINE ;
819859
@@ -827,7 +867,7 @@ ZEND_API int async_select(zval *read_streams, zval *write_streams, zval *except_
827867 // Calculate timeout in milliseconds
828868 zend_ulong timeout = 0 ;
829869 if (tv != NULL ) {
830- timeout = (zend_ulong )( tv -> tv_sec * 1000 + tv -> tv_usec / 1000 ) ;
870+ timeout = (zend_ulong )tv -> tv_sec * 1000 + ( zend_ulong ) tv -> tv_usec / 1000 ;
831871 }
832872
833873 zend_async_waker_new_with_timeout (coroutine , timeout , NULL );
@@ -862,26 +902,33 @@ ZEND_API int async_select(zval *read_streams, zval *write_streams, zval *except_
862902 zend_hash_init (& ready_except_streams , 0 , NULL , ZVAL_PTR_DTOR , 0 );
863903
864904 // Collect ready streams from triggered_events
865- zval * callback_val ;
866- ZEND_HASH_FOREACH_VAL (coroutine -> waker -> triggered_events , callback_val ) {
867- if (Z_TYPE_P (callback_val ) == IS_PTR ) {
868- async_stream_callback_t * cb = (async_stream_callback_t * )Z_PTR_P (callback_val );
869- if (cb -> ready ) {
870- HashTable * target = NULL ;
871- if (cb -> stream_type & ASYNC_READABLE ) {
872- target = & ready_read_streams ;
873- } else if (cb -> stream_type & ASYNC_WRITABLE ) {
874- target = & ready_write_streams ;
875- } else if (cb -> stream_type & ASYNC_PRIORITIZED ) {
876- target = & ready_except_streams ;
905+ async_stream_callback_t * cb ;
906+ ZEND_HASH_FOREACH_PTR (coroutine -> waker -> triggered_events , cb ) {
907+ if (cb -> ready ) {
908+ HashTable * target = NULL ;
909+ if (cb -> event -> triggered_events & ASYNC_READABLE ) {
910+ zval * dest_elem = zend_hash_next_index_insert_ptr (& ready_read_streams , cb -> stream );
911+ if (dest_elem ) {
912+ zval_add_ref (dest_elem );
913+ } else {
914+ zend_throw_exception (NULL , "Failed to insert ready read stream" , 0 );
915+ goto error ;
916+ }
917+ } else if (cb -> event -> triggered_events & ASYNC_WRITABLE ) {
918+ zval * dest_elem = zend_hash_next_index_insert_ptr (& ready_read_streams , cb -> stream );
919+ if (dest_elem ) {
920+ zval_add_ref (dest_elem );
921+ } else {
922+ zend_throw_exception (NULL , "Failed to insert ready write stream" , 0 );
923+ goto error ;
877924 }
878-
879- if ( target != NULL ) {
880- zval * stream_elem = cb -> stream ;
881- zval * dest_elem = zend_hash_next_index_insert ( target , stream_elem );
882- if ( dest_elem ) {
883- zval_add_ref ( dest_elem );
884- }
925+ } else if ( cb -> event -> triggered_events & ASYNC_PRIORITIZED ) {
926+ zval * dest_elem = zend_hash_next_index_insert_ptr ( & ready_read_streams , cb -> stream );
927+ if ( dest_elem ) {
928+ zval_add_ref ( dest_elem );
929+ } else {
930+ zend_throw_exception ( NULL , "Failed to insert ready except stream" , 0 );
931+ goto error ;
885932 }
886933 }
887934 }
0 commit comments