diff --git a/Zend/zend.c b/Zend/zend.c index 9411b92a2018..432c5ba876b9 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -88,6 +88,7 @@ ZEND_API FILE *(*zend_fopen)(zend_string *filename, zend_string **opened_path); ZEND_API zend_result (*zend_stream_open_function)(zend_file_handle *handle); ZEND_API void (*zend_ticks_function)(int ticks); ZEND_API void (*zend_interrupt_function)(zend_execute_data *execute_data); +ZEND_API zend_signal_interrupt_result (*zend_signal_interrupt_function)(void); ZEND_API void (*zend_error_cb)(int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message); void (*zend_printf_to_smart_string)(smart_string *buf, const char *format, va_list ap); void (*zend_printf_to_smart_str)(smart_str *buf, const char *format, va_list ap); @@ -969,6 +970,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ zend_resolve_path = utility_functions->resolve_path_function; zend_interrupt_function = NULL; + zend_signal_interrupt_function = zend_signal_interrupt; #ifdef HAVE_DTRACE /* build with dtrace support */ diff --git a/Zend/zend.h b/Zend/zend.h index 0d5303192b57..0bbf31158c57 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -364,6 +364,13 @@ extern ZEND_API void (*zend_ticks_function)(int ticks); */ extern ZEND_API void (*zend_interrupt_function)(zend_execute_data *execute_data); +typedef enum { + ZEND_SIGNAL_RESTART, + ZEND_SIGNAL_INTERRUPT, +} zend_signal_interrupt_result; + +extern ZEND_API zend_signal_interrupt_result (*zend_signal_interrupt_function)(void); + extern ZEND_API void (*zend_error_cb)(int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message); extern ZEND_API void (*zend_on_timeout)(int seconds); extern ZEND_API zend_result (*zend_stream_open_function)(zend_file_handle *handle); diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index ba48b19bcfe1..90fae09b7ae5 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -473,6 +473,8 @@ ZEND_API zend_class_entry *zend_get_executed_scope(void); ZEND_API bool zend_is_executing(void); ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_cannot_pass_by_reference(uint32_t arg_num); +ZEND_API zend_signal_interrupt_result zend_signal_interrupt(void); + ZEND_API void zend_set_timeout(zend_long seconds, bool reset_signals); ZEND_API void zend_unset_timeout(void); ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 71e0c56a51c8..9404e5e57093 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1419,6 +1419,15 @@ ZEND_API zend_result zend_eval_string_ex(const char *str, zval *retval_ptr, cons } /* }}} */ +ZEND_API zend_signal_interrupt_result zend_signal_interrupt(void) +{ + if (zend_atomic_bool_load_ex(&EG(timed_out))) { + return ZEND_SIGNAL_INTERRUPT; + } + + return ZEND_SIGNAL_RESTART; +} + static void zend_set_timeout_ex(zend_long seconds, bool reset_signals); ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */ diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index 794e75a1716e..63a732adb202 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -148,11 +148,14 @@ typedef psetid_t cpu_set_t; #define LONG_CONST(c) (zend_long) c +#include "Zend/zend_bitset.h" #include "Zend/zend_enum.h" #include "Zend/zend_max_execution_timer.h" #include "pcntl_arginfo.h" #include "pcntl_decl.h" + +static zend_class_entry *SignalReturn_ce; static zend_class_entry *QosClass_ce; ZEND_DECLARE_MODULE_GLOBALS(pcntl) @@ -183,12 +186,14 @@ ZEND_GET_MODULE(pcntl) #endif static void (*orig_interrupt_function)(zend_execute_data *execute_data); +static zend_signal_interrupt_result (*orig_signal_interrupt_function)(void); static void pcntl_signal_handler(int, siginfo_t*, void*); static void pcntl_siginfo_to_zval(int, siginfo_t*, zval*); -static void pcntl_signal_dispatch(void); +static zend_signal_interrupt_result pcntl_signal_dispatch(void); static void pcntl_signal_dispatch_tick_function(int dummy_int, void *dummy_pointer); static void pcntl_interrupt_function(zend_execute_data *execute_data); +static zend_signal_interrupt_result pcntl_signal_interrupt_function(void); static PHP_GINIT_FUNCTION(pcntl) { @@ -213,15 +218,19 @@ PHP_RINIT_FUNCTION(pcntl) PCNTL_G(num_signals) = SIGRTMAX + 1; } #endif + PCNTL_G(restart_syscalls) = ecalloc(zend_bitset_len(PCNTL_G(num_signals)), ZEND_BITSET_ELM_SIZE); return SUCCESS; } PHP_MINIT_FUNCTION(pcntl) { + SignalReturn_ce = register_class_Pcntl_SignalReturn(); QosClass_ce = register_class_Pcntl_QosClass(); register_pcntl_symbols(module_number); orig_interrupt_function = zend_interrupt_function; zend_interrupt_function = pcntl_interrupt_function; + orig_signal_interrupt_function = zend_signal_interrupt_function; + zend_signal_interrupt_function = pcntl_signal_interrupt_function; return SUCCESS; } @@ -252,6 +261,8 @@ PHP_RSHUTDOWN_FUNCTION(pcntl) efree(sig); } + efree(PCNTL_G(restart_syscalls)); + return SUCCESS; } @@ -850,13 +861,21 @@ PHP_FUNCTION(pcntl_signal) RETURN_THROWS(); } - /* Register with the OS first so that on failure we don't record a handler that was never installed */ - if (php_signal4(signo, pcntl_signal_handler, (int) restart_syscalls, 1) == (void *)SIG_ERR) { + /* Register with the OS first so that on failure we don't record a handler that was never installed. + * Always clear SA_RESTART so that interrupted syscalls return EINTR; zend_signal_interrupt_function + * decides whether to restart based on the handler return value and restart_syscalls. */ + if (php_signal4(signo, pcntl_signal_handler, false, 1) == (void *)SIG_ERR) { PCNTL_G(last_error) = errno; php_error_docref(NULL, E_WARNING, "Error assigning signal"); RETURN_FALSE; } + if (restart_syscalls) { + zend_bitset_incl(PCNTL_G(restart_syscalls), signo); + } else { + zend_bitset_excl(PCNTL_G(restart_syscalls), signo); + } + /* Add the function name to our signal table */ handle = zend_hash_index_update(&PCNTL_G(php_signal_table), signo, handle); Z_TRY_ADDREF_P(handle); @@ -1351,15 +1370,16 @@ static void pcntl_signal_handler(int signo, siginfo_t *siginfo, void *context) } } -void pcntl_signal_dispatch(void) +zend_signal_interrupt_result pcntl_signal_dispatch(void) { zval params[2], *handle, retval; struct php_pcntl_pending_signal *queue, *next; sigset_t mask; sigset_t old_mask; + bool interrupt = false; if(!PCNTL_G(pending_signals)) { - return; + return ZEND_SIGNAL_RESTART; } /* Mask all signals */ @@ -1367,9 +1387,10 @@ void pcntl_signal_dispatch(void) sigprocmask(SIG_BLOCK, &mask, &old_mask); /* Bail if the queue is empty or if we are already playing the queue */ + // TODO: For the purpose of EINTR handling, processing next signals here would be useful if (!PCNTL_G(head) || PCNTL_G(processing_signal_queue)) { sigprocmask(SIG_SETMASK, &old_mask, NULL); - return; + return ZEND_SIGNAL_RESTART; } /* Prevent switching fibers when handling signals */ @@ -1382,7 +1403,6 @@ void pcntl_signal_dispatch(void) PCNTL_G(head) = NULL; /* simple stores are atomic */ PCNTL_G(tail) = NULL; - /* Allocate */ while (queue) { if ((handle = zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo)) != NULL) { if (Z_TYPE_P(handle) != IS_LONG) { @@ -1390,12 +1410,27 @@ void pcntl_signal_dispatch(void) array_init(¶ms[1]); pcntl_siginfo_to_zval(queue->signo, &queue->siginfo, ¶ms[1]); - /* Call php signal handler - Note that we do not report errors, and we ignore the return value */ call_user_function(NULL, NULL, handle, &retval, 2, params); - zval_ptr_dtor(&retval); zval_ptr_dtor(¶ms[1]); + if (Z_TYPE(retval) == IS_OBJECT && Z_OBJCE(retval) == SignalReturn_ce) { + if (zend_enum_fetch_case_id(Z_OBJ(retval)) == ZEND_ENUM_Pcntl_SignalReturn_Interrupt) { + interrupt = true; + } else if (!zend_bitset_in(PCNTL_G(restart_syscalls), queue->signo)) { + interrupt = true; + } + } else if (Z_TYPE(retval) > IS_NULL) { + zend_type_error("Signal handler must return a Pcntl\\SignalReturn or no value, %s returned", + zend_zval_value_name(&retval)); + interrupt = true; + } else if (!zend_bitset_in(PCNTL_G(restart_syscalls), queue->signo)) { + interrupt = true; + } + + zval_ptr_dtor(&retval); + if (EG(exception)) { + interrupt = true; break; } } @@ -1425,11 +1460,13 @@ void pcntl_signal_dispatch(void) /* return signal mask to previous state */ sigprocmask(SIG_SETMASK, &old_mask, NULL); + + return interrupt ? ZEND_SIGNAL_INTERRUPT : ZEND_SIGNAL_RESTART; } static void pcntl_signal_dispatch_tick_function(int dummy_int, void *dummy_pointer) { - return pcntl_signal_dispatch(); + pcntl_signal_dispatch(); } /* {{{ Enable/disable asynchronous signal handling and return the old setting. */ @@ -1908,3 +1945,13 @@ static void pcntl_interrupt_function(zend_execute_data *execute_data) orig_interrupt_function(execute_data); } } + +static zend_signal_interrupt_result pcntl_signal_interrupt_function(void) +{ + zend_signal_interrupt_result result = pcntl_signal_dispatch(); + if (orig_signal_interrupt_function() == ZEND_SIGNAL_INTERRUPT) { + result = ZEND_SIGNAL_INTERRUPT; + } + + return result; +} diff --git a/ext/pcntl/pcntl.stub.php b/ext/pcntl/pcntl.stub.php index 4a4b8fe86931..8e71457c6865 100644 --- a/ext/pcntl/pcntl.stub.php +++ b/ext/pcntl/pcntl.stub.php @@ -1112,6 +1112,12 @@ function pcntl_setqos_class(Pcntl\QosClass $qos_class = Pcntl\QosClass::Default) namespace Pcntl { + enum SignalReturn + { + case Default; + case Interrupt; + } + enum QosClass { case UserInteractive; diff --git a/ext/pcntl/pcntl_arginfo.h b/ext/pcntl/pcntl_arginfo.h index 2da7c8ad5db8..0d12b7a1f052 100644 --- a/ext/pcntl/pcntl_arginfo.h +++ b/ext/pcntl/pcntl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit pcntl.stub.php instead. - * Stub hash: 04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826 + * Stub hash: 2be6f0046170cad02be1d01227b749412af7b51f * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_fork, 0, 0, IS_LONG, 0) @@ -662,6 +662,17 @@ static void register_pcntl_symbols(int module_number) #endif } +static zend_class_entry *register_class_Pcntl_SignalReturn(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum("Pcntl\\SignalReturn", IS_UNDEF, NULL); + + zend_enum_add_case_cstr(class_entry, "Default", NULL); + + zend_enum_add_case_cstr(class_entry, "Interrupt", NULL); + + return class_entry; +} + static zend_class_entry *register_class_Pcntl_QosClass(void) { zend_class_entry *class_entry = zend_register_internal_enum("Pcntl\\QosClass", IS_UNDEF, NULL); diff --git a/ext/pcntl/pcntl_decl.h b/ext/pcntl/pcntl_decl.h index 7f8e5172cedb..8efc8f4d69ca 100644 --- a/ext/pcntl/pcntl_decl.h +++ b/ext/pcntl/pcntl_decl.h @@ -1,8 +1,13 @@ /* This is a generated file, edit pcntl.stub.php instead. - * Stub hash: 04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826 */ + * Stub hash: 2be6f0046170cad02be1d01227b749412af7b51f */ -#ifndef ZEND_PCNTL_DECL_04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826_H -#define ZEND_PCNTL_DECL_04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826_H +#ifndef ZEND_PCNTL_DECL_2be6f0046170cad02be1d01227b749412af7b51f_H +#define ZEND_PCNTL_DECL_2be6f0046170cad02be1d01227b749412af7b51f_H + +typedef enum zend_enum_Pcntl_SignalReturn { + ZEND_ENUM_Pcntl_SignalReturn_Default = 1, + ZEND_ENUM_Pcntl_SignalReturn_Interrupt = 2, +} zend_enum_Pcntl_SignalReturn; typedef enum zend_enum_Pcntl_QosClass { ZEND_ENUM_Pcntl_QosClass_UserInteractive = 1, @@ -12,4 +17,4 @@ typedef enum zend_enum_Pcntl_QosClass { ZEND_ENUM_Pcntl_QosClass_Background = 5, } zend_enum_Pcntl_QosClass; -#endif /* ZEND_PCNTL_DECL_04e7b30c6fb23cf6ce6bc26fe094fd5b4dbfe826_H */ +#endif /* ZEND_PCNTL_DECL_2be6f0046170cad02be1d01227b749412af7b51f_H */ diff --git a/ext/pcntl/php_pcntl.h b/ext/pcntl/php_pcntl.h index aaf1ea0ad473..64d2e0ac6824 100644 --- a/ext/pcntl/php_pcntl.h +++ b/ext/pcntl/php_pcntl.h @@ -15,6 +15,7 @@ #ifndef PHP_PCNTL_H #define PHP_PCNTL_H +#include "Zend/zend_bitset.h" #include "pcntl_decl.h" #if defined(HAVE_DECL_WCONTINUED) && HAVE_DECL_WCONTINUED == 1 && defined(HAVE_WIFCONTINUED) @@ -48,6 +49,7 @@ ZEND_BEGIN_MODULE_GLOBALS(pcntl) uint16_t num_signals; int last_error; struct php_pcntl_pending_signal *head, *tail, *spares; + zend_bitset restart_syscalls; ZEND_END_MODULE_GLOBALS(pcntl) #if defined(ZTS) && defined(COMPILE_DL_PCNTL) diff --git a/ext/pcntl/tests/signal_return_interrupt_pending.phpt b/ext/pcntl/tests/signal_return_interrupt_pending.phpt new file mode 100644 index 000000000000..cbc795d5de57 --- /dev/null +++ b/ext/pcntl/tests/signal_return_interrupt_pending.phpt @@ -0,0 +1,38 @@ +--TEST-- +SignalReturn::Interrupt with pending signal interrupts socket read before poll +--EXTENSIONS-- +pcntl +posix +--SKIPIF-- + +--FILE-- + +--EXPECT-- +handler +bool(false) diff --git a/ext/pcntl/tests/signal_return_interrupt_syscall.phpt b/ext/pcntl/tests/signal_return_interrupt_syscall.phpt new file mode 100644 index 000000000000..a104f3464410 --- /dev/null +++ b/ext/pcntl/tests/signal_return_interrupt_syscall.phpt @@ -0,0 +1,49 @@ +--TEST-- +SignalReturn::Default with restart_syscalls=false interrupts a blocking socket read +--EXTENSIONS-- +pcntl +posix +--SKIPIF-- + +--FILE-- + +--EXPECT-- +handler +string(0) "" +bool(true) diff --git a/ext/pcntl/tests/signal_return_restart_syscall.phpt b/ext/pcntl/tests/signal_return_restart_syscall.phpt new file mode 100644 index 000000000000..9690fba07ff5 --- /dev/null +++ b/ext/pcntl/tests/signal_return_restart_syscall.phpt @@ -0,0 +1,47 @@ +--TEST-- +SignalReturn::Default with restart_syscalls=true restarts a blocking socket read +--EXTENSIONS-- +pcntl +posix +--SKIPIF-- + +--FILE-- + +--EXPECT-- +handler +string(6) "hello +" diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index d18b0e901957..e5a8aa3d9eeb 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -127,13 +127,14 @@ static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t coun return didwrite; } -static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock, bool has_buffered_data) +/* Returns true if the socket is ready for reading (poll returned > 0), false otherwise. */ +static bool php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock, bool has_buffered_data) { int retval; struct timeval *ptimeout, zero_timeout; if (!sock || sock->socket == -1) { - return; + return false; } sock->timeout_event = false; @@ -160,7 +161,12 @@ static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data if (php_socket_errno() != EINTR) break; + + if (zend_signal_interrupt_function() != ZEND_SIGNAL_RESTART) + break; } + + return retval > 0; } static ssize_t php_sockop_read(php_stream *stream, char *buf, size_t count) @@ -171,6 +177,11 @@ static ssize_t php_sockop_read(php_stream *stream, char *buf, size_t count) return -1; } +restart: + if (zend_signal_interrupt_function() == ZEND_SIGNAL_INTERRUPT) { + return -1; + } + int recv_flags = 0; /* Special handling for blocking read. */ if (sock->is_blocked) { @@ -187,19 +198,30 @@ static ssize_t php_sockop_read(php_stream *stream, char *buf, size_t count) /* If the wait is needed or it is a platform without MSG_DONTWAIT support (e.g. Windows), * then poll for data. */ if (!dont_wait || MSG_DONTWAIT == 0) { - php_sock_stream_wait_for_data(stream, sock, has_buffered_data); + bool data_ready = php_sock_stream_wait_for_data(stream, sock, has_buffered_data); if (sock->timeout_event) { /* It is ok to timeout if there is any data buffered so return 0, otherwise -1. */ return has_buffered_data ? 0 : -1; } + /* If poll was interrupted (signal) and recv would block (no MSG_DONTWAIT), + * skip the recv() to avoid blocking indefinitely. */ + if (!data_ready && recv_flags == 0) { + return -1; + } } } ssize_t nr_bytes = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(count), recv_flags); int err = php_socket_errno(); + if (nr_bytes < 0 && err == EINTR) { + goto restart; + } if (nr_bytes < 0) { - if (PHP_IS_TRANSIENT_ERROR(err)) { + if (err == EINTR) { + /* Signal interrupt: don't treat as EOF, just return -1. */ + return -1; + } else if (PHP_IS_TRANSIENT_ERROR(err)) { nr_bytes = 0; } else { stream->eof = 1;