diff --git a/ext/standard/io_poll.c b/ext/standard/io_poll.c index 5632fc270c56..dd0b27ae0067 100644 --- a/ext/standard/io_poll.c +++ b/ext/standard/io_poll.c @@ -17,6 +17,7 @@ #include "zend_exceptions.h" #include "php_network.h" #include "php_poll.h" +#include "io_poll.h" #include "io_poll_arginfo.h" #include "io_poll_decl.h" #include "ext/date/php_time.h" @@ -54,14 +55,17 @@ typedef struct php_io_poll_watcher_object { uint32_t triggered_events; zval data; bool active; + bool closed; /* Deactivated because its stream was closed */ php_io_poll_context_object *context; /* Back reference to Context object */ + php_socket_t fd; /* Registered fd, SOCK_ERR when inactive */ + php_stream *stream; /* Watched stream, NULL when not registered in its watcher list */ zend_object std; } php_io_poll_watcher_object; /* Context object structure */ struct php_io_poll_context_object { php_poll_ctx *ctx; - HashTable *watchers; /* Maps handle pointer -> watcher object */ + HashTable *watchers; /* Maps fd -> watcher object */ zend_object std; }; @@ -266,7 +270,10 @@ static zend_object *php_io_poll_watcher_create_object(zend_class_entry *ce) intern->watched_events = 0; intern->triggered_events = 0; intern->active = false; + intern->closed = false; intern->context = NULL; + intern->fd = SOCK_ERR; + intern->stream = NULL; ZVAL_NULL(&intern->data); return &intern->std; @@ -285,12 +292,88 @@ static zend_object *php_io_poll_context_create_object(zend_class_entry *ce) return &intern->std; } +/* Watcher registration helpers */ + +static zend_always_inline zend_ulong php_io_poll_compute_ptr_key(void *ptr) +{ + zend_ulong key = (zend_ulong) (uintptr_t) ptr; + return (key >> 3) | (key << ((sizeof(key) * 8) - 3)); +} + +static zend_always_inline void php_io_poll_watcher_deactivate(php_io_poll_watcher_object *watcher) +{ + watcher->active = false; + watcher->context = NULL; + watcher->fd = SOCK_ERR; +} + +static void php_io_poll_stream_watch(php_stream *stream, php_io_poll_watcher_object *watcher) +{ + if (!stream->poll_watchers) { + stream->poll_watchers = pemalloc(sizeof(HashTable), stream->is_persistent); + zend_hash_init(stream->poll_watchers, 4, NULL, NULL, stream->is_persistent); + } + + zval zv; + ZVAL_PTR(&zv, watcher); + zend_hash_index_add_new(stream->poll_watchers, php_io_poll_compute_ptr_key(watcher), &zv); + watcher->stream = stream; +} + +static void php_io_poll_stream_unwatch(php_io_poll_watcher_object *watcher) +{ + php_stream *stream = watcher->stream; + + if (!stream) { + return; + } + watcher->stream = NULL; + + zend_hash_index_del(stream->poll_watchers, php_io_poll_compute_ptr_key(watcher)); + if (zend_hash_num_elements(stream->poll_watchers) == 0) { + zend_hash_destroy(stream->poll_watchers); + pefree(stream->poll_watchers, stream->is_persistent); + stream->poll_watchers = NULL; + } +} + +static void php_io_poll_context_retire_watcher( + php_io_poll_context_object *context, php_io_poll_watcher_object *watcher) +{ + php_socket_t fd = watcher->fd; + + php_poll_remove(context->ctx, (int) fd); + php_io_poll_stream_unwatch(watcher); + php_io_poll_watcher_deactivate(watcher); + zend_hash_index_del(context->watchers, (zend_ulong) fd); +} + +/* Called from php_stream_free() while the fd is still open */ +PHPAPI void php_io_poll_stream_notify_close(php_stream *stream) +{ + HashTable *watchers = stream->poll_watchers; + stream->poll_watchers = NULL; + + ZEND_HASH_FOREACH_VAL(watchers, zval *zv) { + php_io_poll_watcher_object *watcher = Z_PTR_P(zv); + watcher->stream = NULL; + watcher->closed = true; + if (watcher->context) { + php_io_poll_context_retire_watcher(watcher->context, watcher); + } + } ZEND_HASH_FOREACH_END(); + + zend_hash_destroy(watchers); + pefree(watchers, stream->is_persistent); +} + /* Object Destruction Functions */ static void php_io_poll_watcher_free_object(zend_object *obj) { php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(obj); + php_io_poll_stream_unwatch(intern); zval_ptr_dtor(&intern->data); if (intern->handle) { @@ -307,8 +390,8 @@ static void php_io_poll_context_free_object(zend_object *obj) if (intern->watchers) { ZEND_HASH_FOREACH_VAL(intern->watchers, zval *zv) { php_io_poll_watcher_object *watcher = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(zv)); - watcher->active = false; - watcher->context = NULL; + php_io_poll_stream_unwatch(watcher); + php_io_poll_watcher_deactivate(watcher); } ZEND_HASH_FOREACH_END(); } @@ -355,12 +438,6 @@ static HashTable *php_io_poll_context_get_gc(zend_object *obj, zval **table, int /* Utility functions */ -static zend_always_inline zend_ulong php_io_poll_compute_ptr_key(void *ptr) -{ - zend_ulong key = (zend_ulong) (uintptr_t) ptr; - return (key >> 3) | (key << ((sizeof(key) * 8) - 3)); -} - static zend_result php_io_poll_watcher_modify_events( php_io_poll_watcher_object *watcher, uint32_t events) { @@ -370,16 +447,12 @@ static zend_result php_io_poll_watcher_modify_events( return FAILURE; } - php_socket_t fd = php_poll_handle_get_fd(watcher->handle); - if (fd == SOCK_ERR) { - zend_throw_exception( - php_io_poll_invalid_handle_class_entry, "Invalid handle for polling", 0); - return FAILURE; - } - - /* Modify in poll context */ + /* Re-add if the backend dropped a fired one-shot registration */ php_poll_ctx *poll_ctx = watcher->context->ctx; - if (php_poll_modify(poll_ctx, (int) fd, events, watcher) != SUCCESS) { + int fd = (int) watcher->fd; + if (php_poll_modify(poll_ctx, fd, events, watcher) != SUCCESS + && (php_poll_get_error(poll_ctx) != PHP_POLL_ERR_NOTFOUND + || php_poll_add(poll_ctx, fd, events, watcher) != SUCCESS)) { php_poll_error err = php_poll_get_error(poll_ctx); php_io_poll_throw_failed_operation(php_io_poll_failed_watcher_mod_class_entry, "Failed to modify watcher in polling system", err); @@ -634,26 +707,16 @@ PHP_METHOD(Io_Poll_Watcher, remove) php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); if (!intern->active || !intern->context) { + /* Closing the stream already removed it, so this is just the expected cleanup */ + if (intern->closed) { + return; + } zend_throw_exception( php_io_poll_inactive_watcher_class_entry, "Cannot remove inactive watcher", 0); RETURN_THROWS(); } - php_io_poll_context_object *context = intern->context; - php_poll_ctx *poll_ctx = context->ctx; - HashTable *watchers = context->watchers; - zend_ulong hash_key = php_io_poll_compute_ptr_key(intern->handle); - php_socket_t fd = php_poll_handle_get_fd(intern->handle); - if (fd != SOCK_ERR) { - php_poll_remove(poll_ctx, (int) fd); - } - - intern->active = false; - intern->context = NULL; - - if (watchers) { - zend_hash_index_del(watchers, hash_key); - } + php_io_poll_context_retire_watcher(intern->context, intern); } PHP_METHOD(Io_Poll_Context, __construct) @@ -715,6 +778,12 @@ PHP_METHOD(Io_Poll_Context, add) php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis()); php_poll_handle_object *handle = PHP_POLL_HANDLE_OBJ_FROM_ZV(handle_obj); + events = php_io_poll_event_enums_to_events(event_enums); + if (!events) { + zend_argument_type_error(2, "must be array of Event enums"); + RETURN_THROWS(); + } + /* Get file descriptor */ php_socket_t fd = php_poll_handle_get_fd(handle); if (fd == SOCK_ERR) { @@ -723,16 +792,21 @@ PHP_METHOD(Io_Poll_Context, add) RETURN_THROWS(); } + zval *existing_zv = zend_hash_index_find(intern->watchers, (zend_ulong) fd); + if (existing_zv) { + php_io_poll_watcher_object *existing = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(existing_zv)); + if (php_poll_handle_get_fd(existing->handle) == fd) { + zend_throw_exception( + php_io_poll_handle_already_watched_class_entry, "Handle already added", 0); + RETURN_THROWS(); + } + php_io_poll_context_retire_watcher(intern, existing); + } + /* Create watcher object */ object_init_ex(return_value, php_io_poll_watcher_class_entry); php_io_poll_watcher_object *watcher = PHP_POLL_WATCHER_OBJ_FROM_ZV(return_value); - events = php_io_poll_event_enums_to_events(event_enums); - if (!events) { - zend_argument_type_error(2, "must be array of Event enums"); - RETURN_THROWS(); - } - watcher->handle = handle; watcher->watched_events = events; watcher->triggered_events = 0; @@ -758,16 +832,22 @@ PHP_METHOD(Io_Poll_Context, add) RETURN_THROWS(); } - /* Store in our watchers map using shifted pointer as key */ + /* Store in our watchers map */ zval watcher_zv; ZVAL_OBJ(&watcher_zv, &watcher->std); GC_ADDREF(&watcher->std); - - zend_ulong hash_key = php_io_poll_compute_ptr_key(handle); - zend_hash_index_add_new(intern->watchers, hash_key, &watcher_zv); + zend_hash_index_add_new(intern->watchers, (zend_ulong) fd, &watcher_zv); watcher->active = true; watcher->context = intern; + watcher->fd = fd; + + if (handle->ops == &php_stream_poll_handle_ops) { + php_stream *stream = php_stream_poll_handle_get_stream(handle); + if (stream) { + php_io_poll_stream_watch(stream, watcher); + } + } } PHP_METHOD(Io_Poll_Context, wait) diff --git a/ext/standard/io_poll.h b/ext/standard/io_poll.h new file mode 100644 index 000000000000..1b94355f27a5 --- /dev/null +++ b/ext/standard/io_poll.h @@ -0,0 +1,26 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_IO_POLL_H +#define PHP_IO_POLL_H + +#include "php_streams.h" + +BEGIN_EXTERN_C() + +PHPAPI void php_io_poll_stream_notify_close(php_stream *stream); + +END_EXTERN_C() + +#endif /* PHP_IO_POLL_H */ diff --git a/ext/standard/tests/poll/poll_stream_closed_dup_fd.phpt b/ext/standard/tests/poll/poll_stream_closed_dup_fd.phpt new file mode 100644 index 000000000000..9fafd91a46aa --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_closed_dup_fd.phpt @@ -0,0 +1,49 @@ +--TEST-- +Io\Poll: a watched stream closed while a duplicated fd exists is unregistered in time +--SKIPIF-- +isAvailable()) { + die("skip Epoll backend required\n"); +} +?> +--FILE-- +add(new StreamPollHandle($r), [Io\Poll\Event::Read]); + +$proc = proc_open('sleep 2', [0 => $r], $pipes); +fclose($r); +$watcher->remove(); +unset($watcher); +gc_collect_cycles(); + +fwrite($w, "ping"); +echo "Events count: ", count($ctx->wait(Time\Duration::fromSeconds(0))), "\n"; + +// A new stream that takes the same fd number gets its own registration +list($r2, $w2) = pt_new_socket_pair(); +$watcher2 = $ctx->add(new StreamPollHandle($r2), [Io\Poll\Event::Read], "new"); +echo "Events count: ", count($ctx->wait(Time\Duration::fromSeconds(0))), "\n"; +fwrite($w2, "pong"); +$events = $ctx->wait(Time\Duration::fromMicroseconds(100000)); +echo "Events count: ", count($events), "\n"; +var_dump($events[0]->getData()); + +proc_terminate($proc); +proc_close($proc); +echo "done\n"; +?> +--EXPECT-- +Events count: 0 +Events count: 0 +Events count: 1 +string(3) "new" +done diff --git a/ext/standard/tests/poll/poll_stream_closed_fd_reuse.phpt b/ext/standard/tests/poll/poll_stream_closed_fd_reuse.phpt new file mode 100644 index 000000000000..74c359f301bf --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_closed_fd_reuse.phpt @@ -0,0 +1,47 @@ +--TEST-- +Io\Poll: a new stream reusing the fd number of a closed watched stream can be added +--SKIPIF-- + +--FILE-- +add(new StreamPollHandle($r0), [Io\Poll\Event::Read], "old"); + +// The lowest free fd number is handed out first, so $r1 takes the number $r0 had +fclose($r0); +list($r1, $w1) = pt_new_socket_pair(); + +$new = $ctx->add(new StreamPollHandle($r1), [Io\Poll\Event::Read], "new"); +var_dump($old->isActive()); +var_dump($new->isActive()); + +fwrite($w1, "ping"); +$events = $ctx->wait(Time\Duration::fromMicroseconds(100000)); +var_dump(count($events)); +var_dump($events[0] === $new); +var_dump($events[0]->getData()); + +$old->remove(); +echo "old removed\n"; + +$new->remove(); +var_dump($new->isActive()); +echo "Events count: ", count($ctx->wait(Time\Duration::fromSeconds(0))), "\n"; +?> +--EXPECT-- +bool(false) +bool(true) +int(1) +bool(true) +string(3) "new" +old removed +bool(false) +Events count: 0 diff --git a/ext/standard/tests/poll/poll_stream_closed_fd_reuse_unwatched.phpt b/ext/standard/tests/poll/poll_stream_closed_fd_reuse_unwatched.phpt new file mode 100644 index 000000000000..bf699c948586 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_closed_fd_reuse_unwatched.phpt @@ -0,0 +1,36 @@ +--TEST-- +Io\Poll: an unwatched stream reusing the fd number of a closed watched stream is never reported +--SKIPIF-- + +--FILE-- +add(new StreamPollHandle($r0), [Io\Poll\Event::Read], "old"); + +// The lowest free fd number is handed out first, so $r1 takes the number $r0 had +fclose($r0); +list($r1, $w1) = pt_new_socket_pair(); +fwrite($w1, "ping"); + +echo "Events count: ", count($ctx->wait(Time\Duration::fromSeconds(0))), "\n"; +echo "Events count: ", count($ctx->wait(Time\Duration::fromSeconds(0))), "\n"; +var_dump($old->isActive()); + +$old->remove(); +var_dump($old->isActive()); +var_dump(fread($r1, 10)); +?> +--EXPECT-- +Events count: 0 +Events count: 0 +bool(false) +bool(false) +string(4) "ping" diff --git a/ext/standard/tests/poll/poll_stream_closed_many.phpt b/ext/standard/tests/poll/poll_stream_closed_many.phpt new file mode 100644 index 000000000000..3f436fc940a6 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_closed_many.phpt @@ -0,0 +1,52 @@ +--TEST-- +Io\Poll: many watchers closed before removal leave the context consistent +--FILE-- +add(new StreamPollHandle($rk), [Io\Poll\Event::Read], "keep"); + +list($ro, $wo) = pt_new_socket_pair(); +$oneshot = $ctx->add(new StreamPollHandle($ro), [Io\Poll\Event::Read, Io\Poll\Event::OneShot], "oneshot"); +fwrite($wo, "x"); +$events = $ctx->wait(Time\Duration::fromMicroseconds(100000)); +var_dump(count($events), $events[0]->getData()); + +for ($i = 0; $i < 200; $i++) { + list($r, $w) = pt_new_socket_pair(); + $watcher = $ctx->add(new StreamPollHandle($r), [Io\Poll\Event::Read]); + fclose($r); + $watcher->remove(); + fclose($w); +} + +// The fired one-shot watcher stays disarmed, the kept one is still watched +fwrite($wk, "y"); +$events = $ctx->wait(Time\Duration::fromMicroseconds(100000)); +var_dump(count($events), $events[0]->getData()); +var_dump($keep->isActive(), $oneshot->isActive()); + +$oneshot->modifyEvents([Io\Poll\Event::Read, Io\Poll\Event::OneShot]); +$events = $ctx->wait(Time\Duration::fromMicroseconds(100000)); +var_dump(count($events)); + +list($r, $w) = pt_new_socket_pair(); +$new = $ctx->add(new StreamPollHandle($r), [Io\Poll\Event::Read], "new"); +fwrite($w, "z"); +$events = $ctx->wait(Time\Duration::fromMicroseconds(100000)); +var_dump(count($events)); +var_dump($oneshot->isActive()); +?> +--EXPECT-- +int(1) +string(7) "oneshot" +int(1) +string(4) "keep" +bool(true) +bool(true) +int(2) +int(2) +bool(true) diff --git a/ext/standard/tests/poll/poll_stream_closed_multi_context.phpt b/ext/standard/tests/poll/poll_stream_closed_multi_context.phpt new file mode 100644 index 000000000000..c14b4803bee2 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_closed_multi_context.phpt @@ -0,0 +1,34 @@ +--TEST-- +Io\Poll: closing a stream watched in several contexts leaves every watcher removable +--FILE-- +add($handle, [Io\Poll\Event::Read]); +$w2 = $ctx2->add($handle, [Io\Poll\Event::Read]); + +fwrite($w, "ping"); +fclose($r); + +echo "ctx1 events count: ", count($ctx1->wait(Time\Duration::fromSeconds(0))), "\n"; +echo "ctx2 events count: ", count($ctx2->wait(Time\Duration::fromSeconds(0))), "\n"; +var_dump($w1->isActive()); +var_dump($w2->isActive()); + +$w1->remove(); +$w2->remove(); +echo "removed\n"; + +fclose($w); +?> +--EXPECT-- +ctx1 events count: 0 +ctx2 events count: 0 +bool(false) +bool(false) +removed diff --git a/ext/standard/tests/poll/poll_stream_closed_persistent.phpt b/ext/standard/tests/poll/poll_stream_closed_persistent.phpt new file mode 100644 index 000000000000..b26f24b7cc9a --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_closed_persistent.phpt @@ -0,0 +1,28 @@ +--TEST-- +Io\Poll: a watcher on a persistent stream is unregistered at request shutdown +--SKIPIF-- + +--FILE-- +add(new StreamPollHandle($p), [Io\Poll\Event::Write]); +echo "Events count: ", count($ctx->wait(Time\Duration::fromMicroseconds(100000))), "\n"; + +// The persistent stream outlives the request, so the watcher and context are +// intentionally left to be freed at shutdown +echo "done\n"; +?> +--EXPECT-- +Events count: 1 +done diff --git a/ext/standard/tests/poll/poll_stream_closed_watcher_inactive.phpt b/ext/standard/tests/poll/poll_stream_closed_watcher_inactive.phpt new file mode 100644 index 000000000000..7c5e943ebe5a --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_closed_watcher_inactive.phpt @@ -0,0 +1,38 @@ +--TEST-- +Io\Poll: closing a watched stream deactivates its watcher and remove() stays harmless +--FILE-- +add(new StreamPollHandle($r), [Io\Poll\Event::Read]); + +fwrite($w, "ping"); +var_dump($watcher->isActive()); + +fclose($r); +var_dump($watcher->isActive()); + +echo "Events count: ", count($ctx->wait(Time\Duration::fromSeconds(0))), "\n"; + +$watcher->remove(); +$watcher->remove(); +echo "removed\n"; +var_dump($watcher->isActive()); + +try { + $watcher->modifyEvents([Io\Poll\Event::Write]); +} catch (Io\Poll\InactiveWatcherException $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +fclose($w); +?> +--EXPECT-- +bool(true) +bool(false) +Events count: 0 +removed +bool(false) +Io\Poll\InactiveWatcherException: Cannot modify inactive watcher diff --git a/ext/standard/tests/poll/poll_stream_handle_closed_stream.phpt b/ext/standard/tests/poll/poll_stream_handle_closed_stream.phpt index 56b5c08fadef..5e9e6729aa98 100644 --- a/ext/standard/tests/poll/poll_stream_handle_closed_stream.phpt +++ b/ext/standard/tests/poll/poll_stream_handle_closed_stream.phpt @@ -19,7 +19,7 @@ var_dump(get_debug_type($handle->getStream())); try { $watcher->modifyEvents([Io\Poll\Event::Write]); -} catch (Io\Poll\InvalidHandleException $e) { +} catch (Io\Poll\InactiveWatcherException $e) { echo $e->getMessage(), "\n"; } @@ -37,7 +37,7 @@ var_dump($watcher->isActive()); --EXPECT-- bool(false) string(17) "resource (closed)" -Invalid handle for polling +Cannot modify inactive watcher Invalid handle for polling Events count: 0 bool(false) diff --git a/ext/standard/tests/poll/poll_stream_oneshot_rearm.phpt b/ext/standard/tests/poll/poll_stream_oneshot_rearm.phpt new file mode 100644 index 000000000000..5ccc43113dae --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_oneshot_rearm.phpt @@ -0,0 +1,44 @@ +--TEST-- +Io\Poll: a fired one-shot watcher stays active and is re-armed by modifyEvents() +--FILE-- +add($handle, [Io\Poll\Event::Read, Io\Poll\Event::OneShot], "data"); + +fwrite($w, "a"); +echo "Events count: ", count($ctx->wait(Time\Duration::fromMicroseconds(100000))), "\n"; +var_dump(fread($r, 10)); + +fwrite($w, "b"); +echo "Events count: ", count($ctx->wait(Time\Duration::fromSeconds(0))), "\n"; +var_dump($watcher->isActive()); + +try { + $ctx->add($handle, [Io\Poll\Event::Read]); +} catch (Io\Poll\HandleAlreadyWatchedException $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +$watcher->modifyEvents([Io\Poll\Event::Read, Io\Poll\Event::OneShot]); +$events = $ctx->wait(Time\Duration::fromMicroseconds(100000)); +echo "Events count: ", count($events), "\n"; +var_dump($events[0] === $watcher); +var_dump(fread($r, 10)); + +$watcher->remove(); +var_dump($watcher->isActive()); +?> +--EXPECT-- +Events count: 1 +string(1) "a" +Events count: 0 +bool(true) +Io\Poll\HandleAlreadyWatchedException: Handle already added +Events count: 1 +bool(true) +string(1) "b" +bool(false) diff --git a/main/php_streams.h b/main/php_streams.h index fb0c57ecf83d..69bd996fea4f 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -250,6 +250,8 @@ struct _php_stream { struct _php_stream *enclosing_stream; /* this is a private stream owned by enclosing_stream */ zend_llist *error_list; + + HashTable *poll_watchers; /* Io\Poll watchers notified before the stream is closed */ }; /* php_stream */ #define PHP_STREAM_CONTEXT(stream) \ diff --git a/main/poll/poll_backend_kqueue.c b/main/poll/poll_backend_kqueue.c index 19ff0ad6d228..f2c0212f4d15 100644 --- a/main/poll/poll_backend_kqueue.c +++ b/main/poll/poll_backend_kqueue.c @@ -279,6 +279,11 @@ static zend_result kqueue_backend_remove(php_poll_ctx *ctx, int fd) return FAILURE; } + /* Remove from tracking */ + if (!ctx->raw_events) { + zend_hash_index_del(backend_data->fd_tracking, fd); + } + /* If no filters were successfully deleted, that's an error */ if (successful_deletes == 0) { php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND); @@ -289,11 +294,6 @@ static zend_result kqueue_backend_remove(php_poll_ctx *ctx, int fd) backend_data->fd_count--; backend_data->filter_count -= successful_deletes; - /* Remove from tracking */ - if (!ctx->raw_events) { - zend_hash_index_del(backend_data->fd_tracking, fd); - } - return SUCCESS; } diff --git a/main/streams/streams.c b/main/streams/streams.c index 7cd63f0038d3..0862dc7081e5 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -28,6 +28,7 @@ #include "ext/standard/basic_functions.h" /* for BG(CurrentStatFile) */ #include "ext/standard/php_string.h" /* for php_memnstr, used by php_stream_get_record() */ #include "ext/uri/php_uri.h" +#include "ext/standard/io_poll.h" #include #include #include "php_streams_int.h" @@ -371,6 +372,11 @@ fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remov return ret; } + /* Watchers must unregister while the fd is still open */ + if (stream->poll_watchers) { + php_io_poll_stream_notify_close(stream); + } + ret = stream->ops->close(stream, preserve_handle ? 0 : 1); if (!ret) { ret = flush_result; @@ -386,6 +392,10 @@ fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remov } if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) { + if (stream->poll_watchers) { + php_io_poll_stream_notify_close(stream); + } + while (stream->readfilters.head) { if (stream->readfilters.head->res != NULL) { zend_list_close(stream->readfilters.head->res);