Skip to content

Commit dffe371

Browse files
Make PHP usleep() and time_nanosleep() alertable on Windows
1 parent d4de18c commit dffe371

3 files changed

Lines changed: 95 additions & 2 deletions

File tree

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ PHP NEWS
9191
file fails after it was already truncated. (Ilia Alshanetsky)
9292

9393
- Standard:
94+
. Fixed bug GH-24399 (usleep() and time_nanosleep() ignore queued APCs on
95+
Windows). (Nicolas Grekas)
9496
. Fixed chown() and lchown() failing to resolve user names in ZTS builds
9597
when getpwnam_r() needs a larger buffer. (Ilia Alshanetsky)
9698
. Fixed password_get_info() and password_needs_rehash() accepting malformed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
--TEST--
2+
Sleep functions process queued APCs on Windows
3+
--EXTENSIONS--
4+
ffi
5+
--SKIPIF--
6+
<?php
7+
if (PHP_OS_FAMILY !== 'Windows') die('skip Windows only');
8+
?>
9+
--INI--
10+
ffi.enable=1
11+
--FILE--
12+
<?php
13+
$ffi = FFI::cdef(<<<'C'
14+
typedef void (__stdcall *PAPCFUNC)(uintptr_t);
15+
void * __stdcall GetCurrentThread(void);
16+
unsigned long __stdcall QueueUserAPC(PAPCFUNC callback, void *thread, uintptr_t data);
17+
unsigned long __stdcall SleepEx(unsigned long milliseconds, int alertable);
18+
C, 'kernel32.dll');
19+
20+
$called = 0;
21+
$callback = static function (int $data) use (&$called): void {
22+
$called = $data;
23+
};
24+
25+
echo "usleep()\n";
26+
var_dump($ffi->QueueUserAPC($callback, $ffi->GetCurrentThread(), 1) !== 0);
27+
var_dump(usleep(1000000));
28+
var_dump($called === 1);
29+
30+
echo "time_nanosleep()\n";
31+
var_dump($ffi->QueueUserAPC($callback, $ffi->GetCurrentThread(), 2) !== 0);
32+
$remaining = time_nanosleep(2, 0);
33+
var_dump($called === 2);
34+
var_dump(is_array($remaining));
35+
if (is_array($remaining)) {
36+
var_dump($remaining['seconds'] >= 0 && $remaining['seconds'] <= 2);
37+
var_dump($remaining['nanoseconds'] >= 0 && $remaining['nanoseconds'] < 1000000000);
38+
var_dump($remaining['seconds'] > 0 || $remaining['nanoseconds'] > 0);
39+
var_dump($remaining['seconds'] < 2 || $remaining['nanoseconds'] === 0);
40+
}
41+
42+
echo "time_sleep_until()\n";
43+
var_dump($ffi->QueueUserAPC($callback, $ffi->GetCurrentThread(), 3) !== 0);
44+
$deadline = microtime(true) + 0.1;
45+
var_dump(time_sleep_until($deadline));
46+
var_dump($called === 3);
47+
var_dump(microtime(true) + 0.002 >= $deadline);
48+
49+
$ffi->SleepEx(0, 1);
50+
?>
51+
--EXPECT--
52+
usleep()
53+
bool(true)
54+
NULL
55+
bool(true)
56+
time_nanosleep()
57+
bool(true)
58+
bool(true)
59+
bool(true)
60+
bool(true)
61+
bool(true)
62+
bool(true)
63+
bool(true)
64+
time_sleep_until()
65+
bool(true)
66+
bool(true)
67+
bool(true)
68+
bool(true)

‎win32/time.c‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <mmsystem.h>
2323
#include <errno.h>
2424
#include "php_win32_globals.h"
25+
#include "zend_hrtime.h"
2526

2627
static zend_always_inline void getfilesystemtime(struct timeval *tv)
2728
{/*{{{*/
@@ -67,13 +68,18 @@ PHPAPI int usleep(unsigned int useconds)
6768
{/*{{{*/
6869
HANDLE timer;
6970
LARGE_INTEGER due;
71+
DWORD result;
7072

7173
due.QuadPart = -(10 * (__int64)useconds);
7274

7375
timer = CreateWaitableTimer(NULL, TRUE, NULL);
7476
SetWaitableTimer(timer, &due, 0, NULL, NULL, 0);
75-
WaitForSingleObject(timer, INFINITE);
77+
result = WaitForSingleObjectEx(timer, INFINITE, TRUE);
7678
CloseHandle(timer);
79+
if (result == WAIT_IO_COMPLETION) {
80+
errno = EINTR;
81+
return -1;
82+
}
7783
return 0;
7884
}/*}}}*/
7985

@@ -84,5 +90,22 @@ PHPAPI int nanosleep( const struct timespec * rqtp, struct timespec * rmtp )
8490
errno = EINVAL;
8591
return -1;
8692
}
87-
return usleep( rqtp->tv_sec * 1000000 + rqtp->tv_nsec / 1000 );
93+
zend_hrtime_t start = zend_hrtime();
94+
int result = usleep(rqtp->tv_sec * 1000000 + rqtp->tv_nsec / 1000);
95+
if (result == -1 && errno == EINTR && rmtp != NULL) {
96+
zend_hrtime_t elapsed = zend_hrtime() - start;
97+
time_t seconds = rqtp->tv_sec - (time_t) (elapsed / ZEND_NANO_IN_SEC);
98+
long nanoseconds = rqtp->tv_nsec - (long) (elapsed % ZEND_NANO_IN_SEC);
99+
if (nanoseconds < 0) {
100+
seconds--;
101+
nanoseconds += (long) ZEND_NANO_IN_SEC;
102+
}
103+
if (seconds < 0) {
104+
seconds = 0;
105+
nanoseconds = 0;
106+
}
107+
rmtp->tv_sec = seconds;
108+
rmtp->tv_nsec = nanoseconds;
109+
}
110+
return result;
88111
}/*}}}*/

0 commit comments

Comments
 (0)