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 @@ -172,6 +172,8 @@ PHP NEWS
. Fixed quoted_printable_decode() truncating input at NUL bytes. (Weilin Du)
. Fixed version_compare() silently truncating version strings at NUL bytes by
rejecting such input. (Weilin Du)
. 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 @@ -63,6 +63,11 @@ PHP 8.7 UPGRADE NOTES
. bindec(), octdec(), hexdec() and base_convert() now throw an Exception
when the passed string contains characters that are invalid for the
specified number base.
. 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
6 changes: 6 additions & 0 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,12 @@ PHP_FUNCTION(time_sleep_until)
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;
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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
time_sleep_until() resumes until its deadline after an async signal handler returns
--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);
$start = hrtime(true);
$calledAfter = null;
pcntl_signal(SIGALRM, static function () use ($start, &$calledAfter): void {
$calledAfter = hrtime(true) - $start;
});

pcntl_alarm(1);
var_dump(time_sleep_until(microtime(true) + 2));
$elapsed = hrtime(true) - $start;
// The handler ran while sleeping
var_dump($calledAfter !== null && $calledAfter < 1_500_000_000);
// The sleep went on after the handler returned (100ms of tolerance for early wakes)
var_dump($elapsed >= 1_900_000_000);
?>
--EXPECT--
bool(true)
bool(true)
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