Skip to content
Merged
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 @@ -67,6 +67,8 @@ PHP NEWS
is_link(), file_exists(), lstat(), stat(). (Girgias)
. Fixed bug GH-22818 (stream_filter_register() orphaned user_filter_map on
shutdown re-registration). (David Carlier)
. Io\Poll\Context::wait() now takes a Time\Duration object as a timeout.
(timwolla)

30 Jul 2026, PHP 8.6.0alpha3

Expand Down
35 changes: 13 additions & 22 deletions ext/standard/io_poll.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "php_poll.h"
#include "io_poll_arginfo.h"
#include "io_poll_decl.h"
#include "ext/date/php_time.h"

/* Class entries */
static zend_class_entry *php_io_poll_backend_class_entry;
Expand Down Expand Up @@ -774,38 +775,28 @@ PHP_METHOD(Io_Poll_Context, add)

PHP_METHOD(Io_Poll_Context, wait)
{
zend_long timeout_seconds = -1;
bool timeout_seconds_is_null = true;
zend_long timeout_microseconds = 0;
php_date_time_duration *timeout = NULL;
zend_long max_events = 0;
bool max_events_is_null = true;

ZEND_PARSE_PARAMETERS_START(0, 3)
ZEND_PARSE_PARAMETERS_START(0, 2)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_OR_NULL(timeout_seconds, timeout_seconds_is_null)
Z_PARAM_LONG(timeout_microseconds)
Z_PARAM_DATE_TIME_DURATION_OR_NULL(timeout)
Z_PARAM_LONG_OR_NULL(max_events, max_events_is_null)
ZEND_PARSE_PARAMETERS_END();

php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis());

/* Build timespec from seconds + microseconds, or NULL for indefinite */
struct timespec ts;
const struct timespec *timeout = NULL;
if (timeout_seconds >= 0) {
if (timeout_microseconds < 0) {
zend_argument_value_error(2, "must be greater than or equal to 0");
/* Build timespec from php_date_time_duration, or NULL for indefinite */
struct timespec timeout_ts;
if (timeout) {
if (timeout->duration.negative) {
zend_argument_value_error(2, "must not be negative");
RETURN_THROWS();
}

/* Allow microseconds >= 1000000, carry overflow into seconds
* (same behavior as stream_select) */
ts.tv_sec = (time_t) (timeout_seconds + (timeout_microseconds / 1000000));
ts.tv_nsec = (long) ((timeout_microseconds % 1000000) * 1000);
timeout = &ts;
} else if (!timeout_seconds_is_null) {
zend_argument_value_error(1, "must be greater than or equal to 0");
RETURN_THROWS();
timeout_ts.tv_sec = timeout->duration.seconds;
timeout_ts.tv_nsec = timeout->duration.nanoseconds;
}

if (max_events_is_null) {
Expand All @@ -814,12 +805,12 @@ PHP_METHOD(Io_Poll_Context, wait)
max_events = 64;
}
} else if (max_events <= 0) {
zend_argument_value_error(3, "must be greater than 0");
zend_argument_value_error(2, "must be greater than 0");
RETURN_THROWS();
}

php_poll_event *events = safe_emalloc(max_events, sizeof(*events), 0);
int num_events = php_poll_wait(intern->ctx, events, (int) max_events, timeout);
int num_events = php_poll_wait(intern->ctx, events, (int) max_events, timeout ? &timeout_ts : NULL);

if (num_events < 0) {
php_poll_error err = php_poll_get_error(intern->ctx);
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/io_poll.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function __construct(Backend $backend = Backend::Auto) {}
public function add(Handle $handle, array $events, mixed $data = null): Watcher {}

/** @return list<Watcher> */
public function wait(?int $timeoutSeconds = null, int $timeoutMicroseconds = 0, ?int $maxEvents = null): array {}
public function wait(?\Time\Duration $timeout = null, ?int $maxEvents = null): array {}

public function getBackend(): Backend {}
}
Expand Down
5 changes: 2 additions & 3 deletions ext/standard/io_poll_arginfo.h

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

8 changes: 4 additions & 4 deletions ext/standard/io_poll_decl.h

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

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ $poll_ctx = pt_new_stream_poll();
$watcher = pt_stream_poll_add($poll_ctx, $socket2, [Io\Poll\Event::Write], "socket_data");
$watcher->modify([Io\Poll\Event::Write], "modified_data");

pt_expect_events($poll_ctx->wait(0), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'modified_data']
]);
?>
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/tests/poll/poll_stream_sock_read.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read], "socket_data");

fwrite($socket1w, "test data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket_data', 'read' => 'test data']
]);

Expand Down
4 changes: 2 additions & 2 deletions ext/standard/tests/poll/poll_stream_sock_remove_write.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ $poll_ctx = pt_new_stream_poll();
$watcher1w = pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket_data_1");
pt_stream_poll_add($poll_ctx, $socket2w, [Io\Poll\Event::Write], "socket_data_2");

pt_expect_events($poll_ctx->wait(0), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket_data_1'],
['events' => [Io\Poll\Event::Write], 'data' => 'socket_data_2']
]);

$watcher1w->remove();

pt_expect_events($poll_ctx->wait(0), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket_data_2']
]);

Expand Down
4 changes: 2 additions & 2 deletions ext/standard/tests/poll/poll_stream_sock_rw_close.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket2_data")
fwrite($socket1w, "test data");

fclose($socket1r);
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
[
'events' => [
'default' => [Io\Poll\Event::Write, Io\Poll\Event::Error, Io\Poll\Event::HangUp],
Expand All @@ -28,7 +28,7 @@ pt_expect_events($poll_ctx->wait(0, 100000), [
], $poll_ctx);

fclose($socket1w);
pt_expect_events($poll_ctx->wait(0, 100000), []);
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);

?>
--EXPECT--
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/tests/poll/poll_stream_sock_rw_max_events.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ for ($i = 0; $i < 4; $i++) {
];
}

pt_expect_events($poll_ctx->wait(0, 100000, 8), $expected);
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000), 8), $expected);

// All read data was drained above, so only write events remain
$expected = [];
for ($i = 0; $i < 4; $i++) {
$expected[] = ['events' => [Io\Poll\Event::Write], 'data' => "sock$i"];
}
pt_expect_events($poll_ctx->wait(0, 100000, 8), $expected);
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000), 8), $expected);

?>
--EXPECT--
Expand Down
16 changes: 8 additions & 8 deletions ext/standard/tests/poll/poll_stream_sock_rw_multi_edge.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,40 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read, Io\Poll\Event::EdgeTriggered], "socket1_data");
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write, Io\Poll\Event::EdgeTriggered], "socket2_data");

pt_expect_events($poll_ctx->wait(0), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);

pt_expect_events($poll_ctx->wait(0), []);
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), []);

fwrite($socket1w, "test data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'test data']
]);

fwrite($socket1w, "more data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data']
]);

pt_expect_events($poll_ctx->wait(0, 100000), []);
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);

fwrite($socket1w, " and even more data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'more data and even more data']
]);

fclose($socket1r);
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
[
'events' => ['default' => [Io\Poll\Event::Write, Io\Poll\Event::HangUp]],
'data' => 'socket2_data'
]
], $poll_ctx);

fclose($socket1w);
pt_expect_events($poll_ctx->wait(0, 100000), []);
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);

?>
--EXPECT--
Expand Down
16 changes: 8 additions & 8 deletions ext/standard/tests/poll/poll_stream_sock_rw_multi_level.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,39 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read], "socket1_data");
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket2_data");

pt_expect_events($poll_ctx->wait(0), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);

pt_expect_events($poll_ctx->wait(0), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);

fwrite($socket1w, "test data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'test data']
]);

fwrite($socket1w, "more data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data']
]);

pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data']
]);

fwrite($socket1w, " and even more data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'more data and even more data']
]);

fclose($socket1r);
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
[
'events' => [
'default' => [Io\Poll\Event::Write, Io\Poll\Event::HangUp],
Expand All @@ -56,7 +56,7 @@ pt_expect_events($poll_ctx->wait(0, 100000), [
], $poll_ctx);

fclose($socket1w);
pt_expect_events($poll_ctx->wait(0, 100000), []);
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);

?>
--EXPECT--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read, Io\Poll\Event::EdgeTriggered], "socket1_data");
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write, Io\Poll\Event::EdgeTriggered], "socket2_data");

pt_expect_events($poll_ctx->wait(0), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);
fwrite($socket1w, "test data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'test data']
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read], "socket1_data");
pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket2_data");

pt_expect_events($poll_ctx->wait(0), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data']
]);
fwrite($socket1w, "test data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket2_data'],
['events' => [Io\Poll\Event::Read], 'data' => 'socket1_data', 'read' => 'test data']
]);
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/tests/poll/poll_stream_sock_write.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ $poll_ctx = pt_new_stream_poll();

pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Write], "socket_data");

pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Write], 'data' => 'socket_data']
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pt_stream_poll_add($poll_ctx, $socket2w, [Io\Poll\Event::Write], "socket2w_data"

fclose($socket1w);
fclose($socket2w);
pt_expect_events($poll_ctx->wait(0, 100000), []);
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), []);

?>
--EXPECT--
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/tests/poll/poll_stream_tcp_read.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ $poll_ctx = pt_new_stream_poll();
pt_stream_poll_add($poll_ctx, $socket1r, [Io\Poll\Event::Read], "socket_data");

pt_write_sleep($socket1w, "test data");
pt_expect_events($poll_ctx->wait(0, 100000), [
pt_expect_events($poll_ctx->wait(Time\Duration::fromMicroseconds(100000)), [
['events' => [Io\Poll\Event::Read], 'data' => 'socket_data', 'read' => 'test data']
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ for ($i = 0; $i < count($clients); $i++) {
// events must be delivered by the following wait() without any duplicates.
$seen = [];
for ($round = 1; $round <= 2; $round++) {
$watchers = $poll_ctx->wait(0, 100000, 4);
$watchers = $poll_ctx->wait(Time\Duration::fromMicroseconds(100000), 4);
echo "Round $round count: " . count($watchers) . "\n";
foreach ($watchers as $watcher) {
$data = $watcher->getData();
Expand All @@ -35,7 +35,7 @@ for ($round = 1; $round <= 2; $round++) {
ksort($seen);
echo "Seen: " . implode(',', array_keys($seen)) . "\n";

pt_expect_events($poll_ctx->wait(0), []);
pt_expect_events($poll_ctx->wait(Time\Duration::fromSeconds(0)), []);

?>
--EXPECT--
Expand Down
Loading
Loading