Skip to content

Execute signal handlers before restarting syscalls#22538

Draft
arnaud-lb wants to merge 13 commits into
php:masterfrom
arnaud-lb:signal-restart
Draft

Execute signal handlers before restarting syscalls#22538
arnaud-lb wants to merge 13 commits into
php:masterfrom
arnaud-lb:signal-restart

Conversation

@arnaud-lb

@arnaud-lb arnaud-lb commented Jul 1, 2026

Copy link
Copy Markdown
Member

This PoCs the ideas discussed in #5521:

First and foremost, the whole concept of "restart syscalls" IMHO doesn't really work in PHP right now. The idea behind SA_RESTART is that the signal handler gets executed and then the syscall is restarted. However, because in PHP (at least as far as pcntl signals are concerned) the signal handler only sets a flag and the actual PHP-level callback is only executed on VM reentry, this means that "restart syscalls" really means "delay syscalls until the end of I/O operations", which is pretty useless. To use something like SIGALARM this effectively means that you need to disable syscall restarting, which also means that you need to handle this all over the place, which most code is not prepared to do.

The correct approach to handle that is (as the PEP outlines) disable SA_RESTART and manually implement restart on EINTR, but with an explicit call to the interrupt handler. This implements how C-level SA_RESTART behaves in PHP.

Syscalls are interrupted when:

  • Any signal handler returns SignalResult::Interrupt
  • Any signal handler returns SignalResult::Default or no value, and was registered with $restart_syscall=false
  • Any signal handler throws an exception
  • EG(timed_out) is set

Otherwise, syscalls are restarted.

Implemented only in php_sockop_* for now, but the goal would be to implement this for all syscalls eventually, and at least for existing EINTR loops. (Edit: Since we are clearing SA_RESTART now, syscalls that would be restarted by the OS before are not restarted anymore. So we may have to implement restarting for all syscalls immediately.)

API changes:

New enum:

namespace Pcntl;

enum SignalReturn {
    case Default;   /* Use pcntl_signal()'s $restart_syscall */
    case Interrupt; /* Interrupt the syscall */
};

pcntl_signal() changes: The $handler function must now return a SignalReturn, NULL, or no value. Before, the return value was ignored.

Some considerations:
  • Some signal handlers may already be pending before performing a syscall, so we have to check the queue before that
  • Operations with a timeout should use a deadline, as restarting will extend the total time (not implemented)
  • Syscalls executed by a signal handler may need to execute other signal handlers, if new signals are delivered (not implemented)
Exceptions vs return value:

Returning a value is more PHP-ish than throwing an exception, but exceptions have some benefits in this context:

  • Returning SignalResult::Interrupt from a handler does nothing outside of syscalls
  • Exceptions will propagate to parent signal handlers by default (see above)
BC breaks:
  • Returning anything but NULL or SignalResult from a signal handler will throw a TypeError
Demo:
$pair = @stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
[$read, $write] = $pair;

pcntl_signal(SIGINT, function () {
    echo "received SIGINT (Ctrl-C)\n";
    return Pcntl\SignalReturn::Interrupt;
});

var_dump(fread($read, 1));

echo "Done\n";

If a SIGINT (Ctrl-C) is delivered during the fread() call, the signal handler is executed immediately to decide whether the syscall should be restarted or not. In this case the handler decides to not restart, so the fread() call returns.

Previously, the signal would be queued and fread() would hang indefinitely.

TODO:
  • Use a deadline
    • Use a monotonic clock
  • Allow nested signal delivery
  • Restart handling in main/streams/xp_socket.c
  • Restart handling for remaining syscalls

@arnaud-lb arnaud-lb mentioned this pull request Jul 3, 2026
25 tasks
@bwoebi

bwoebi commented Jul 3, 2026

Copy link
Copy Markdown
Member

This is a pretty reasonable proposal and I will support this. Thank you for picking this up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants