Skip to content
Open
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 NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ PHP NEWS
sockets. (David Carlier)

- Standard:
. Dispatch asynchronous signal handlers while time_sleep_until() waits.
(Nicolas Grekas)
. Fixed bug #60110 (fclose(), file_put_contents(), copy() do not return false
properly). (Jakub Zelenka, Ilija Tovilo)
. Fixed three Windows-only proc_open() defects: an uninitialized
Expand Down
5 changes: 5 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ PHP 8.7 UPGRADE NOTES
application terminate one attribute and append further ones.

- Standard:
. With pcntl_async_signals(true), time_sleep_until() now runs signal
handlers before the sleep ends. Previously, handlers waited until the
deadline. If a handler returns normally, the sleep resumes until the
original deadline. A handler can now interrupt the sleep by throwing an
exception or calling exit.
. The number of filters that can be chained in a php://filter URL is limited
to 16 by default. Set the stream context option max_filter_count to change
this.
Expand Down
25 changes: 19 additions & 6 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "ext/standard/info.h"
#include "ext/session/php_session.h"
#include "zend_exceptions.h"
#include "zend_hrtime.h"
#include "zend_ini.h"
#include "zend_operators.h"
#include "ext/standard/php_dns.h"
Expand Down Expand Up @@ -1206,8 +1207,9 @@ PHP_FUNCTION(time_sleep_until)
{
double target_secs;
struct timeval tm;
struct timespec php_req, php_rem;
struct timespec php_req;
uint64_t current_ns, target_ns, diff_ns;
zend_hrtime_t start;
const uint64_t ns_per_sec = 1000000000;
const double top_target_sec = (double)(UINT64_MAX / ns_per_sec);

Expand All @@ -1218,6 +1220,7 @@ PHP_FUNCTION(time_sleep_until)
if (gettimeofday((struct timeval *) &tm, NULL) != 0) {
RETURN_FALSE;
}
start = zend_hrtime();

if (UNEXPECTED(!(target_secs >= 0 && target_secs <= top_target_sec))) {
zend_argument_value_error(1, "must be between 0 and %" PRIu64, (uint64_t)top_target_sec);
Expand All @@ -1235,13 +1238,23 @@ PHP_FUNCTION(time_sleep_until)
php_req.tv_sec = (time_t) (diff_ns / ns_per_sec);
php_req.tv_nsec = (long) (diff_ns % ns_per_sec);

while (nanosleep(&php_req, &php_rem)) {
if (errno == EINTR) {
php_req.tv_sec = php_rem.tv_sec;
php_req.tv_nsec = php_rem.tv_nsec;
} else {
while (nanosleep(&php_req, NULL)) {
if (errno != EINTR) {
RETURN_FALSE;
}
if (UNEXPECTED(atomic_load(&EG(vm_interrupt)))) {
zend_fcall_interrupt(execute_data);
if (UNEXPECTED(EG(exception))) {
RETURN_THROWS();
}
}
zend_hrtime_t elapsed = zend_hrtime() - start;
if (elapsed >= diff_ns) {
break;
}
uint64_t remaining_ns = diff_ns - elapsed;
php_req.tv_sec = (time_t) (remaining_ns / ns_per_sec);
php_req.tv_nsec = (long) (remaining_ns % ns_per_sec);
}

RETURN_TRUE;
Expand Down
30 changes: 30 additions & 0 deletions ext/standard/tests/misc/time_sleep_until_async_signals.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
time_sleep_until() dispatches async signals and keeps the original deadline
--EXTENSIONS--
pcntl
--SKIPIF--
<?php
if (!function_exists('time_sleep_until')) die('skip time_sleep_until() not available');
if (getenv('SKIP_SLOW_TESTS')) die('skip slow test');
?>
--FILE--
<?php
pcntl_async_signals(true);
$deadline = microtime(true) + 2;
$calledBeforeDeadline = false;
pcntl_signal(SIGALRM, static function () use ($deadline, &$calledBeforeDeadline): void {
$calledBeforeDeadline = microtime(true) < $deadline;
time_sleep_until($deadline + 0.1);
});

pcntl_alarm(1);
var_dump(time_sleep_until($deadline));
var_dump($calledBeforeDeadline);
var_dump(microtime(true) >= $deadline);
var_dump(microtime(true) < $deadline + 0.8);
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
An async signal handler can throw during time_sleep_until()
--EXTENSIONS--
pcntl
--SKIPIF--
<?php
if (!function_exists('time_sleep_until')) die('skip time_sleep_until() not available');
if (getenv('SKIP_SLOW_TESTS')) die('skip slow test');
?>
--FILE--
<?php
pcntl_async_signals(true);
$deadline = microtime(true) + 2;
pcntl_signal(SIGALRM, static function () use ($deadline): void {
var_dump(microtime(true) < $deadline);
throw new Exception('Interrupted');
});

pcntl_alarm(1);
try {
time_sleep_until($deadline);
echo "Sleep completed\n";
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
bool(true)
Interrupted
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
An async signal handler can exit during time_sleep_until()
--EXTENSIONS--
pcntl
--SKIPIF--
<?php
if (!function_exists('time_sleep_until')) die('skip time_sleep_until() not available');
if (getenv('SKIP_SLOW_TESTS')) die('skip slow test');
?>
--FILE--
<?php
pcntl_async_signals(true);
$deadline = microtime(true) + 2;
pcntl_signal(SIGALRM, static function () use ($deadline): void {
var_dump(microtime(true) < $deadline);
exit;
});

pcntl_alarm(1);
time_sleep_until($deadline);
echo "Sleep completed\n";
?>
--EXPECT--
bool(true)
27 changes: 27 additions & 0 deletions ext/standard/tests/misc/time_sleep_until_manual_signals.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
time_sleep_until() leaves manually dispatched signals pending
--EXTENSIONS--
pcntl
--SKIPIF--
<?php
if (!function_exists('time_sleep_until')) die('skip time_sleep_until() not available');
if (getenv('SKIP_SLOW_TESTS')) die('skip slow test');
?>
--FILE--
<?php
pcntl_async_signals(false);
$called = false;
pcntl_signal(SIGALRM, static function () use (&$called): void {
$called = true;
});

pcntl_alarm(1);
var_dump(time_sleep_until(microtime(true) + 2));
var_dump($called);
pcntl_signal_dispatch();
var_dump($called);
?>
--EXPECT--
bool(true)
bool(false)
bool(true)
Loading