Skip to content

int|float for sleep #13401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
50 changes: 44 additions & 6 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -1113,18 +1113,56 @@ PHP_FUNCTION(flush)
/* {{{ Delay for a given number of seconds */
PHP_FUNCTION(sleep)
{
zend_long num;
zval* num;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(num)
Z_PARAM_NUMBER(num)
ZEND_PARSE_PARAMETERS_END();

if (num < 0) {
if (Z_TYPE_P(num) == IS_DOUBLE) {
const double seconds = Z_DVAL_P(num);
if (UNEXPECTED(seconds < 0)) {
zend_argument_value_error(1, "must be greater than or equal to 0");
RETURN_THROWS();
}
#ifdef HAVE_NANOSLEEP
time_t seconds_long = (time_t)seconds;
zend_long fraction_nanoseconds = (zend_long)((seconds - seconds_long) * 1000000000);
if (fraction_nanoseconds > 999999999) {
// for this to happen, you have to request to sleep for longer than
// 0.999999999 seconds and yet less than 1 second..
// nanosleep() has a documented limit of 999999999 nanoseconds, so let's just round it up to 1 second.
// that means we'll be off by <=0.9 nanoseconds in this edge-case, probably close enough.
fraction_nanoseconds = 0;
seconds_long += 1;
}
struct timespec php_req, php_rem;
php_req.tv_sec = (time_t)seconds_long;
php_req.tv_nsec = fraction_nanoseconds;
const int result = nanosleep(&php_req, &php_rem);
if (UNEXPECTED(result == -1)) {
ZEND_ASSERT(errno != EINVAL); // this should be impossible, we carefully checked the input above
// it's probably EINTR
RETURN_DOUBLE(php_rem.tv_sec + (((double)php_rem.tv_nsec) / 1000000000.0));
}
RETURN_LONG(0);
#elif defined(HAVE_USLEEP)
const unsigned int fraction_microseconds = (unsigned int)((seconds - (unsigned int)seconds) * 1000000);
if (fraction_microseconds > 0) {
usleep(fraction_microseconds);
}
RETURN_LONG(php_sleep((unsigned int)seconds));
#else
// avoid -Werror=unreachable-code
RETURN_LONG(php_sleep((unsigned int)seconds));
#endif
}
ZEND_ASSERT(Z_TYPE_P(num) == IS_LONG); // Z_PARAM_NUMBER(num) above guarantee that it's double or float or throw :)
zend_long seconds = Z_LVAL_P(num);
if (UNEXPECTED(seconds < 0)) {
zend_argument_value_error(1, "must be greater than or equal to 0");
RETURN_THROWS();
}

RETURN_LONG(php_sleep((unsigned int)num));
RETURN_LONG(php_sleep((unsigned int)seconds));
}
/* }}} */

Expand Down
2 changes: 1 addition & 1 deletion ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -1966,7 +1966,7 @@ function getopt(string $short_options, array $long_options = [], &$rest_index =

function flush(): void {}

function sleep(int $seconds): int {}
function sleep(int|float $seconds): int|float {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accepting float should be enough, int is accepted by float type even with strict types mode


function usleep(int $microseconds): void {}

Expand Down
4 changes: 2 additions & 2 deletions ext/standard/basic_functions_arginfo.h

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

32 changes: 32 additions & 0 deletions ext/standard/tests/general_functions/sleep_basic.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
<?php
echo "*** Testing sleep() : basic functionality ***\n";

function have_usleep() {
// if usleep is not avaiable on the native platform,
// usleep() calls will just do nothing
// so to check if we have usleep at runtime, we need to sleep for a short time
$t = microtime(true);
usleep(10000);
return microtime(true) - $t >= 0.009; // 0.009 is 9ms, we asked usleep to sleep for 10ms
}


$sleeptime = 1; // sleep for 1 seconds

set_time_limit(20);
Expand All @@ -31,9 +41,31 @@ if ($time >= $sleeplow) {
} else {
echo "TEST FAILED - time is {$time} secs and sleep was {$sleeptime} secs\n";
}
if (!have_usleep()) {
// ¯\_(ツ)_/¯
echo "Fractional sleep return value: 0\n";
echo "FRACTIONAL SLEEP TEST PASSED\n";
} else {
$time = microtime(true);
$result = sleep(0.1);
$time = microtime(true) - $time;
echo "Fractional sleep return value: " . $result . "\n";
if ($time >= 0.09) {
echo "FRACTIONAL SLEEP TEST PASSED\n";
} else {
var_dump([
'time' => $time,
'result' => $result,
'expected_time' => 0.1
]);
echo "FRACTIONAL SLEEP TEST FAILED\n";
}
}
?>
--EXPECTF--
*** Testing sleep() : basic functionality ***
Thread slept for %f seconds
Return value: 0
TEST PASSED
Fractional sleep return value: 0
FRACTIONAL SLEEP TEST PASSED