Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down
7 changes: 7 additions & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) /* {{{ */
Expand Down
67 changes: 57 additions & 10 deletions ext/pcntl/pcntl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -252,6 +261,8 @@ PHP_RSHUTDOWN_FUNCTION(pcntl)
efree(sig);
}

efree(PCNTL_G(restart_syscalls));

return SUCCESS;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1351,25 +1370,27 @@ 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 */
sigfillset(&mask);
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 */
Expand All @@ -1382,20 +1403,34 @@ 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) {
ZVAL_LONG(&params[0], queue->signo);
array_init(&params[1]);
pcntl_siginfo_to_zval(queue->signo, &queue->siginfo, &params[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(&params[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;
}
}
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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;
}
6 changes: 6 additions & 0 deletions ext/pcntl/pcntl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 12 additions & 1 deletion ext/pcntl/pcntl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions ext/pcntl/pcntl_decl.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ext/pcntl/php_pcntl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions ext/pcntl/tests/signal_return_interrupt_pending.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
SignalReturn::Interrupt with pending signal interrupts socket read before poll
--EXTENSIONS--
pcntl
posix
--SKIPIF--
<?php
$pair = @stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
if (!$pair) die("skip stream_socket_pair() not available");
?>
--FILE--
<?php
// Signal handler returns Interrupt; signal is sent to self and is pending
// (not yet dispatched) when fread() is called. The top-of-function check in
// php_sockop_read dispatches pending signals before blocking; since the handler
// returns Interrupt it returns -1 immediately instead of entering poll().
pcntl_signal(SIGUSR1, function (int $signo): Pcntl\SignalReturn {
echo "handler\n";
return Pcntl\SignalReturn::Interrupt;
}, restart_syscalls: true); // restart_syscalls=true, but handler overrides

[$read, $write] = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
stream_set_blocking($read, true);

// Signal is now pending; the PHP handler has not run yet.
posix_kill(posix_getpid(), SIGUSR1);

// fread triggers zend_signal_interrupt_function at the top of php_sockop_read,
// dispatches the pending signal, and returns false immediately.
$result = fread($read, 1024);
var_dump($result);

fclose($read);
fclose($write);
?>
--EXPECT--
handler
bool(false)
Loading
Loading