diff --git a/include/php_http_server.h b/include/php_http_server.h index 512747cd..ae39b595 100644 --- a/include/php_http_server.h +++ b/include/php_http_server.h @@ -307,6 +307,15 @@ struct _http_server_config_t { zval log_stream; bool telemetry_enabled; + /* Hot-reload triggers (issue #93) — consumed by the pool parent only + * (the orchestrator coroutines run there), never snapshotted. + * hot_reload_paths IS_UNDEF = watcher trigger off. */ + zval hot_reload_paths; /* array of directories to watch */ + zval hot_reload_extensions; /* array of extensions; empty = all */ + uint32_t hot_reload_debounce_ms; + uint32_t hot_reload_max_hold_ms; + bool reload_on_sighup; + /* Frozen persistent snapshot — populated at lock time, shared refcounted * across threads. NULL until the owning HttpServer constructs (which calls * http_server_config_lock) or the Config is transferred directly via a diff --git a/src/http_server_class.c b/src/http_server_class.c index 867c6894..52198f04 100644 --- a/src/http_server_class.c +++ b/src/http_server_class.c @@ -33,6 +33,10 @@ #include "core/worker_registry.h" #include "core/response_wire.h" #include "log/http_log.h" +#ifndef PHP_WIN32 +#include +#include +#endif #include "static/static_handler.h" #include "static/http_static_cache.h" #ifdef HAVE_HTTP_SERVER_HTTP3 @@ -479,6 +483,14 @@ struct http_server_object { bool reload_in_progress;/* parent: one rotation at a time */ void *pool_await_state; /* parent: st of the active pool run */ + /* Hot-reload triggers (issue #93), pool parent only. Watcher objects are + * closed and released by http_server_hot_reload_down; the SIGHUP event is + * owned by its orchestrator coroutine (the pointer here only lets teardown + * wake it). */ + zval hot_reload_watchers; /* array of Async\FileSystemWatcher */ + void *sighup_event; /* zend_async_signal_event_t* */ + bool hot_reload_stopping; /* orchestrators must exit */ + /* Transit sidecar — non-NULL only in the persistent shell created by * transfer_obj(TRANSFER). Holds pemalloc-copied closures so the LOAD * side can rebuild fcall_t entries in the destination thread's heap. @@ -576,6 +588,9 @@ typedef struct { } http_server_reload_shared_t; static void http_server_do_stop(http_server_object *server, const char *reason); +static void http_server_hot_reload_up(http_server_object *server, zval *this_zv, + http_server_config_t *cfg); +static void http_server_hot_reload_down(http_server_object *server); /* Runs on a fresh coroutine enqueued by the deadline tick: stop() disposes that * very tick timer, which must not happen from inside its own callback. */ @@ -2710,6 +2725,12 @@ static int http_server_start_pool(http_server_object *server, server->in_pool_mode = true; server->running = true; + /* Arm hot-reload triggers (issue #93) on the parent before it suspends. */ + if (boot_cfg != NULL + && (Z_TYPE(boot_cfg->hot_reload_paths) == IS_ARRAY || boot_cfg->reload_on_sighup)) { + http_server_hot_reload_up(server, this_zv, boot_cfg); + } + /* Suspend until all submitted workers report done. Skip the suspend * entirely when nothing got submitted — there is no callback to * notify all_done, so awaiting on it would deadlock. */ @@ -2741,6 +2762,9 @@ static int http_server_start_pool(http_server_object *server, server->worker_pool = NULL; server->pool_await_state = NULL; + /* Retire the hot-reload orchestrators before the pool machinery goes. */ + http_server_hot_reload_down(server); + /* Workers have quiesced (the suspend returned). Tear down the transport * reactor pool: this releases the H3 listeners the gated reactors own, * frees the worker registry, and stops the reactor loops. */ @@ -3630,6 +3654,17 @@ ZEND_METHOD(TrueAsync_HttpServer, reload) RETURN_THROWS(); } + /* The gated transport reactor pool (#80) routes requests through worker + * inboxes registered for the pool's lifetime; retiring a worker while the + * reactors are live would leave them posting into freed inboxes (there is + * no registry unregister yet). Refuse loudly instead of corrupting. */ + if (UNEXPECTED(http_server_reactor_pool_enabled())) { + zend_throw_exception(http_server_runtime_exception_ce, + "reload() is not yet supported with TRUE_ASYNC_SERVER_REACTOR_POOL=1 " + "(worker inbox rotation is not implemented, #93)", 0); + RETURN_THROWS(); + } + if (server->reload_in_progress) { RETURN_FALSE; /* one rotation at a time — call again after it returns */ } @@ -3743,6 +3778,433 @@ ZEND_METHOD(TrueAsync_HttpServer, reload) } /* }}} */ +/* ========================================================================== + * Hot-reload triggers (issue #93) — pool-parent orchestrators. + * ========================================================================== */ + +typedef struct { + zend_object *server_obj; /* addref'd parent HttpServer */ + zval watcher; /* owned Async\FileSystemWatcher */ +} hot_reload_watch_ctx_t; + +typedef struct { + zend_object *server_obj; +} hot_reload_sighup_ctx_t; + +static void hot_reload_watch_ctx_dispose(zend_coroutine_t *co) +{ + hot_reload_watch_ctx_t *ctx = co->extended_data; + + if (ctx == NULL) { + return; + } + + co->extended_data = NULL; + zval_ptr_dtor(&ctx->watcher); + OBJ_RELEASE(ctx->server_obj); + efree(ctx); +} + +static void hot_reload_sighup_ctx_dispose(zend_coroutine_t *co) +{ + hot_reload_sighup_ctx_t *ctx = co->extended_data; + + if (ctx == NULL) { + return; + } + + co->extended_data = NULL; + OBJ_RELEASE(ctx->server_obj); + efree(ctx); +} + +#ifndef PHP_WIN32 +/* Case-insensitive extension allow-list check; NULL/empty list = every file. */ +static bool hot_reload_ext_matches(const HashTable *exts, const char *name) +{ + if (exts == NULL || zend_hash_num_elements((HashTable *)exts) == 0) { + return true; + } + + const char *dot = strrchr(name, '.'); + + if (dot == NULL || dot[1] == '\0') { + return false; + } + + const zval *entry; + ZEND_HASH_FOREACH_VAL((HashTable *)exts, entry) + { + if (Z_TYPE_P(entry) != IS_STRING) { + continue; + } + + const char *e = Z_STRVAL_P(entry); + + if (e[0] == '.') { + e++; + } + + if (strcasecmp(dot + 1, e) == 0) { + return true; + } + } + ZEND_HASH_FOREACH_END(); + + return false; +} + +/* opcache_invalidate($file, true) for every matching file under `dir`. + * `fname` is the pre-resolved "opcache_invalidate" callable zval. */ +static void hot_reload_invalidate_tree(const char *dir, const HashTable *exts, + zval *fname, const int depth) +{ + if (depth > 16) { + return; + } + + DIR *d = opendir(dir); + + if (d == NULL) { + return; + } + + const struct dirent *ent; + + while ((ent = readdir(d)) != NULL) { + if (ent->d_name[0] == '.' + && (ent->d_name[1] == '\0' || (ent->d_name[1] == '.' && ent->d_name[2] == '\0'))) { + continue; + } + + char full[MAXPATHLEN]; + + if ((size_t)snprintf(full, sizeof(full), "%s/%s", dir, ent->d_name) >= sizeof(full)) { + continue; + } + + struct stat st; + + if (lstat(full, &st) != 0) { + continue; + } + + if (S_ISDIR(st.st_mode)) { + hot_reload_invalidate_tree(full, exts, fname, depth + 1); + continue; + } + + if (!S_ISREG(st.st_mode) || !hot_reload_ext_matches(exts, ent->d_name)) { + continue; + } + + zval rv, params[2]; + ZVAL_UNDEF(&rv); + ZVAL_STRING(¶ms[0], full); + ZVAL_TRUE(¶ms[1]); + call_user_function(NULL, NULL, fname, &rv, 2, params); + zval_ptr_dtor(¶ms[0]); + zval_ptr_dtor(&rv); + + if (EG(exception)) { + zend_clear_exception(); + } + } + + closedir(d); +} +#endif /* !PHP_WIN32 */ + +/* One trigger firing: invalidate the watched trees in opcache (so replacement + * bootloaders recompile the changed files) and rotate via HttpServer::reload(). */ +static void http_server_hot_reload_fire(zend_object *server_obj, const char *trigger) +{ + http_server_object *server = http_server_from_obj(server_obj); + + if (server->hot_reload_stopping || !server->in_pool_mode) { + return; + } + + http_logf_info(&server->log_state, "reload.trigger source=%s", trigger); + +#ifndef PHP_WIN32 + http_server_config_t *cfg = http_server_get_config(server); + + if (cfg != NULL && Z_TYPE(cfg->hot_reload_paths) == IS_ARRAY) { + zval fname; + ZVAL_STRING(&fname, "opcache_invalidate"); + + if (zend_is_callable(&fname, 0, NULL)) { + const HashTable *exts = (Z_TYPE(cfg->hot_reload_extensions) == IS_ARRAY) + ? Z_ARRVAL(cfg->hot_reload_extensions) : NULL; + const zval *path; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL(cfg->hot_reload_paths), path) + { + hot_reload_invalidate_tree(Z_STRVAL_P(path), exts, &fname, 0); + } + ZEND_HASH_FOREACH_END(); + } + + zval_ptr_dtor(&fname); + } +#endif + + zval rv; + ZVAL_UNDEF(&rv); + zend_call_method_with_0_params(server_obj, NULL, NULL, "reload", &rv); + zval_ptr_dtor(&rv); + + if (EG(exception)) { + http_logf_info(&server->log_state, "reload.trigger source=%s failed", trigger); + zend_clear_exception(); + } +} + +/* Coroutine: drive one watcher's iterator; every delivered (debounced) event + * is one reload. Ends when the watcher is closed by teardown. */ +static void http_server_hot_reload_watch_entry(void) +{ + zend_coroutine_t *co = ZEND_ASYNC_CURRENT_COROUTINE; + hot_reload_watch_ctx_t *ctx = co->extended_data; + http_server_object *server = http_server_from_obj(ctx->server_obj); + + zval iter; + ZVAL_UNDEF(&iter); + zend_call_method_with_0_params(Z_OBJ(ctx->watcher), NULL, NULL, "getIterator", &iter); + + if (EG(exception) || Z_TYPE(iter) != IS_OBJECT) { + zend_clear_exception(); + zval_ptr_dtor(&iter); + return; + } + + zval rv; + ZVAL_UNDEF(&rv); + zend_call_method_with_0_params(Z_OBJ(iter), NULL, NULL, "rewind", &rv); + zval_ptr_dtor(&rv); + + while (!EG(exception) && !server->hot_reload_stopping) { + zval valid; + ZVAL_UNDEF(&valid); + zend_call_method_with_0_params(Z_OBJ(iter), NULL, NULL, "valid", &valid); + + const bool go = !EG(exception) && zend_is_true(&valid); + zval_ptr_dtor(&valid); + + if (!go || server->hot_reload_stopping) { + break; + } + + http_server_hot_reload_fire(ctx->server_obj, "watcher"); + + if (server->hot_reload_stopping) { + break; + } + + ZVAL_UNDEF(&rv); + zend_call_method_with_0_params(Z_OBJ(iter), NULL, NULL, "next", &rv); + zval_ptr_dtor(&rv); + } + + if (EG(exception)) { + zend_clear_exception(); + } + + zval_ptr_dtor(&iter); +} + +#ifndef PHP_WIN32 +/* Coroutine: persistent SIGHUP handler — one signal event armed for the whole + * pool run (no unarmed window), each delivery is one reload. */ +static void http_server_hot_reload_sighup_entry(void) +{ + zend_coroutine_t *co = ZEND_ASYNC_CURRENT_COROUTINE; + hot_reload_sighup_ctx_t *ctx = co->extended_data; + http_server_object *server = http_server_from_obj(ctx->server_obj); + + zend_async_signal_event_t *sig = ZEND_ASYNC_NEW_SIGNAL_EVENT(SIGHUP); + + if (UNEXPECTED(sig == NULL)) { + zend_clear_exception(); + return; + } + + if (UNEXPECTED(!sig->base.start(&sig->base))) { + zend_clear_exception(); + sig->base.dispose(&sig->base); + return; + } + + server->sighup_event = sig; + http_logf_info(&server->log_state, "reload.signal armed signal=SIGHUP"); + + while (!server->hot_reload_stopping) { + zend_async_resume_when(co, &sig->base, false, zend_async_waker_callback_resolve, NULL); + ZEND_ASYNC_SUSPEND(); + ZEND_ASYNC_WAKER_DESTROY(co); + + if (EG(exception)) { + zend_clear_exception(); + break; + } + + if (server->hot_reload_stopping) { + break; + } + + http_server_hot_reload_fire(ctx->server_obj, "sighup"); + } + + server->sighup_event = NULL; + sig->base.stop(&sig->base); + sig->base.dispose(&sig->base); +} +#endif /* !PHP_WIN32 */ + +/* Arm the configured triggers on the pool parent (called from start_pool just + * before it suspends). Failures degrade to "trigger off" with a log line. */ +static void http_server_hot_reload_up(http_server_object *server, zval *this_zv, + http_server_config_t *cfg) +{ + server->hot_reload_stopping = false; + + if (Z_TYPE(cfg->hot_reload_paths) == IS_ARRAY) { + zend_string *cls = zend_string_init("Async\\FileSystemWatcher", + sizeof("Async\\FileSystemWatcher") - 1, 0); + zend_class_entry *ce = zend_lookup_class(cls); + zend_string_release(cls); + + if (UNEXPECTED(ce == NULL)) { + zend_clear_exception(); + http_logf_info(&server->log_state, + "reload.watch unavailable (Async\\FileSystemWatcher missing)"); + return; + } + + array_init(&server->hot_reload_watchers); + + const zval *path; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL(cfg->hot_reload_paths), path) + { + zval watcher; + + if (object_init_ex(&watcher, ce) != SUCCESS) { + zend_clear_exception(); + continue; + } + + zval args[6], rv; + ZVAL_COPY(&args[0], path); + ZVAL_TRUE(&args[1]); /* recursive */ + ZVAL_TRUE(&args[2]); /* coalesce */ + ZVAL_LONG(&args[3], cfg->hot_reload_debounce_ms); + ZVAL_LONG(&args[4], cfg->hot_reload_max_hold_ms); + + if (Z_TYPE(cfg->hot_reload_extensions) == IS_ARRAY) { + ZVAL_COPY(&args[5], &cfg->hot_reload_extensions); + } else { + array_init(&args[5]); + } + + ZVAL_UNDEF(&rv); + zend_call_known_function(ce->constructor, Z_OBJ(watcher), ce, &rv, + 6, args, NULL); + zval_ptr_dtor(&rv); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[5]); + + if (EG(exception)) { + http_logf_info(&server->log_state, "reload.watch failed path=%s", + Z_STRVAL_P(path)); + zend_clear_exception(); + zval_ptr_dtor(&watcher); + continue; + } + + hot_reload_watch_ctx_t *wctx = emalloc(sizeof(*wctx)); + wctx->server_obj = Z_OBJ_P(this_zv); + GC_ADDREF(wctx->server_obj); + ZVAL_COPY(&wctx->watcher, &watcher); + + zend_coroutine_t *co = ZEND_ASYNC_NEW_COROUTINE(ZEND_ASYNC_MAIN_SCOPE); + + if (UNEXPECTED(co == NULL)) { + zend_clear_exception(); + zval_ptr_dtor(&wctx->watcher); + OBJ_RELEASE(wctx->server_obj); + efree(wctx); + zval_ptr_dtor(&watcher); + continue; + } + + co->internal_entry = http_server_hot_reload_watch_entry; + co->extended_data = wctx; + co->extended_dispose = hot_reload_watch_ctx_dispose; + ZEND_ASYNC_ENQUEUE_COROUTINE(co); + + add_next_index_zval(&server->hot_reload_watchers, &watcher); + http_logf_info(&server->log_state, + "reload.watch armed path=%s debounce_ms=%u max_hold_ms=%u", + Z_STRVAL_P(path), cfg->hot_reload_debounce_ms, + cfg->hot_reload_max_hold_ms); + } + ZEND_HASH_FOREACH_END(); + } + +#ifndef PHP_WIN32 + if (cfg->reload_on_sighup) { + hot_reload_sighup_ctx_t *sctx = emalloc(sizeof(*sctx)); + sctx->server_obj = Z_OBJ_P(this_zv); + GC_ADDREF(sctx->server_obj); + + zend_coroutine_t *co = ZEND_ASYNC_NEW_COROUTINE(ZEND_ASYNC_MAIN_SCOPE); + + if (UNEXPECTED(co == NULL)) { + zend_clear_exception(); + OBJ_RELEASE(sctx->server_obj); + efree(sctx); + return; + } + + co->internal_entry = http_server_hot_reload_sighup_entry; + co->extended_data = sctx; + co->extended_dispose = hot_reload_sighup_ctx_dispose; + ZEND_ASYNC_ENQUEUE_COROUTINE(co); + } +#endif +} + +/* Retire the triggers: closing a watcher ends its iterator (the orchestrator + * sees valid()=false); notifying the signal event wakes its waiter, which + * observes hot_reload_stopping and disposes the event itself. */ +static void http_server_hot_reload_down(http_server_object *server) +{ + server->hot_reload_stopping = true; + + if (Z_TYPE(server->hot_reload_watchers) == IS_ARRAY) { + const zval *w; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL(server->hot_reload_watchers), w) + { + zval rv; + ZVAL_UNDEF(&rv); + zend_call_method_with_0_params(Z_OBJ_P(w), NULL, NULL, "close", &rv); + zval_ptr_dtor(&rv); + + if (EG(exception)) { + zend_clear_exception(); + } + } + ZEND_HASH_FOREACH_END(); + + zval_ptr_dtor(&server->hot_reload_watchers); + ZVAL_UNDEF(&server->hot_reload_watchers); + } + + if (server->sighup_event != NULL) { + ZEND_ASYNC_CALLBACKS_NOTIFY((zend_async_event_t *) server->sighup_event, NULL, NULL); + } +} + /* {{{ proto HttpServer::isRunning(): bool */ ZEND_METHOD(TrueAsync_HttpServer, isRunning) { diff --git a/src/http_server_config.c b/src/http_server_config.c index c668667b..15a1e5aa 100644 --- a/src/http_server_config.c +++ b/src/http_server_config.c @@ -411,6 +411,11 @@ ZEND_METHOD(TrueAsync_HttpServerConfig, __construct) config->stream_write_buffer_bytes = DEFAULT_STREAM_WRITE_BUFFER_BYTES; config->max_body_size = DEFAULT_MAX_BODY_SIZE; config->h2_static_budget_max = 0; /* 0 = auto from memory_limit */ + ZVAL_UNDEF(&config->hot_reload_paths); + ZVAL_UNDEF(&config->hot_reload_extensions); + config->hot_reload_debounce_ms = 300; + config->hot_reload_max_hold_ms = 2000; + config->reload_on_sighup = false; config->http3_idle_timeout_ms = DEFAULT_HTTP3_IDLE_TIMEOUT_MS; config->http3_stream_window_bytes = DEFAULT_HTTP3_STREAM_WINDOW_BYTES; config->http3_max_concurrent_streams = DEFAULT_HTTP3_MAX_CONCURRENT_STREAMS; @@ -727,6 +732,111 @@ ZEND_METHOD(TrueAsync_HttpServerConfig, getWorkers) } /* }}} */ +/* {{{ proto HttpServerConfig::enableHotReload(array $watchPaths, array $extensions = ['php'], int $debounceMs = 300, int $maxHoldMs = 2000): static + * + * Dev hot-reload trigger (issue #93), pool mode only. The pool parent spawns + * one recursive Async\FileSystemWatcher per path with the given debounce; a + * collapsed change event invalidates the watched trees in opcache and calls + * HttpServer::reload() — replacement workers re-run the bootloader. */ +ZEND_METHOD(TrueAsync_HttpServerConfig, enableHotReload) +{ + HashTable *paths; + HashTable *extensions = NULL; + zend_long debounce_ms = 300; + zend_long max_hold_ms = 2000; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_ARRAY_HT(paths) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT(extensions) + Z_PARAM_LONG(debounce_ms) + Z_PARAM_LONG(max_hold_ms) + ZEND_PARSE_PARAMETERS_END(); + + http_server_config_t *config = Z_HTTP_SERVER_CONFIG_P(ZEND_THIS); + + if (config_check_locked(config)) { + return; + } + + if (zend_hash_num_elements(paths) == 0) { + zend_throw_exception(http_server_invalid_argument_exception_ce, + "enableHotReload() requires at least one watch path", 0); + return; + } + + const zval *entry; + ZEND_HASH_FOREACH_VAL(paths, entry) + { + if (Z_TYPE_P(entry) != IS_STRING || Z_STRLEN_P(entry) == 0) { + zend_throw_exception(http_server_invalid_argument_exception_ce, + "enableHotReload() watch paths must be non-empty strings", 0); + return; + } + } + ZEND_HASH_FOREACH_END(); + + if (debounce_ms < 1 || debounce_ms > 600000 || max_hold_ms < 0 || max_hold_ms > 600000) { + zend_throw_exception(http_server_invalid_argument_exception_ce, + "enableHotReload() debounceMs must be 1..600000, maxHoldMs 0..600000", 0); + return; + } + + if (Z_TYPE(config->hot_reload_paths) != IS_UNDEF) { + zval_ptr_dtor(&config->hot_reload_paths); + } + + if (Z_TYPE(config->hot_reload_extensions) != IS_UNDEF) { + zval_ptr_dtor(&config->hot_reload_extensions); + ZVAL_UNDEF(&config->hot_reload_extensions); + } + + ZVAL_ARR(&config->hot_reload_paths, zend_array_dup(paths)); + + if (extensions != NULL && zend_hash_num_elements(extensions) > 0) { + ZVAL_ARR(&config->hot_reload_extensions, zend_array_dup(extensions)); + } + + config->hot_reload_debounce_ms = (uint32_t)debounce_ms; + config->hot_reload_max_hold_ms = (uint32_t)max_hold_ms; + + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); +} +/* }}} */ + +/* {{{ proto HttpServerConfig::enableReloadOnSignal(bool $enabled = true): static + * + * Prod hot-reload trigger (issue #93), pool mode only: the pool parent arms a + * persistent SIGHUP handler that calls HttpServer::reload(). */ +ZEND_METHOD(TrueAsync_HttpServerConfig, enableReloadOnSignal) +{ + bool enabled = true; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(enabled) + ZEND_PARSE_PARAMETERS_END(); + + http_server_config_t *config = Z_HTTP_SERVER_CONFIG_P(ZEND_THIS); + + if (config_check_locked(config)) { + return; + } + +#ifdef PHP_WIN32 + if (enabled) { + zend_throw_exception(http_server_invalid_argument_exception_ce, + "enableReloadOnSignal() is not supported on Windows (no SIGHUP)", 0); + return; + } +#endif + + config->reload_on_sighup = enabled; + + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); +} +/* }}} */ + /* {{{ proto HttpServerConfig::setBootloader(?Closure $bootloader): static * * Optional bootloader Closure handed to the built-in worker pool. The @@ -2802,6 +2912,16 @@ static void http_server_config_free(zend_object *obj) ZVAL_UNDEF(&config->log_stream); } + if (Z_TYPE(config->hot_reload_paths) != IS_UNDEF) { + zval_ptr_dtor(&config->hot_reload_paths); + ZVAL_UNDEF(&config->hot_reload_paths); + } + + if (Z_TYPE(config->hot_reload_extensions) != IS_UNDEF) { + zval_ptr_dtor(&config->hot_reload_extensions); + ZVAL_UNDEF(&config->hot_reload_extensions); + } + if (Z_TYPE(config->bootloader) != IS_UNDEF) { zval_ptr_dtor(&config->bootloader); ZVAL_UNDEF(&config->bootloader); diff --git a/stubs/HttpServerConfig.php b/stubs/HttpServerConfig.php index 5b0695dd..2a960a55 100644 --- a/stubs/HttpServerConfig.php +++ b/stubs/HttpServerConfig.php @@ -138,6 +138,27 @@ public function getWorkers(): int {} */ public function setBootloader(?\Closure $bootloader): static {} + /** + * Dev hot-reload trigger (issue #93), pool mode only ({@see setWorkers()} > 1). + * The pool parent watches each path recursively (with the given debounce); + * a collapsed change event invalidates the watched trees in opcache and + * calls {@see HttpServer::reload()} — replacement workers re-run the + * bootloader with the changed code, on the same listen sockets. + * + * @param array $watchPaths Directories to watch (recursively). + * @param array $extensions Case-insensitive extension allow-list; empty = every file. + * @param int $debounceMs Quiet window before a burst of changes fires one reload. + * @param int $maxHoldMs Force a reload at most this long after the first change (0 = no cap). + */ + public function enableHotReload(array $watchPaths, array $extensions = ['php'], int $debounceMs = 300, int $maxHoldMs = 2000): static {} + + /** + * Prod hot-reload trigger (issue #93), pool mode only: the pool parent arms + * a persistent SIGHUP handler that calls {@see HttpServer::reload()}. + * Not supported on Windows. + */ + public function enableReloadOnSignal(bool $enabled = true): static {} + /** * Returns the bootloader previously set, or null. */ diff --git a/stubs/HttpServerConfig.php_arginfo.h b/stubs/HttpServerConfig.php_arginfo.h index 4280a978..b7d2f86b 100644 --- a/stubs/HttpServerConfig.php_arginfo.h +++ b/stubs/HttpServerConfig.php_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit HttpServerConfig.php.stub.php instead. - * Stub hash: 3f74222f92482fa79cba17174f12fd9f023a317f */ + * Stub hash: 4d389377f8768c3f1faf3593872627ba44c80676 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_TrueAsync_HttpServerConfig___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, host, IS_STRING, 1, "null") @@ -43,6 +43,17 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_TrueAsync_HttpServerConfig ZEND_ARG_OBJ_INFO(0, bootloader, Closure, 1) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_TrueAsync_HttpServerConfig_enableHotReload, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, watchPaths, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, extensions, IS_ARRAY, 0, "[\'php\']") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, debounceMs, IS_LONG, 0, "300") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, maxHoldMs, IS_LONG, 0, "2000") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_TrueAsync_HttpServerConfig_enableReloadOnSignal, 0, 0, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, enabled, _IS_BOOL, 0, "true") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_TrueAsync_HttpServerConfig_getBootloader, 0, 0, Closure, 1) ZEND_END_ARG_INFO() @@ -289,6 +300,8 @@ ZEND_METHOD(TrueAsync_HttpServerConfig, setBacklog); ZEND_METHOD(TrueAsync_HttpServerConfig, setWorkers); ZEND_METHOD(TrueAsync_HttpServerConfig, getWorkers); ZEND_METHOD(TrueAsync_HttpServerConfig, setBootloader); +ZEND_METHOD(TrueAsync_HttpServerConfig, enableHotReload); +ZEND_METHOD(TrueAsync_HttpServerConfig, enableReloadOnSignal); ZEND_METHOD(TrueAsync_HttpServerConfig, getBootloader); ZEND_METHOD(TrueAsync_HttpServerConfig, getBacklog); ZEND_METHOD(TrueAsync_HttpServerConfig, setMaxConnections); @@ -404,6 +417,8 @@ static const zend_function_entry class_TrueAsync_HttpServerConfig_methods[] = { ZEND_ME(TrueAsync_HttpServerConfig, setWorkers, arginfo_class_TrueAsync_HttpServerConfig_setWorkers, ZEND_ACC_PUBLIC) ZEND_ME(TrueAsync_HttpServerConfig, getWorkers, arginfo_class_TrueAsync_HttpServerConfig_getWorkers, ZEND_ACC_PUBLIC) ZEND_ME(TrueAsync_HttpServerConfig, setBootloader, arginfo_class_TrueAsync_HttpServerConfig_setBootloader, ZEND_ACC_PUBLIC) + ZEND_ME(TrueAsync_HttpServerConfig, enableHotReload, arginfo_class_TrueAsync_HttpServerConfig_enableHotReload, ZEND_ACC_PUBLIC) + ZEND_ME(TrueAsync_HttpServerConfig, enableReloadOnSignal, arginfo_class_TrueAsync_HttpServerConfig_enableReloadOnSignal, ZEND_ACC_PUBLIC) ZEND_ME(TrueAsync_HttpServerConfig, getBootloader, arginfo_class_TrueAsync_HttpServerConfig_getBootloader, ZEND_ACC_PUBLIC) ZEND_ME(TrueAsync_HttpServerConfig, getBacklog, arginfo_class_TrueAsync_HttpServerConfig_getBacklog, ZEND_ACC_PUBLIC) ZEND_ME(TrueAsync_HttpServerConfig, setMaxConnections, arginfo_class_TrueAsync_HttpServerConfig_setMaxConnections, ZEND_ACC_PUBLIC) diff --git a/tests/phpt/server/_free_port.inc b/tests/phpt/server/_free_port.inc index 0579fc9c..b48f8dc5 100644 --- a/tests/phpt/server/_free_port.inc +++ b/tests/phpt/server/_free_port.inc @@ -24,3 +24,31 @@ function tas_free_port(): int return (int) substr($name, strrpos($name, ':') + 1); } + +/* A base port P such that P..P+$count-1 are all bindable — for tests that + * derive sibling ports as $port + N (e.g. a TCP listener next to the H3 UDP + * one). Retries with fresh kernel-assigned bases until a whole span fits. */ +function tas_free_port_span(int $count): int +{ + for ($try = 0; $try < 32; $try++) { + $base = tas_free_port(); + $held = []; + $ok = true; + + for ($i = 0; $i < $count; $i++) { + $h = @stream_socket_server("tcp://127.0.0.1:" . ($base + $i), $e, $m); + if ($h === false) { $ok = false; break; } + $held[] = $h; + } + + foreach ($held as $h) { + fclose($h); + } + + if ($ok) { + return $base; + } + } + + die('skip cannot allocate a free port span'); +} diff --git a/tests/phpt/server/chaos/001-h2-rapid-reset.phpt b/tests/phpt/server/chaos/001-h2-rapid-reset.phpt index 49f38d8b..0ff5e534 100644 --- a/tests/phpt/server/chaos/001-h2-rapid-reset.phpt +++ b/tests/phpt/server/chaos/001-h2-rapid-reset.phpt @@ -26,8 +26,9 @@ use function Async\delay; require __DIR__ . '/../h2/_h2_client.inc'; -$port = 19870 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/chaos/002-h2-continuation-flood.phpt b/tests/phpt/server/chaos/002-h2-continuation-flood.phpt index 2f402f7c..e7edf3c2 100644 --- a/tests/phpt/server/chaos/002-h2-continuation-flood.phpt +++ b/tests/phpt/server/chaos/002-h2-continuation-flood.phpt @@ -27,8 +27,9 @@ use function Async\delay; require __DIR__ . '/../h2/_h2_client.inc'; -$port = 19872 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/compression/010-h1-buffered-gzip.phpt b/tests/phpt/server/compression/010-h1-buffered-gzip.phpt index 36e7322d..01a75438 100644 --- a/tests/phpt/server/compression/010-h1-buffered-gzip.phpt +++ b/tests/phpt/server/compression/010-h1-buffered-gzip.phpt @@ -16,8 +16,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19200 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/011-h1-buffered-skips.phpt b/tests/phpt/server/compression/011-h1-buffered-skips.phpt index c5baae67..4a7ec4d1 100644 --- a/tests/phpt/server/compression/011-h1-buffered-skips.phpt +++ b/tests/phpt/server/compression/011-h1-buffered-skips.phpt @@ -15,8 +15,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19250 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/012-h1-streaming-gzip.phpt b/tests/phpt/server/compression/012-h1-streaming-gzip.phpt index d4dfc8b7..2fe8dbaa 100644 --- a/tests/phpt/server/compression/012-h1-streaming-gzip.phpt +++ b/tests/phpt/server/compression/012-h1-streaming-gzip.phpt @@ -16,8 +16,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19300 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/020-h2-buffered-gzip.phpt b/tests/phpt/server/compression/020-h2-buffered-gzip.phpt index 9f653e9e..2edf48dc 100644 --- a/tests/phpt/server/compression/020-h2-buffered-gzip.phpt +++ b/tests/phpt/server/compression/020-h2-buffered-gzip.phpt @@ -15,8 +15,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19350 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/030-h1-request-gzip-in.phpt b/tests/phpt/server/compression/030-h1-request-gzip-in.phpt index 51aea0fc..da933778 100644 --- a/tests/phpt/server/compression/030-h1-request-gzip-in.phpt +++ b/tests/phpt/server/compression/030-h1-request-gzip-in.phpt @@ -16,8 +16,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19400 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/040-h1-buffered-brotli.phpt b/tests/phpt/server/compression/040-h1-buffered-brotli.phpt index 90e44bb7..1f6cbef5 100644 --- a/tests/phpt/server/compression/040-h1-buffered-brotli.phpt +++ b/tests/phpt/server/compression/040-h1-buffered-brotli.phpt @@ -18,8 +18,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19500 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/041-h1-streaming-brotli.phpt b/tests/phpt/server/compression/041-h1-streaming-brotli.phpt index 28d8dd72..7ba3d83d 100644 --- a/tests/phpt/server/compression/041-h1-streaming-brotli.phpt +++ b/tests/phpt/server/compression/041-h1-streaming-brotli.phpt @@ -18,8 +18,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19700 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/050-h1-buffered-zstd.phpt b/tests/phpt/server/compression/050-h1-buffered-zstd.phpt index 185b7ba2..98c92479 100644 --- a/tests/phpt/server/compression/050-h1-buffered-zstd.phpt +++ b/tests/phpt/server/compression/050-h1-buffered-zstd.phpt @@ -18,8 +18,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19600 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/060-h1-request-malformed.phpt b/tests/phpt/server/compression/060-h1-request-malformed.phpt index 9d154fd7..e97fbcd4 100644 --- a/tests/phpt/server/compression/060-h1-request-malformed.phpt +++ b/tests/phpt/server/compression/060-h1-request-malformed.phpt @@ -19,8 +19,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19800 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/compression/070-encoder-pool-reuse.phpt b/tests/phpt/server/compression/070-encoder-pool-reuse.phpt index d6c5b7d8..ba2009f1 100644 --- a/tests/phpt/server/compression/070-encoder-pool-reuse.phpt +++ b/tests/phpt/server/compression/070-encoder-pool-reuse.phpt @@ -16,8 +16,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19300 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/004-server-start-stop.phpt b/tests/phpt/server/core/004-server-start-stop.phpt index 4b903876..acd1cb1f 100644 --- a/tests/phpt/server/core/004-server-start-stop.phpt +++ b/tests/phpt/server/core/004-server-start-stop.phpt @@ -9,8 +9,9 @@ use TrueAsync\HttpServer; use TrueAsync\HttpServerConfig; use function Async\spawn; -$port = 19900 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/005-server-e2e-simple.phpt b/tests/phpt/server/core/005-server-e2e-simple.phpt index 459c6f42..04b28d07 100644 --- a/tests/phpt/server/core/005-server-e2e-simple.phpt +++ b/tests/phpt/server/core/005-server-e2e-simple.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19800 + getmypid() % 1000; // Random-ish port to avoid conflicts +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/007-server-transfer-handler-dispatch.phpt b/tests/phpt/server/core/007-server-transfer-handler-dispatch.phpt index f7d1f87b..846f2aae 100644 --- a/tests/phpt/server/core/007-server-transfer-handler-dispatch.phpt +++ b/tests/phpt/server/core/007-server-transfer-handler-dispatch.phpt @@ -33,8 +33,9 @@ use Async\ThreadPool; use function Async\spawn; use function Async\await; -$port = 19840 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $coro = spawn(function () use ($port) { $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/core/008-backpressure-telemetry.phpt b/tests/phpt/server/core/008-backpressure-telemetry.phpt index 4d6ef8c8..795074f7 100644 --- a/tests/phpt/server/core/008-backpressure-telemetry.phpt +++ b/tests/phpt/server/core/008-backpressure-telemetry.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19700 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/009-http-exception-from-handler.phpt b/tests/phpt/server/core/009-http-exception-from-handler.phpt index f54c089b..df0ac987 100644 --- a/tests/phpt/server/core/009-http-exception-from-handler.phpt +++ b/tests/phpt/server/core/009-http-exception-from-handler.phpt @@ -11,7 +11,9 @@ use TrueAsync\HttpException; use function Async\spawn; use function Async\await; -$port = 19820 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); diff --git a/tests/phpt/server/core/012-drain-telemetry-reset.phpt b/tests/phpt/server/core/012-drain-telemetry-reset.phpt index 1a0a417a..e5fbf49b 100644 --- a/tests/phpt/server/core/012-drain-telemetry-reset.phpt +++ b/tests/phpt/server/core/012-drain-telemetry-reset.phpt @@ -16,8 +16,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19845 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/core/014-log-emit-on-start-stop.phpt b/tests/phpt/server/core/014-log-emit-on-start-stop.phpt index 6e05ff7f..67182083 100644 --- a/tests/phpt/server/core/014-log-emit-on-start-stop.phpt +++ b/tests/phpt/server/core/014-log-emit-on-start-stop.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use TrueAsync\LogSeverity; use function Async\spawn; -$port = 19960 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port_span(2); $logfile = sys_get_temp_dir() . "/php-http-server-091-" . getmypid() . ".log"; @unlink($logfile); $logfh = fopen($logfile, "w+b"); diff --git a/tests/phpt/server/core/015-log-multipart-debug.phpt b/tests/phpt/server/core/015-log-multipart-debug.phpt index ebcea824..c94e6d41 100644 --- a/tests/phpt/server/core/015-log-multipart-debug.phpt +++ b/tests/phpt/server/core/015-log-multipart-debug.phpt @@ -11,7 +11,9 @@ use TrueAsync\LogSeverity; use function Async\spawn; use function Async\await; -$port = 19880 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port_span(2); $logfile = sys_get_temp_dir() . "/php-http-server-092-" . getmypid() . ".log"; @unlink($logfile); $logfh = fopen($logfile, "w+b"); diff --git a/tests/phpt/server/core/016-log-tls-warn.phpt b/tests/phpt/server/core/016-log-tls-warn.phpt index 8b45e289..e5f695e0 100644 --- a/tests/phpt/server/core/016-log-tls-warn.phpt +++ b/tests/phpt/server/core/016-log-tls-warn.phpt @@ -23,7 +23,9 @@ $cert = $tmp . '/cert.pem'; $key = $tmp . '/key.pem'; if (!tls_gen_cert($key, $cert)) { echo "cert generation failed\n"; exit(1); } -$port = 19940 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $logfile = sys_get_temp_dir() . "/php-http-server-093-" . getmypid() . ".log"; @unlink($logfile); $logfh = fopen($logfile, "w+b"); diff --git a/tests/phpt/server/core/018-log-off-no-overhead.phpt b/tests/phpt/server/core/018-log-off-no-overhead.phpt index 218e385a..87a18535 100644 --- a/tests/phpt/server/core/018-log-off-no-overhead.phpt +++ b/tests/phpt/server/core/018-log-off-no-overhead.phpt @@ -17,6 +17,8 @@ if (getenv('GITHUB_ACTIONS') !== false || getenv('SKIP_PERF_TESTS') !== false) { ?> --FILE-- addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/019-parse-error-503-oom.phpt b/tests/phpt/server/core/019-parse-error-503-oom.phpt index 2957f8aa..bc903fc4 100644 --- a/tests/phpt/server/core/019-parse-error-503-oom.phpt +++ b/tests/phpt/server/core/019-parse-error-503-oom.phpt @@ -22,8 +22,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19900 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setMaxBodySize(128 * 1024 * 1024) /* allow big bodies in principle */ diff --git a/tests/phpt/server/core/020-builtin-worker-pool.phpt b/tests/phpt/server/core/020-builtin-worker-pool.phpt index a5c86020..321064ec 100644 --- a/tests/phpt/server/core/020-builtin-worker-pool.phpt +++ b/tests/phpt/server/core/020-builtin-worker-pool.phpt @@ -25,7 +25,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19880 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $workers = 2; $config = (new HttpServerConfig()) diff --git a/tests/phpt/server/core/020-telemetry-trace-context.phpt b/tests/phpt/server/core/020-telemetry-trace-context.phpt index 3250f266..56a6a458 100644 --- a/tests/phpt/server/core/020-telemetry-trace-context.phpt +++ b/tests/phpt/server/core/020-telemetry-trace-context.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19770 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/021-bootloader.phpt b/tests/phpt/server/core/021-bootloader.phpt index 2f10f1c2..43e35b89 100644 --- a/tests/phpt/server/core/021-bootloader.phpt +++ b/tests/phpt/server/core/021-bootloader.phpt @@ -20,7 +20,9 @@ use TrueAsync\HttpServer; use TrueAsync\HttpServerConfig; use function Async\spawn; -$port = 19980 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $workers = 2; $config = (new HttpServerConfig()) diff --git a/tests/phpt/server/core/021-response-headers-api.phpt b/tests/phpt/server/core/021-response-headers-api.phpt index 0d27ce21..b5e432be 100644 --- a/tests/phpt/server/core/021-response-headers-api.phpt +++ b/tests/phpt/server/core/021-response-headers-api.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19200 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5)); diff --git a/tests/phpt/server/core/022-response-status-api.phpt b/tests/phpt/server/core/022-response-status-api.phpt index 50f014e0..ad326e28 100644 --- a/tests/phpt/server/core/022-response-status-api.phpt +++ b/tests/phpt/server/core/022-response-status-api.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19210 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5)); diff --git a/tests/phpt/server/core/023-response-body-api.phpt b/tests/phpt/server/core/023-response-body-api.phpt index 2d48f750..56b9002e 100644 --- a/tests/phpt/server/core/023-response-body-api.phpt +++ b/tests/phpt/server/core/023-response-body-api.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19220 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5)); diff --git a/tests/phpt/server/core/024-response-helpers.phpt b/tests/phpt/server/core/024-response-helpers.phpt index 031109d4..753def76 100644 --- a/tests/phpt/server/core/024-response-helpers.phpt +++ b/tests/phpt/server/core/024-response-helpers.phpt @@ -13,8 +13,9 @@ use function Async\await; // One server, three requests against three different paths — each path // triggers a different helper. Server stops after the third response. -$port = 19230 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5)); diff --git a/tests/phpt/server/core/025-response-state-api.phpt b/tests/phpt/server/core/025-response-state-api.phpt index e77f4cdc..b58c3f18 100644 --- a/tests/phpt/server/core/025-response-state-api.phpt +++ b/tests/phpt/server/core/025-response-state-api.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19240 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5)); diff --git a/tests/phpt/server/core/026-response-trailers-api.phpt b/tests/phpt/server/core/026-response-trailers-api.phpt index 38ae4a36..64b65a59 100644 --- a/tests/phpt/server/core/026-response-trailers-api.phpt +++ b/tests/phpt/server/core/026-response-trailers-api.phpt @@ -14,8 +14,9 @@ use function Async\await; // drops trailers silently per the stub doc — no need for an H2 client // to verify the in-handler getters). -$port = 19250 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5)); diff --git a/tests/phpt/server/core/027-response-validation.phpt b/tests/phpt/server/core/027-response-validation.phpt index b654d477..c3d23726 100644 --- a/tests/phpt/server/core/027-response-validation.phpt +++ b/tests/phpt/server/core/027-response-validation.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19260 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5)); diff --git a/tests/phpt/server/core/030-response-json.phpt b/tests/phpt/server/core/030-response-json.phpt index 3154f70f..5cea3a69 100644 --- a/tests/phpt/server/core/030-response-json.phpt +++ b/tests/phpt/server/core/030-response-json.phpt @@ -15,8 +15,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19900 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/036-response-reason-phrases.phpt b/tests/phpt/server/core/036-response-reason-phrases.phpt index 9313a187..95b96df6 100644 --- a/tests/phpt/server/core/036-response-reason-phrases.phpt +++ b/tests/phpt/server/core/036-response-reason-phrases.phpt @@ -55,7 +55,9 @@ $codes = [ 299 => 'Unknown', ]; -$port = 19920 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); diff --git a/tests/phpt/server/core/037-http-methods-end-to-end.phpt b/tests/phpt/server/core/037-http-methods-end-to-end.phpt index 73b7e6d5..01176748 100644 --- a/tests/phpt/server/core/037-http-methods-end-to-end.phpt +++ b/tests/phpt/server/core/037-http-methods-end-to-end.phpt @@ -15,7 +15,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 20000 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); diff --git a/tests/phpt/server/core/040-request-scope-isolation.phpt b/tests/phpt/server/core/040-request-scope-isolation.phpt index 421f2004..a0e3b3b0 100644 --- a/tests/phpt/server/core/040-request-scope-isolation.phpt +++ b/tests/phpt/server/core/040-request-scope-isolation.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 20100 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/041-current-context-child-coroutines.phpt b/tests/phpt/server/core/041-current-context-child-coroutines.phpt index ef4f1b97..03d429ea 100644 --- a/tests/phpt/server/core/041-current-context-child-coroutines.phpt +++ b/tests/phpt/server/core/041-current-context-child-coroutines.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 20200 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/042-request-context.phpt b/tests/phpt/server/core/042-request-context.phpt index dbdba66b..f518b3b8 100644 --- a/tests/phpt/server/core/042-request-context.phpt +++ b/tests/phpt/server/core/042-request-context.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 20300 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/043-request-context-equals-scope-context.phpt b/tests/phpt/server/core/043-request-context-equals-scope-context.phpt index accf1b8b..a1984192 100644 --- a/tests/phpt/server/core/043-request-context-equals-scope-context.phpt +++ b/tests/phpt/server/core/043-request-context-equals-scope-context.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 20340 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/044-worker-pool-bound-handler.phpt b/tests/phpt/server/core/044-worker-pool-bound-handler.phpt index 652cbf69..082fd6d3 100644 --- a/tests/phpt/server/core/044-worker-pool-bound-handler.phpt +++ b/tests/phpt/server/core/044-worker-pool-bound-handler.phpt @@ -42,7 +42,9 @@ PHP; eval($handlerClassSrc); // define BoundHandler in this (parent) thread -$port = 19840 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $workers = 2; $config = (new HttpServerConfig()) diff --git a/tests/phpt/server/core/045-request-context-h2.phpt b/tests/phpt/server/core/045-request-context-h2.phpt index b49f14c6..1ab2612b 100644 --- a/tests/phpt/server/core/045-request-context-h2.phpt +++ b/tests/phpt/server/core/045-request-context-h2.phpt @@ -30,8 +30,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19790 + getmypid() % 60; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/045-shutdown-drains-inflight.phpt b/tests/phpt/server/core/045-shutdown-drains-inflight.phpt index 0398f56a..5a10cb46 100644 --- a/tests/phpt/server/core/045-shutdown-drains-inflight.phpt +++ b/tests/phpt/server/core/045-shutdown-drains-inflight.phpt @@ -15,8 +15,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 20180 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/core/046-request-context-h3.phpt b/tests/phpt/server/core/046-request-context-h3.phpt index bca8f840..d0981890 100644 --- a/tests/phpt/server/core/046-request-context-h3.phpt +++ b/tests/phpt/server/core/046-request-context-h3.phpt @@ -37,8 +37,9 @@ exec(sprintf('openssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes ' escapeshellarg($key), escapeshellarg($cert)), $_, $rc); if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } -$port = 20360 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/core/047-request-context-concurrent.phpt b/tests/phpt/server/core/047-request-context-concurrent.phpt index 51822fe1..ba1033ef 100644 --- a/tests/phpt/server/core/047-request-context-concurrent.phpt +++ b/tests/phpt/server/core/047-request-context-concurrent.phpt @@ -26,8 +26,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 20120 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/048-request-context-first-accessor.phpt b/tests/phpt/server/core/048-request-context-first-accessor.phpt index cf7629cf..e9e31c0f 100644 --- a/tests/phpt/server/core/048-request-context-first-accessor.phpt +++ b/tests/phpt/server/core/048-request-context-first-accessor.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 20340 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/049-request-scope-setter.phpt b/tests/phpt/server/core/049-request-scope-setter.phpt index 3f20196a..497b0925 100644 --- a/tests/phpt/server/core/049-request-scope-setter.phpt +++ b/tests/phpt/server/core/049-request-scope-setter.phpt @@ -25,7 +25,8 @@ echo "set true -> " . (int)$c->setRequestScope(true)->isRequestScope() . "\n"; echo "chainable=" . (int)($c->setRequestScope(false) instanceof HttpServerConfig) . "\n"; /* 1 */ /* === Part B: locked after the config is handed to a server === */ -$port = 19965 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $cfg = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setRequestScope(false) /* serve with the per-request scope OFF */ diff --git a/tests/phpt/server/core/050-request-scope-workers.phpt b/tests/phpt/server/core/050-request-scope-workers.phpt index 9ebb2bb4..b8dfa82d 100644 --- a/tests/phpt/server/core/050-request-scope-workers.phpt +++ b/tests/phpt/server/core/050-request-scope-workers.phpt @@ -23,8 +23,9 @@ use TrueAsync\HttpServer; use TrueAsync\HttpServerConfig; use function Async\spawn; -$port = 19985 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/core/053-hot-reload-watcher.phpt b/tests/phpt/server/core/053-hot-reload-watcher.phpt new file mode 100644 index 00000000..701453fe --- /dev/null +++ b/tests/phpt/server/core/053-hot-reload-watcher.phpt @@ -0,0 +1,77 @@ +--TEST-- +HttpServerConfig::enableHotReload — file change triggers a pool rotation, new code served (#93) +--EXTENSIONS-- +true_async_server +true_async +--SKIPIF-- +/dev/null')) die('skip curl CLI not available'); +?> +--FILE-- +addListener('127.0.0.1', $port) + ->setReadTimeout(5) + ->setWriteTimeout(5) + ->setWorkers(2) + ->setBootloader(static function () use ($code): void { + define('APP_V', (string) include $code); + }) + ->enableHotReload([$dir], ['php'], 150, 1000); + +$server = new HttpServer($config); +$server->addHttpHandler(function ($req, $res) { + $res->setStatusCode(200)->setBody(APP_V); +}); + +spawn(function () use ($port, $code, $dir) { + $curl = static fn (): string => (string) shell_exec(sprintf( + 'curl -s --max-time 2 http://127.0.0.1:%d/ 2>/dev/null', $port)); + + $v1 = ''; + for ($i = 0; $i < 50 && $v1 !== 'v1'; $i++) { + usleep(200000); + $v1 = $curl(); + } + + echo "before=", $v1, "\n"; + + /* The only action: rewrite the watched file. */ + file_put_contents($code, "start(); +?> +--EXPECTF-- +before=v1 +%A +after=v2%A diff --git a/tests/phpt/server/core/054-hot-reload-sighup.phpt b/tests/phpt/server/core/054-hot-reload-sighup.phpt new file mode 100644 index 00000000..4625e06b --- /dev/null +++ b/tests/phpt/server/core/054-hot-reload-sighup.phpt @@ -0,0 +1,79 @@ +--TEST-- +HttpServerConfig::enableReloadOnSignal — SIGHUP rotates the pool, serving continues (#93) +--EXTENSIONS-- +true_async_server +true_async +--SKIPIF-- +/dev/null')) die('skip curl CLI not available'); +?> +--FILE-- +addListener('127.0.0.1', $port) + ->setReadTimeout(5) + ->setWriteTimeout(5) + ->setWorkers(2) + ->setBootloader(static function () use ($bfile): void { + file_put_contents($bfile, 'x', FILE_APPEND | LOCK_EX); + }) + ->enableReloadOnSignal(); + +$server = new HttpServer($config); +$server->addHttpHandler(function ($req, $res) { + $res->setStatusCode(200)->setBody('pong'); +}); + +spawn(function () use ($port, $bfile) { + $curl = static fn (): string => (string) shell_exec(sprintf( + 'curl -s --max-time 2 http://127.0.0.1:%d/ 2>/dev/null', $port)); + + $up = ''; + for ($i = 0; $i < 50 && $up !== 'pong'; $i++) { + usleep(200000); + $up = $curl(); + } + + echo "up=", $up, "\n"; + + posix_kill(getmypid(), SIGHUP); + + /* Rotation done when both replacements have re-run the bootloader. */ + $boots = 0; + for ($i = 0; $i < 100 && $boots < 4; $i++) { + usleep(200000); + $boots = strlen((string) @file_get_contents($bfile)); + } + + echo "boots=", $boots, "\n"; + + $again = ''; + for ($i = 0; $i < 50 && $again !== 'pong'; $i++) { + usleep(200000); + $again = $curl(); + } + + echo "served_after=", $again, "\n"; + + @unlink($bfile); + posix_kill(getmypid(), SIGKILL); +}); + +$server->start(); +?> +--EXPECTF-- +up=pong +%A +boots=4 +served_after=pong%A diff --git a/tests/phpt/server/h1/001-pipelining.phpt b/tests/phpt/server/h1/001-pipelining.phpt index dd980e0b..aa88f42e 100644 --- a/tests/phpt/server/h1/001-pipelining.phpt +++ b/tests/phpt/server/h1/001-pipelining.phpt @@ -18,8 +18,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19650 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/h1/002-parse-error-414.phpt b/tests/phpt/server/h1/002-parse-error-414.phpt index 70930835..3cedd88b 100644 --- a/tests/phpt/server/h1/002-parse-error-414.phpt +++ b/tests/phpt/server/h1/002-parse-error-414.phpt @@ -10,8 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19817 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/h1/003-parse-error-431.phpt b/tests/phpt/server/h1/003-parse-error-431.phpt index 849a18c9..d1e54e07 100644 --- a/tests/phpt/server/h1/003-parse-error-431.phpt +++ b/tests/phpt/server/h1/003-parse-error-431.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19818 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/server/h1/004-parse-error-400.phpt b/tests/phpt/server/h1/004-parse-error-400.phpt index 064373ce..26827493 100644 --- a/tests/phpt/server/h1/004-parse-error-400.phpt +++ b/tests/phpt/server/h1/004-parse-error-400.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19819 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/server/h1/005-parse-error-mid-stream-body.phpt b/tests/phpt/server/h1/005-parse-error-mid-stream-body.phpt index 161c4435..58852eb2 100644 --- a/tests/phpt/server/h1/005-parse-error-mid-stream-body.phpt +++ b/tests/phpt/server/h1/005-parse-error-mid-stream-body.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19821 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10)->setWriteTimeout(10); diff --git a/tests/phpt/server/h1/006-smuggling-cl-te.phpt b/tests/phpt/server/h1/006-smuggling-cl-te.phpt index 57cf4065..5d173414 100644 --- a/tests/phpt/server/h1/006-smuggling-cl-te.phpt +++ b/tests/phpt/server/h1/006-smuggling-cl-te.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19822 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/server/h1/007-smuggling-dup-cl.phpt b/tests/phpt/server/h1/007-smuggling-dup-cl.phpt index efd8911d..635e6515 100644 --- a/tests/phpt/server/h1/007-smuggling-dup-cl.phpt +++ b/tests/phpt/server/h1/007-smuggling-dup-cl.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19823 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/server/h1/008-invalid-content-length.phpt b/tests/phpt/server/h1/008-invalid-content-length.phpt index 033f96a8..6160a316 100644 --- a/tests/phpt/server/h1/008-invalid-content-length.phpt +++ b/tests/phpt/server/h1/008-invalid-content-length.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19824 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/server/h1/009-obs-fold-rejected.phpt b/tests/phpt/server/h1/009-obs-fold-rejected.phpt index 1f29cc15..92f5a9b6 100644 --- a/tests/phpt/server/h1/009-obs-fold-rejected.phpt +++ b/tests/phpt/server/h1/009-obs-fold-rejected.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19825 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/server/h1/010-drain-proactive-age-h1.phpt b/tests/phpt/server/h1/010-drain-proactive-age-h1.phpt index 792ece87..e98d6d16 100644 --- a/tests/phpt/server/h1/010-drain-proactive-age-h1.phpt +++ b/tests/phpt/server/h1/010-drain-proactive-age-h1.phpt @@ -16,8 +16,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19840 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h1/011-drain-reactive-hardcap-h1.phpt b/tests/phpt/server/h1/011-drain-reactive-hardcap-h1.phpt index 42530176..06724ef1 100644 --- a/tests/phpt/server/h1/011-drain-reactive-hardcap-h1.phpt +++ b/tests/phpt/server/h1/011-drain-reactive-hardcap-h1.phpt @@ -16,8 +16,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19841 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h1/012-drain-cooldown-blocks-refire.phpt b/tests/phpt/server/h1/012-drain-cooldown-blocks-refire.phpt index f55e0a43..2ff1fee5 100644 --- a/tests/phpt/server/h1/012-drain-cooldown-blocks-refire.phpt +++ b/tests/phpt/server/h1/012-drain-cooldown-blocks-refire.phpt @@ -16,8 +16,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19846 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h1/013-h1-chunked-basic.phpt b/tests/phpt/server/h1/013-h1-chunked-basic.phpt index e2e00a3b..c261d836 100644 --- a/tests/phpt/server/h1/013-h1-chunked-basic.phpt +++ b/tests/phpt/server/h1/013-h1-chunked-basic.phpt @@ -14,8 +14,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19820 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h1/014-h1-sse-pattern.phpt b/tests/phpt/server/h1/014-h1-sse-pattern.phpt index 305ac13f..60e28ace 100644 --- a/tests/phpt/server/h1/014-h1-sse-pattern.phpt +++ b/tests/phpt/server/h1/014-h1-sse-pattern.phpt @@ -14,8 +14,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19840 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h1/015-h1-stream-edge-cases.phpt b/tests/phpt/server/h1/015-h1-stream-edge-cases.phpt index ac56ea11..cbc7a43b 100644 --- a/tests/phpt/server/h1/015-h1-stream-edge-cases.phpt +++ b/tests/phpt/server/h1/015-h1-stream-edge-cases.phpt @@ -14,8 +14,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19340 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10)->setWriteTimeout(10)); diff --git a/tests/phpt/server/h1/016-h1-stream-after-send-many.phpt b/tests/phpt/server/h1/016-h1-stream-after-send-many.phpt index b945ed70..465b07fb 100644 --- a/tests/phpt/server/h1/016-h1-stream-after-send-many.phpt +++ b/tests/phpt/server/h1/016-h1-stream-after-send-many.phpt @@ -14,8 +14,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19350 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10)->setWriteTimeout(10)); diff --git a/tests/phpt/server/h1/017-admission-reject-h1.phpt b/tests/phpt/server/h1/017-admission-reject-h1.phpt index a16599b7..1d450eb1 100644 --- a/tests/phpt/server/h1/017-admission-reject-h1.phpt +++ b/tests/phpt/server/h1/017-admission-reject-h1.phpt @@ -23,8 +23,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19910 + getmypid() % 80; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setMaxConnections(32) diff --git a/tests/phpt/server/h1/018-tcp-fragmentation.phpt b/tests/phpt/server/h1/018-tcp-fragmentation.phpt index 07b96ca8..3bac072e 100644 --- a/tests/phpt/server/h1/018-tcp-fragmentation.phpt +++ b/tests/phpt/server/h1/018-tcp-fragmentation.phpt @@ -24,8 +24,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19850 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/h1/019-host-validation.phpt b/tests/phpt/server/h1/019-host-validation.phpt index 0a7c9298..b0fbe313 100644 --- a/tests/phpt/server/h1/019-host-validation.phpt +++ b/tests/phpt/server/h1/019-host-validation.phpt @@ -22,7 +22,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19824 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/server/h1/020-method-and-response-conformance.phpt b/tests/phpt/server/h1/020-method-and-response-conformance.phpt index 4650b2c4..a8f2c01c 100644 --- a/tests/phpt/server/h1/020-method-and-response-conformance.phpt +++ b/tests/phpt/server/h1/020-method-and-response-conformance.phpt @@ -21,7 +21,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19825 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5)); $server->addHttpHandler(function ($req, $res) { diff --git a/tests/phpt/server/h1/021-request-target-hardening.phpt b/tests/phpt/server/h1/021-request-target-hardening.phpt index 7793db39..4808d577 100644 --- a/tests/phpt/server/h1/021-request-target-hardening.phpt +++ b/tests/phpt/server/h1/021-request-target-hardening.phpt @@ -22,7 +22,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19826 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port)->setReadTimeout(5)->setWriteTimeout(5)); $server->addHttpHandler(function ($req, $res) { diff --git a/tests/phpt/server/h1/022-h1-sse-api.phpt b/tests/phpt/server/h1/022-h1-sse-api.phpt index c735100c..92c0e735 100644 --- a/tests/phpt/server/h1/022-h1-sse-api.phpt +++ b/tests/phpt/server/h1/022-h1-sse-api.phpt @@ -24,8 +24,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19880 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h1/023-h1-sse-validation.phpt b/tests/phpt/server/h1/023-h1-sse-validation.phpt index 9c1e6574..4e4f5fbb 100644 --- a/tests/phpt/server/h1/023-h1-sse-validation.phpt +++ b/tests/phpt/server/h1/023-h1-sse-validation.phpt @@ -20,8 +20,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19680 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h1/024-h1-sse-misuse.phpt b/tests/phpt/server/h1/024-h1-sse-misuse.phpt index 88b946e2..9f57e128 100644 --- a/tests/phpt/server/h1/024-h1-sse-misuse.phpt +++ b/tests/phpt/server/h1/024-h1-sse-misuse.phpt @@ -23,8 +23,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19700 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h1/025-h1-sse-client-disconnect.phpt b/tests/phpt/server/h1/025-h1-sse-client-disconnect.phpt index 83bd4c66..ab8f0016 100644 --- a/tests/phpt/server/h1/025-h1-sse-client-disconnect.phpt +++ b/tests/phpt/server/h1/025-h1-sse-client-disconnect.phpt @@ -22,8 +22,9 @@ use TrueAsync\HttpException; use function Async\spawn; use function Async\await; -$port = 19780 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h1/050-stream-request-body.phpt b/tests/phpt/server/h1/050-stream-request-body.phpt index 1c4a0a30..fe95017c 100644 --- a/tests/phpt/server/h1/050-stream-request-body.phpt +++ b/tests/phpt/server/h1/050-stream-request-body.phpt @@ -14,8 +14,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19920 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h2/001-h2c-get.phpt b/tests/phpt/server/h2/001-h2c-get.phpt index cc433a83..5b44e76f 100644 --- a/tests/phpt/server/h2/001-h2c-get.phpt +++ b/tests/phpt/server/h2/001-h2c-get.phpt @@ -24,8 +24,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19700 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/h2/002-h2c-post.phpt b/tests/phpt/server/h2/002-h2c-post.phpt index 4b3ec49d..4d3aef10 100644 --- a/tests/phpt/server/h2/002-h2c-post.phpt +++ b/tests/phpt/server/h2/002-h2c-post.phpt @@ -21,8 +21,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19710 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/h2/003-h2c-trailers.phpt b/tests/phpt/server/h2/003-h2c-trailers.phpt index f0db5cd3..aadf7dee 100644 --- a/tests/phpt/server/h2/003-h2c-trailers.phpt +++ b/tests/phpt/server/h2/003-h2c-trailers.phpt @@ -21,8 +21,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19720 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/h2/004-h2-tls-get.phpt b/tests/phpt/server/h2/004-h2-tls-get.phpt index dbf5a6ab..21fc9be4 100644 --- a/tests/phpt/server/h2/004-h2-tls-get.phpt +++ b/tests/phpt/server/h2/004-h2-tls-get.phpt @@ -29,8 +29,9 @@ $cert = $tmp_dir . '/cert.pem'; $key = $tmp_dir . '/key.pem'; if (!h2_gen_cert($key, $cert)) { echo "cert gen failed\n"; exit(1); } -$port = 19970 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/h2/005-h2-tls-fallback.phpt b/tests/phpt/server/h2/005-h2-tls-fallback.phpt index cf1b578a..b2178bb7 100644 --- a/tests/phpt/server/h2/005-h2-tls-fallback.phpt +++ b/tests/phpt/server/h2/005-h2-tls-fallback.phpt @@ -29,8 +29,9 @@ $cert = $tmp_dir . '/cert.pem'; $key = $tmp_dir . '/key.pem'; if (!h2_gen_cert($key, $cert)) { echo "cert gen failed\n"; exit(1); } -$port = 19990 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/h2/006-h2c-multiplex.phpt b/tests/phpt/server/h2/006-h2c-multiplex.phpt index c3e49cab..2e11142a 100644 --- a/tests/phpt/server/h2/006-h2c-multiplex.phpt +++ b/tests/phpt/server/h2/006-h2c-multiplex.phpt @@ -26,8 +26,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19730 + getmypid() % 60; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/server/h2/007-h2c-peer-reset-cancel.phpt b/tests/phpt/server/h2/007-h2c-peer-reset-cancel.phpt index f6e99e98..36e21aa2 100644 --- a/tests/phpt/server/h2/007-h2c-peer-reset-cancel.phpt +++ b/tests/phpt/server/h2/007-h2c-peer-reset-cancel.phpt @@ -31,8 +31,9 @@ use function Async\delay; require __DIR__ . '/_h2_client.inc'; -$port = 19830 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h2/008-h2c-peer-reset-await-body.phpt b/tests/phpt/server/h2/008-h2c-peer-reset-await-body.phpt index f6b7ddd2..5bbe3ff6 100644 --- a/tests/phpt/server/h2/008-h2c-peer-reset-await-body.phpt +++ b/tests/phpt/server/h2/008-h2c-peer-reset-await-body.phpt @@ -29,8 +29,9 @@ use function Async\delay; require __DIR__ . '/_h2_client.inc'; -$port = 19831 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h2/009-h2-h2spec-gate.phpt b/tests/phpt/server/h2/009-h2-h2spec-gate.phpt index 00b44350..45575c66 100644 --- a/tests/phpt/server/h2/009-h2-h2spec-gate.phpt +++ b/tests/phpt/server/h2/009-h2-h2spec-gate.phpt @@ -41,9 +41,9 @@ if ($h2spec === '' && is_executable($home = (string)getenv('HOME') . '/.local/bi $h2spec = $home; } -$port = 19500 + getmypid() % 200; +require_once __DIR__ . '/../_free_port.inc'; -// PHP_BINARY would re-enter THIS test under the phpt runner; use its +$port = tas_free_port(); // caller chain via posix or fall back to reading /proc/self/exe. $php = PHP_BINARY; diff --git a/tests/phpt/server/h2/010-drain-proactive-age-h2-goaway.phpt b/tests/phpt/server/h2/010-drain-proactive-age-h2-goaway.phpt index e93afef9..8ba43ee1 100644 --- a/tests/phpt/server/h2/010-drain-proactive-age-h2-goaway.phpt +++ b/tests/phpt/server/h2/010-drain-proactive-age-h2-goaway.phpt @@ -22,8 +22,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19842 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h2/011-drain-h2-multiplex-completes.phpt b/tests/phpt/server/h2/011-drain-h2-multiplex-completes.phpt index a71e8eff..c8dc0301 100644 --- a/tests/phpt/server/h2/011-drain-h2-multiplex-completes.phpt +++ b/tests/phpt/server/h2/011-drain-h2-multiplex-completes.phpt @@ -21,8 +21,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19843 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h2/013-h2-streaming-basic.phpt b/tests/phpt/server/h2/013-h2-streaming-basic.phpt index 609c145d..b7fc636f 100644 --- a/tests/phpt/server/h2/013-h2-streaming-basic.phpt +++ b/tests/phpt/server/h2/013-h2-streaming-basic.phpt @@ -23,8 +23,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19850 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h2/014-h2-streaming-large.phpt b/tests/phpt/server/h2/014-h2-streaming-large.phpt index b38229f7..278c4af2 100644 --- a/tests/phpt/server/h2/014-h2-streaming-large.phpt +++ b/tests/phpt/server/h2/014-h2-streaming-large.phpt @@ -21,8 +21,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19851 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h2/015-h2-streaming-backpressure.phpt b/tests/phpt/server/h2/015-h2-streaming-backpressure.phpt index 0343b2a8..7bbaf605 100644 --- a/tests/phpt/server/h2/015-h2-streaming-backpressure.phpt +++ b/tests/phpt/server/h2/015-h2-streaming-backpressure.phpt @@ -25,8 +25,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19870 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(15) diff --git a/tests/phpt/server/h2/016-h2-streaming-telemetry.phpt b/tests/phpt/server/h2/016-h2-streaming-telemetry.phpt index 15b453c9..17f763a6 100644 --- a/tests/phpt/server/h2/016-h2-streaming-telemetry.phpt +++ b/tests/phpt/server/h2/016-h2-streaming-telemetry.phpt @@ -20,8 +20,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19910 + getmypid() % 80; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h2/017-h2-streaming-cancel.phpt b/tests/phpt/server/h2/017-h2-streaming-cancel.phpt index f45d890e..46a413a9 100644 --- a/tests/phpt/server/h2/017-h2-streaming-cancel.phpt +++ b/tests/phpt/server/h2/017-h2-streaming-cancel.phpt @@ -32,8 +32,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19860 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h2/018-h2-only-listener.phpt b/tests/phpt/server/h2/018-h2-only-listener.phpt index ae79752f..0118a1cb 100644 --- a/tests/phpt/server/h2/018-h2-only-listener.phpt +++ b/tests/phpt/server/h2/018-h2-only-listener.phpt @@ -26,8 +26,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19880 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h2/019-h2-stream-telemetry.phpt b/tests/phpt/server/h2/019-h2-stream-telemetry.phpt index 2f3ecff4..d5147433 100644 --- a/tests/phpt/server/h2/019-h2-stream-telemetry.phpt +++ b/tests/phpt/server/h2/019-h2-stream-telemetry.phpt @@ -16,8 +16,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19900 + getmypid() % 80; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h2/020-admission-reject-h2.phpt b/tests/phpt/server/h2/020-admission-reject-h2.phpt index 924167ed..9e35172f 100644 --- a/tests/phpt/server/h2/020-admission-reject-h2.phpt +++ b/tests/phpt/server/h2/020-admission-reject-h2.phpt @@ -33,8 +33,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19980 + getmypid() % 10; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setMaxConnections(32) diff --git a/tests/phpt/server/h2/021-h2-tls-multiplex.phpt b/tests/phpt/server/h2/021-h2-tls-multiplex.phpt index 85d02505..4bfc4bfe 100644 --- a/tests/phpt/server/h2/021-h2-tls-multiplex.phpt +++ b/tests/phpt/server/h2/021-h2-tls-multiplex.phpt @@ -46,7 +46,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19800 + getmypid() % 200; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $body_bytes = 256 * 1024; $config = (new HttpServerConfig()) diff --git a/tests/phpt/server/h2/022-h2-streaming-ring-full.phpt b/tests/phpt/server/h2/022-h2-streaming-ring-full.phpt index f350982e..66469606 100644 --- a/tests/phpt/server/h2/022-h2-streaming-ring-full.phpt +++ b/tests/phpt/server/h2/022-h2-streaming-ring-full.phpt @@ -30,8 +30,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19890 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $CHUNK_SZ = 8192; $N_CHUNKS = 64; diff --git a/tests/phpt/server/h2/023-h2-streaming-sendable.phpt b/tests/phpt/server/h2/023-h2-streaming-sendable.phpt index b1bb8dbf..d7f7478c 100644 --- a/tests/phpt/server/h2/023-h2-streaming-sendable.phpt +++ b/tests/phpt/server/h2/023-h2-streaming-sendable.phpt @@ -25,8 +25,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19920 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $CHUNK_SZ = 8192; $N_CHUNKS = 48; diff --git a/tests/phpt/server/h2/024-h2-tls-large-body.phpt b/tests/phpt/server/h2/024-h2-tls-large-body.phpt index ea4d9a8f..bb5720b8 100644 --- a/tests/phpt/server/h2/024-h2-tls-large-body.phpt +++ b/tests/phpt/server/h2/024-h2-tls-large-body.phpt @@ -28,7 +28,9 @@ $cert = $tmp_dir . '/cert.pem'; $key = $tmp_dir . '/key.pem'; if (!h2_gen_cert($key, $cert)) { echo "cert gen failed\n"; exit(1); } -$port = 19890 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $size = 256 * 1024; $body = str_repeat('A', $size); diff --git a/tests/phpt/server/h2/025-h2-sse-api.phpt b/tests/phpt/server/h2/025-h2-sse-api.phpt index e26d196d..0dc450c8 100644 --- a/tests/phpt/server/h2/025-h2-sse-api.phpt +++ b/tests/phpt/server/h2/025-h2-sse-api.phpt @@ -20,8 +20,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19890 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h2/026-h2-sse-empty-start.phpt b/tests/phpt/server/h2/026-h2-sse-empty-start.phpt index cc405c23..9e78d84b 100644 --- a/tests/phpt/server/h2/026-h2-sse-empty-start.phpt +++ b/tests/phpt/server/h2/026-h2-sse-empty-start.phpt @@ -23,8 +23,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19910 + getmypid() % 80; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/server/h2/050-stream-request-body.phpt b/tests/phpt/server/h2/050-stream-request-body.phpt index a0783fec..d682e7f8 100644 --- a/tests/phpt/server/h2/050-stream-request-body.phpt +++ b/tests/phpt/server/h2/050-stream-request-body.phpt @@ -15,8 +15,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 19950 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer( (new HttpServerConfig()) ->addListener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/001-h3-udp-recv.phpt b/tests/phpt/server/h3/001-h3-udp-recv.phpt index d6a69f5e..5aa936ec 100644 --- a/tests/phpt/server/h3/001-h3-udp-recv.phpt +++ b/tests/phpt/server/h3/001-h3-udp-recv.phpt @@ -30,8 +30,9 @@ $cert_path = $tmp_dir . '/cert.pem'; $key_path = $tmp_dir . '/key.pem'; if (!h3_gen_cert($key_path, $cert_path)) { echo "cert gen failed\n"; exit(1); } -$port = 19900 + getmypid() % 100; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) /* dummy TCP so start() has >=1 listener pre-H3 */ ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/002-h3-packet-parse.phpt b/tests/phpt/server/h3/002-h3-packet-parse.phpt index 2a703e8a..9f0c9603 100644 --- a/tests/phpt/server/h3/002-h3-packet-parse.phpt +++ b/tests/phpt/server/h3/002-h3-packet-parse.phpt @@ -39,8 +39,9 @@ $cert_path = $tmp_dir . '/cert.pem'; $key_path = $tmp_dir . '/key.pem'; if (!h3_gen_cert($key_path, $cert_path)) { echo "cert gen failed\n"; exit(1); } -$port = 19950 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/004-h3-start-without-cert.phpt b/tests/phpt/server/h3/004-h3-start-without-cert.phpt index 9917b673..4244c9f0 100644 --- a/tests/phpt/server/h3/004-h3-start-without-cert.phpt +++ b/tests/phpt/server/h3/004-h3-start-without-cert.phpt @@ -18,8 +18,9 @@ if (!class_exists('TrueAsync\HttpServer')) die('skip http_server not loaded'); use TrueAsync\HttpServer; use TrueAsync\HttpServerConfig; -$port = 20100 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addHttp3Listener('127.0.0.1', $port) ->setReadTimeout(2); diff --git a/tests/phpt/server/h3/005-h3-repeat-initial-same-dcid.phpt b/tests/phpt/server/h3/005-h3-repeat-initial-same-dcid.phpt index 58b2c74e..10ea722f 100644 --- a/tests/phpt/server/h3/005-h3-repeat-initial-same-dcid.phpt +++ b/tests/phpt/server/h3/005-h3-repeat-initial-same-dcid.phpt @@ -32,8 +32,9 @@ $cert_path = $tmp_dir . '/cert.pem'; $key_path = $tmp_dir . '/key.pem'; if (!h3_gen_cert($key_path, $cert_path)) { echo "cert gen failed\n"; exit(1); } -$port = 20150 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/008-h3-stats-handshake-keys.phpt b/tests/phpt/server/h3/008-h3-stats-handshake-keys.phpt index f36d1daa..e606a7d0 100644 --- a/tests/phpt/server/h3/008-h3-stats-handshake-keys.phpt +++ b/tests/phpt/server/h3/008-h3-stats-handshake-keys.phpt @@ -33,8 +33,9 @@ $cert_path = $tmp_dir . '/cert.pem'; $key_path = $tmp_dir . '/key.pem'; if (!h3_gen_cert($key_path, $cert_path)) { echo "cert gen failed\n"; exit(1); } -$port = 20250 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/009-h3-e2e-get.phpt b/tests/phpt/server/h3/009-h3-e2e-get.phpt index 1d4ccbf9..0dd444e2 100644 --- a/tests/phpt/server/h3/009-h3-e2e-get.phpt +++ b/tests/phpt/server/h3/009-h3-e2e-get.phpt @@ -40,8 +40,9 @@ exec(sprintf('openssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes ' escapeshellarg($key), escapeshellarg($cert)), $_, $rc); if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } -$port = 20300 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) /* TCP listener required by start() */ ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/010-h3-e2e-post-await-body.phpt b/tests/phpt/server/h3/010-h3-e2e-post-await-body.phpt index 25c3ecfa..c046b194 100644 --- a/tests/phpt/server/h3/010-h3-e2e-post-await-body.phpt +++ b/tests/phpt/server/h3/010-h3-e2e-post-await-body.phpt @@ -38,8 +38,9 @@ file_put_contents($body_path, $body); $expected = sprintf("len=%d sha1=%s method=POST", strlen($body), sha1($body)); -$port = 20400 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/011-h3-e2e-streaming-send.phpt b/tests/phpt/server/h3/011-h3-e2e-streaming-send.phpt index 2a106137..1beecd19 100644 --- a/tests/phpt/server/h3/011-h3-e2e-streaming-send.phpt +++ b/tests/phpt/server/h3/011-h3-e2e-streaming-send.phpt @@ -47,8 +47,9 @@ $expected_body = implode('', $chunks); $expected_sha1 = sha1($expected_body); $expected_len = strlen($expected_body); -$port = 20500 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/012-h3-graceful-close.phpt b/tests/phpt/server/h3/012-h3-graceful-close.phpt index 31b5bc0a..0c59db0a 100644 --- a/tests/phpt/server/h3/012-h3-graceful-close.phpt +++ b/tests/phpt/server/h3/012-h3-graceful-close.phpt @@ -33,8 +33,9 @@ exec(sprintf('openssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes ' escapeshellarg($key), escapeshellarg($cert)), $_, $rc); if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } -$port = 20400 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/013-h3-stateless-reset.phpt b/tests/phpt/server/h3/013-h3-stateless-reset.phpt index d3b8500f..47e8a8cf 100644 --- a/tests/phpt/server/h3/013-h3-stateless-reset.phpt +++ b/tests/phpt/server/h3/013-h3-stateless-reset.phpt @@ -33,8 +33,9 @@ $cert = $tmp . '/cert.pem'; $key = $tmp . '/key.pem'; if (!h3_gen_cert($key, $cert)) { echo "cert gen failed\n"; exit(1); } -$port = 20500 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/014-h3-drain-max-conn-age.phpt b/tests/phpt/server/h3/014-h3-drain-max-conn-age.phpt index 46261b97..1322fa96 100644 --- a/tests/phpt/server/h3/014-h3-drain-max-conn-age.phpt +++ b/tests/phpt/server/h3/014-h3-drain-max-conn-age.phpt @@ -33,8 +33,9 @@ exec(sprintf('openssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes ' escapeshellarg($key), escapeshellarg($cert)), $_, $rc); if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } -$port = 20600 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/015-h3-idle-timeout.phpt b/tests/phpt/server/h3/015-h3-idle-timeout.phpt index 16080231..4e318db3 100644 --- a/tests/phpt/server/h3/015-h3-idle-timeout.phpt +++ b/tests/phpt/server/h3/015-h3-idle-timeout.phpt @@ -37,8 +37,9 @@ if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } putenv('PHP_HTTP3_IDLE_TIMEOUT_MS=500'); -$port = 20700 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/016-h3-per-peer-budget.phpt b/tests/phpt/server/h3/016-h3-per-peer-budget.phpt index bdc86960..efc05a5a 100644 --- a/tests/phpt/server/h3/016-h3-per-peer-budget.phpt +++ b/tests/phpt/server/h3/016-h3-per-peer-budget.phpt @@ -38,8 +38,9 @@ if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } putenv('PHP_HTTP3_PEER_BUDGET=1'); -$port = 20800 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/017-h3-alt-svc-header.phpt b/tests/phpt/server/h3/017-h3-alt-svc-header.phpt index 67ddf04c..3525c76d 100644 --- a/tests/phpt/server/h3/017-h3-alt-svc-header.phpt +++ b/tests/phpt/server/h3/017-h3-alt-svc-header.phpt @@ -63,7 +63,9 @@ function run_server(int $tcp_port, int $udp_port, string $cert, string $key, boo return $captured; } -$port_tcp = 20900 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; + +$port_tcp = tas_free_port_span(11); $port_udp = $port_tcp + 1; $out = run_server($port_tcp, $port_udp, $cert, $key, /* disable */ false); diff --git a/tests/phpt/server/h3/018-h3-version-negotiation.phpt b/tests/phpt/server/h3/018-h3-version-negotiation.phpt index 6d3cdc06..10457b6b 100644 --- a/tests/phpt/server/h3/018-h3-version-negotiation.phpt +++ b/tests/phpt/server/h3/018-h3-version-negotiation.phpt @@ -31,8 +31,9 @@ $cert = $tmp . '/cert.pem'; $key = $tmp . '/key.pem'; if (!h3_gen_cert($key, $cert)) { echo "cert gen failed\n"; exit(1); } -$port = 21000 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1, true) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/022-h3-stats-error-counters.phpt b/tests/phpt/server/h3/022-h3-stats-error-counters.phpt index 5975c3d3..b749955e 100644 --- a/tests/phpt/server/h3/022-h3-stats-error-counters.phpt +++ b/tests/phpt/server/h3/022-h3-stats-error-counters.phpt @@ -29,8 +29,9 @@ $cert_path = $tmp_dir . '/cert.pem'; $key_path = $tmp_dir . '/key.pem'; if (!h3_gen_cert($key_path, $cert_path)) { echo "cert gen failed\n"; exit(1); } -$port = 20300 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/024-h3-config-setters-e2e.phpt b/tests/phpt/server/h3/024-h3-config-setters-e2e.phpt index 8bec6ecb..9977c0d6 100644 --- a/tests/phpt/server/h3/024-h3-config-setters-e2e.phpt +++ b/tests/phpt/server/h3/024-h3-config-setters-e2e.phpt @@ -38,8 +38,9 @@ exec(sprintf('openssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes ' escapeshellarg($key), escapeshellarg($cert)), $_, $rc); if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } -$port = 20920 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/025-h3-retry-token.phpt b/tests/phpt/server/h3/025-h3-retry-token.phpt index 9415c7a8..3467f163 100644 --- a/tests/phpt/server/h3/025-h3-retry-token.phpt +++ b/tests/phpt/server/h3/025-h3-retry-token.phpt @@ -25,8 +25,9 @@ exec(sprintf('openssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes ' escapeshellarg($key), escapeshellarg($cert)), $_, $rc); if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } -$port = 20420 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/026-h3-static-file.phpt b/tests/phpt/server/h3/026-h3-static-file.phpt index e392e603..f5852f18 100644 --- a/tests/phpt/server/h3/026-h3-static-file.phpt +++ b/tests/phpt/server/h3/026-h3-static-file.phpt @@ -55,8 +55,9 @@ register_shutdown_function(function () use ($tmp, $root, $sizes) { @rmdir($root); @rmdir($tmp); }); -$port = 20360 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/027-h3-static-mount.phpt b/tests/phpt/server/h3/027-h3-static-mount.phpt index 10c1d6cb..162ac939 100644 --- a/tests/phpt/server/h3/027-h3-static-mount.phpt +++ b/tests/phpt/server/h3/027-h3-static-mount.phpt @@ -58,8 +58,9 @@ register_shutdown_function(function () use ($tmp, $root, $sizes) { @rmdir($root); @rmdir($tmp); }); -$port = 20400 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/028-h3-large-body.phpt b/tests/phpt/server/h3/028-h3-large-body.phpt index 9a5b6cff..f680b7c5 100644 --- a/tests/phpt/server/h3/028-h3-large-body.phpt +++ b/tests/phpt/server/h3/028-h3-large-body.phpt @@ -38,9 +38,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 20440 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; -/* Position-varying content so a reorder/truncation shows as a sha1 miss. */ +$port = tas_free_port_span(2); $size = 2 * 1024 * 1024; $body = str_repeat("\0", $size); for ($i = 0; $i < $size; $i++) { $body[$i] = chr(($i * 31 + 7) & 0xff); } diff --git a/tests/phpt/server/h3/029-h3-pacing-setter.phpt b/tests/phpt/server/h3/029-h3-pacing-setter.phpt index 93e6fb16..424e67ad 100644 --- a/tests/phpt/server/h3/029-h3-pacing-setter.phpt +++ b/tests/phpt/server/h3/029-h3-pacing-setter.phpt @@ -42,7 +42,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 20460 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port_span(2); $size = 256 * 1024; $body = str_repeat("\0", $size); for ($i = 0; $i < $size; $i++) { $body[$i] = chr(($i * 31 + 7) & 0xff); } diff --git a/tests/phpt/server/h3/031-h3-stats-nonzero-after-traffic.phpt b/tests/phpt/server/h3/031-h3-stats-nonzero-after-traffic.phpt index f854ab7b..d6011788 100644 --- a/tests/phpt/server/h3/031-h3-stats-nonzero-after-traffic.phpt +++ b/tests/phpt/server/h3/031-h3-stats-nonzero-after-traffic.phpt @@ -33,8 +33,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 20560 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/032-h3-connection-migration.phpt b/tests/phpt/server/h3/032-h3-connection-migration.phpt index bce1a118..6ee050d5 100644 --- a/tests/phpt/server/h3/032-h3-connection-migration.phpt +++ b/tests/phpt/server/h3/032-h3-connection-migration.phpt @@ -39,8 +39,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 21100 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/033-h3-head-request.phpt b/tests/phpt/server/h3/033-h3-head-request.phpt index 2348421f..0e77191a 100644 --- a/tests/phpt/server/h3/033-h3-head-request.phpt +++ b/tests/phpt/server/h3/033-h3-head-request.phpt @@ -31,8 +31,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 20760 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/034-h3-stateless-reset-clamps.phpt b/tests/phpt/server/h3/034-h3-stateless-reset-clamps.phpt index 3d3ddd00..b6ab2b62 100644 --- a/tests/phpt/server/h3/034-h3-stateless-reset-clamps.phpt +++ b/tests/phpt/server/h3/034-h3-stateless-reset-clamps.phpt @@ -35,8 +35,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 20680 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/035-h3-request-body-overflow.phpt b/tests/phpt/server/h3/035-h3-request-body-overflow.phpt index e7aee21e..5e4e76a1 100644 --- a/tests/phpt/server/h3/035-h3-request-body-overflow.phpt +++ b/tests/phpt/server/h3/035-h3-request-body-overflow.phpt @@ -39,8 +39,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key, $big) { @unlink($cert); @unlink($key); @unlink($big); @rmdir($tmp); }); -$port = 20720 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/037-h3-reactor-pool-e2e.phpt b/tests/phpt/server/h3/037-h3-reactor-pool-e2e.phpt index 7e5fa64f..e96be100 100644 --- a/tests/phpt/server/h3/037-h3-reactor-pool-e2e.phpt +++ b/tests/phpt/server/h3/037-h3-reactor-pool-e2e.phpt @@ -45,8 +45,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 21300 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) /* TCP listener required by start() */ ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/038-h3-reactor-pool-post.phpt b/tests/phpt/server/h3/038-h3-reactor-pool-post.phpt index e83133f7..2a3bba96 100644 --- a/tests/phpt/server/h3/038-h3-reactor-pool-post.phpt +++ b/tests/phpt/server/h3/038-h3-reactor-pool-post.phpt @@ -49,8 +49,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key, $body_path) { @unlink($cert); @unlink($key); @unlink($body_path); @rmdir($tmp); }); -$port = 21380 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/039-h3-reactor-pool-static.phpt b/tests/phpt/server/h3/039-h3-reactor-pool-static.phpt index 90c73929..d2e55ded 100644 --- a/tests/phpt/server/h3/039-h3-reactor-pool-static.phpt +++ b/tests/phpt/server/h3/039-h3-reactor-pool-static.phpt @@ -52,8 +52,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key, $root) { @rmdir($root); @rmdir($tmp); }); -$port = 21420 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/040-h3-reactor-pool-steering.phpt b/tests/phpt/server/h3/040-h3-reactor-pool-steering.phpt index 94876aec..0f25e292 100644 --- a/tests/phpt/server/h3/040-h3-reactor-pool-steering.phpt +++ b/tests/phpt/server/h3/040-h3-reactor-pool-steering.phpt @@ -55,8 +55,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 21340 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) /* TCP listener required by start() */ ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/041-h3-migration-storm-guard.phpt b/tests/phpt/server/h3/041-h3-migration-storm-guard.phpt index 15c0ea32..5917d212 100644 --- a/tests/phpt/server/h3/041-h3-migration-storm-guard.phpt +++ b/tests/phpt/server/h3/041-h3-migration-storm-guard.phpt @@ -55,8 +55,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 21380 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) /* TCP listener required by start() */ ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/041-h3-sse-api.phpt b/tests/phpt/server/h3/041-h3-sse-api.phpt index e240d9d8..5a52ae27 100644 --- a/tests/phpt/server/h3/041-h3-sse-api.phpt +++ b/tests/phpt/server/h3/041-h3-sse-api.phpt @@ -36,8 +36,9 @@ $expected_body = "data: multi\ndata: line\n\n" . "id: 7\nevent: ping\ndata: named\n\n"; -$port = 20560 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/042-h3-newcid-dcid-rotation.phpt b/tests/phpt/server/h3/042-h3-newcid-dcid-rotation.phpt index 3fc45c6c..f923b667 100644 --- a/tests/phpt/server/h3/042-h3-newcid-dcid-rotation.phpt +++ b/tests/phpt/server/h3/042-h3-newcid-dcid-rotation.phpt @@ -41,8 +41,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 21420 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/042-h3-sse-empty-start.phpt b/tests/phpt/server/h3/042-h3-sse-empty-start.phpt index 046e8b77..8902df11 100644 --- a/tests/phpt/server/h3/042-h3-sse-empty-start.phpt +++ b/tests/phpt/server/h3/042-h3-sse-empty-start.phpt @@ -32,8 +32,9 @@ exec(sprintf('openssl req -x509 -newkey rsa:2048 -sha256 -days 1 -nodes ' escapeshellarg($key), escapeshellarg($cert)), $_, $rc); if ($rc !== 0) { echo "cert gen failed\n"; exit(1); } -$port = 20600 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/043-h3-newcid-multi-rotation.phpt b/tests/phpt/server/h3/043-h3-newcid-multi-rotation.phpt index c24417ee..2b5e28be 100644 --- a/tests/phpt/server/h3/043-h3-newcid-multi-rotation.phpt +++ b/tests/phpt/server/h3/043-h3-newcid-multi-rotation.phpt @@ -36,8 +36,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 21460 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/044-hq-interop-file.phpt b/tests/phpt/server/h3/044-hq-interop-file.phpt index ca36d2f3..d35790cc 100644 --- a/tests/phpt/server/h3/044-hq-interop-file.phpt +++ b/tests/phpt/server/h3/044-hq-interop-file.phpt @@ -55,8 +55,9 @@ register_shutdown_function(function () use ($tmp, $root, $sizes) { @rmdir($root); @rmdir($tmp); }); -$port = 20460 + getmypid() % 40; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port + 1) ->addHttp3Listener('127.0.0.1', $port) diff --git a/tests/phpt/server/h3/045-h3-reload-reactor-pool.phpt b/tests/phpt/server/h3/045-h3-reload-reactor-pool.phpt new file mode 100644 index 00000000..d9adfc55 --- /dev/null +++ b/tests/phpt/server/h3/045-h3-reload-reactor-pool.phpt @@ -0,0 +1,90 @@ +--TEST-- +HttpServer: reload() under the gated reactor pool is refused — inbox rotation not implemented (#93) +--EXTENSIONS-- +true_async_server +true_async +--SKIPIF-- + true, 'h3client' => true]); +?> +--ENV-- +TRUE_ASYNC_SERVER_REACTOR_POOL=1 +PHP_HTTP3_DISABLE_RETRY=1 +--FILE-- +addListener('127.0.0.1', $tcp_port) /* TCP listener required by start() */ + ->addHttp3Listener('127.0.0.1', $port) + ->enableTls(true)->setCertificate($cert)->setPrivateKey($key) + ->setWorkers(2); +$server = new HttpServer($config); +$server->addHttpHandler(function ($req, $res) { + $res->setStatusCode(201) + ->setHeader('content-type', 'text/plain; charset=utf-8') + ->setBody('echo:' . $req->getMethod() . ':' . $req->getUri()); +}); + +$client_bin = __DIR__ . '/../../../h3client/h3client'; + +spawn(function () use ($server, $port, $client_bin) { + usleep(600000); /* reactors + workers thread up and bind */ + + $get = static function (string $path) use ($client_bin, $port): string { + $cmd = sprintf('H3CLIENT_DEADLINE_MS=4000 %s 127.0.0.1 %d %s GET 2>&1', + escapeshellarg($client_bin), $port, $path); + $out = shell_exec($cmd) ?? ''; + $status = preg_match('/^STATUS=(\d+)$/m', $out, $m) ? (int)$m[1] : -1; + $body = trim((string) preg_replace('/^STATUS=\d+\n?/m', '', $out)); + return $status . ':' . $body; + }; + + echo "before=", $get('/one'), "\n"; + + try { + $server->reload(); + echo "reload=no-throw\n"; + } catch (\Throwable $e) { + echo "reload_refused=", + (int)(strpos($e->getMessage(), 'REACTOR_POOL') !== false), "\n"; + } + + /* The refusal must be side-effect free: H3 keeps serving. */ + echo "after=", $get('/two'), "\n"; + + posix_kill(getmypid(), SIGKILL); +}); + +$server->start(); +?> +--EXPECTF-- +%Abefore=201:echo:GET:/one +reload_refused=1 +after=201:echo:GET:/two +%A diff --git a/tests/phpt/server/sendfile/001-sendfile-basic.phpt b/tests/phpt/server/sendfile/001-sendfile-basic.phpt index 3f8f02d7..c21626ae 100644 --- a/tests/phpt/server/sendfile/001-sendfile-basic.phpt +++ b/tests/phpt/server/sendfile/001-sendfile-basic.phpt @@ -17,7 +17,9 @@ file_put_contents($tmp, "body{margin:0}"); register_shutdown_function(function() use ($tmp) { @unlink($tmp); }); -$port = 28310 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addHttpHandler(function ($req, $res) use ($tmp) { diff --git a/tests/phpt/server/sendfile/002-sendfile-options.phpt b/tests/phpt/server/sendfile/002-sendfile-options.phpt index 855195bd..299e5d38 100644 --- a/tests/phpt/server/sendfile/002-sendfile-options.phpt +++ b/tests/phpt/server/sendfile/002-sendfile-options.phpt @@ -17,7 +17,9 @@ file_put_contents($tmp, "abcdef"); register_shutdown_function(function() use ($tmp) { @unlink($tmp); }); -$port = 28320 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addHttpHandler(function ($req, $res) use ($tmp) { diff --git a/tests/phpt/server/sendfile/003-sendfile-sealed.phpt b/tests/phpt/server/sendfile/003-sendfile-sealed.phpt index 373fee79..d580d5fd 100644 --- a/tests/phpt/server/sendfile/003-sendfile-sealed.phpt +++ b/tests/phpt/server/sendfile/003-sendfile-sealed.phpt @@ -16,7 +16,9 @@ file_put_contents($tmp, "ZZZZZ"); register_shutdown_function(function() use ($tmp) { @unlink($tmp); }); -$port = 28330 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addHttpHandler(function ($req, $res) use ($tmp) { diff --git a/tests/phpt/server/sendfile/004-sendfile-errors.phpt b/tests/phpt/server/sendfile/004-sendfile-errors.phpt index b3f67812..7c1b0366 100644 --- a/tests/phpt/server/sendfile/004-sendfile-errors.phpt +++ b/tests/phpt/server/sendfile/004-sendfile-errors.phpt @@ -10,7 +10,9 @@ use TrueAsync\HttpServerConfig; use function Async\spawn; use function Async\await; -$port = 28340 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addHttpHandler(function ($req, $res) { diff --git a/tests/phpt/server/sendfile/005-sendfile-rfc5987.phpt b/tests/phpt/server/sendfile/005-sendfile-rfc5987.phpt index c13f9d28..0b8c4ebb 100644 --- a/tests/phpt/server/sendfile/005-sendfile-rfc5987.phpt +++ b/tests/phpt/server/sendfile/005-sendfile-rfc5987.phpt @@ -31,7 +31,9 @@ $cases = [ "attachment; filename*=UTF-8''a%22b.txt"], ]; -$port = 28430 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addHttpHandler(function ($req, $res) use ($tmp, $cases) { diff --git a/tests/phpt/server/static/001-static-basic.phpt b/tests/phpt/server/static/001-static-basic.phpt index 9606e1c2..0cb14990 100644 --- a/tests/phpt/server/static/001-static-basic.phpt +++ b/tests/phpt/server/static/001-static-basic.phpt @@ -27,7 +27,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19817 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $h = (new StaticHandler('/static/', $root))->setIndexFiles('index.html'); diff --git a/tests/phpt/server/static/002-static-dotfile-and-onmissing.phpt b/tests/phpt/server/static/002-static-dotfile-and-onmissing.phpt index 8ff376bf..f1ffc1f0 100644 --- a/tests/phpt/server/static/002-static-dotfile-and-onmissing.phpt +++ b/tests/phpt/server/static/002-static-dotfile-and-onmissing.phpt @@ -25,7 +25,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19817 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); diff --git a/tests/phpt/server/static/003-static-security.phpt b/tests/phpt/server/static/003-static-security.phpt index 4b86e44c..3bf23107 100644 --- a/tests/phpt/server/static/003-static-security.phpt +++ b/tests/phpt/server/static/003-static-security.phpt @@ -49,7 +49,9 @@ register_shutdown_function(function() use ($base, $forbidden) { $rm($base); }); -$port = 19817 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $h = new StaticHandler('/static/', $root); diff --git a/tests/phpt/server/static/004-static-workers.phpt b/tests/phpt/server/static/004-static-workers.phpt index c70ed1df..27ba548b 100644 --- a/tests/phpt/server/static/004-static-workers.phpt +++ b/tests/phpt/server/static/004-static-workers.phpt @@ -30,7 +30,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19940 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $workers = 2; $config = (new HttpServerConfig()) diff --git a/tests/phpt/server/static/005-static-if-modified-since.phpt b/tests/phpt/server/static/005-static-if-modified-since.phpt index 7dfc88a4..c4e858f9 100644 --- a/tests/phpt/server/static/005-static-if-modified-since.phpt +++ b/tests/phpt/server/static/005-static-if-modified-since.phpt @@ -30,7 +30,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19960 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler(new StaticHandler('/static/', $root)); diff --git a/tests/phpt/server/static/006-static-zero-coroutine-counter.phpt b/tests/phpt/server/static/006-static-zero-coroutine-counter.phpt index c87666e9..b7cc35b4 100644 --- a/tests/phpt/server/static/006-static-zero-coroutine-counter.phpt +++ b/tests/phpt/server/static/006-static-zero-coroutine-counter.phpt @@ -27,7 +27,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19990 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler((new StaticHandler('/static/', $root))->disableIndex()); diff --git a/tests/phpt/server/static/007-static-tls-streaming.phpt b/tests/phpt/server/static/007-static-tls-streaming.phpt index 7d724617..935526d7 100644 --- a/tests/phpt/server/static/007-static-tls-streaming.phpt +++ b/tests/phpt/server/static/007-static-tls-streaming.phpt @@ -38,8 +38,9 @@ register_shutdown_function(function() use ($root, $tmp) { @rmdir($tmp); }); -$port = 19960 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/static/008-static-cache-counters.phpt b/tests/phpt/server/static/008-static-cache-counters.phpt index bc60a3a0..ad806736 100644 --- a/tests/phpt/server/static/008-static-cache-counters.phpt +++ b/tests/phpt/server/static/008-static-cache-counters.phpt @@ -28,7 +28,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19940 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler( diff --git a/tests/phpt/server/static/009-static-symlink-owner.phpt b/tests/phpt/server/static/009-static-symlink-owner.phpt index d27cbcab..91189c9f 100644 --- a/tests/phpt/server/static/009-static-symlink-owner.phpt +++ b/tests/phpt/server/static/009-static-symlink-owner.phpt @@ -45,7 +45,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19960 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); diff --git a/tests/phpt/server/static/010-static-precompressed.phpt b/tests/phpt/server/static/010-static-precompressed.phpt index bca15c0f..d803be54 100644 --- a/tests/phpt/server/static/010-static-precompressed.phpt +++ b/tests/phpt/server/static/010-static-precompressed.phpt @@ -38,7 +38,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19930 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler( diff --git a/tests/phpt/server/static/011-static-precompressed-cache-uaf.phpt b/tests/phpt/server/static/011-static-precompressed-cache-uaf.phpt index 87aef55b..71c6e1a9 100644 --- a/tests/phpt/server/static/011-static-precompressed-cache-uaf.phpt +++ b/tests/phpt/server/static/011-static-precompressed-cache-uaf.phpt @@ -41,7 +41,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19460 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler( diff --git a/tests/phpt/server/static/011-static-range.phpt b/tests/phpt/server/static/011-static-range.phpt index ab13e1d3..857c1925 100644 --- a/tests/phpt/server/static/011-static-range.phpt +++ b/tests/phpt/server/static/011-static-range.phpt @@ -28,7 +28,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19920 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler((new StaticHandler('/r/', $root))->disableIndex()); diff --git a/tests/phpt/server/static/012-static-h2.phpt b/tests/phpt/server/static/012-static-h2.phpt index 58927d76..d9e8196a 100644 --- a/tests/phpt/server/static/012-static-h2.phpt +++ b/tests/phpt/server/static/012-static-h2.phpt @@ -38,7 +38,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19940 + getmypid() % 50; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler( diff --git a/tests/phpt/server/static/012-static-precompressed-small-cache-headers.phpt b/tests/phpt/server/static/012-static-precompressed-small-cache-headers.phpt index 318165a2..9109dc4d 100644 --- a/tests/phpt/server/static/012-static-precompressed-small-cache-headers.phpt +++ b/tests/phpt/server/static/012-static-precompressed-small-cache-headers.phpt @@ -43,7 +43,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19480 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler( diff --git a/tests/phpt/server/static/013-static-h2-tls.phpt b/tests/phpt/server/static/013-static-h2-tls.phpt index e4a0dd1e..9e3e7530 100644 --- a/tests/phpt/server/static/013-static-h2-tls.phpt +++ b/tests/phpt/server/static/013-static-h2-tls.phpt @@ -49,8 +49,9 @@ register_shutdown_function(function() use ($root, $tmp) { @rmdir($tmp); }); -$port = 19980 + getmypid() % 15; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/static/014-static-body-cache.phpt b/tests/phpt/server/static/014-static-body-cache.phpt index 7cfd39a9..5dfa33a3 100644 --- a/tests/phpt/server/static/014-static-body-cache.phpt +++ b/tests/phpt/server/static/014-static-body-cache.phpt @@ -27,7 +27,9 @@ register_shutdown_function(function () use ($root) { @rmdir($root); }); -$port = 19960 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler( diff --git a/tests/phpt/server/static/015-static-ims-date-formats.phpt b/tests/phpt/server/static/015-static-ims-date-formats.phpt index c0fdb2f4..cc1c891a 100644 --- a/tests/phpt/server/static/015-static-ims-date-formats.phpt +++ b/tests/phpt/server/static/015-static-ims-date-formats.phpt @@ -27,7 +27,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 20040 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler(new StaticHandler('/s/', $root)); diff --git a/tests/phpt/server/static/017-static-handler-locked.phpt b/tests/phpt/server/static/017-static-handler-locked.phpt index 458344cb..617b8af3 100644 --- a/tests/phpt/server/static/017-static-handler-locked.phpt +++ b/tests/phpt/server/static/017-static-handler-locked.phpt @@ -21,7 +21,9 @@ $root = sys_get_temp_dir() . '/sh-lock-' . getmypid() . '-' . bin2hex(random_byt mkdir($root, 0700, true); register_shutdown_function(fn() => @rmdir($root)); -$port = 28560 + getmypid() % 1000; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $sh = new StaticHandler('/s/', $root); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); diff --git a/tests/phpt/server/static/018-static-hide-index-mime.phpt b/tests/phpt/server/static/018-static-hide-index-mime.phpt index bab913e3..07843391 100644 --- a/tests/phpt/server/static/018-static-hide-index-mime.phpt +++ b/tests/phpt/server/static/018-static-hide-index-mime.phpt @@ -38,7 +38,9 @@ register_shutdown_function(function() use ($root1, $root2) { @rmdir("$root1/sub"); @rmdir($root1); @rmdir($root2); }); -$port = 19880 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); diff --git a/tests/phpt/server/static/019-static-dotfile-ignore-and-noindex.phpt b/tests/phpt/server/static/019-static-dotfile-ignore-and-noindex.phpt index e07613df..932f70a1 100644 --- a/tests/phpt/server/static/019-static-dotfile-ignore-and-noindex.phpt +++ b/tests/phpt/server/static/019-static-dotfile-ignore-and-noindex.phpt @@ -35,7 +35,9 @@ register_shutdown_function(function() use ($root) { @rmdir($root); }); -$port = 19960 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); diff --git a/tests/phpt/server/static/020-static-extra-headers-and-mime.phpt b/tests/phpt/server/static/020-static-extra-headers-and-mime.phpt index 6fa3cbfe..d0e01378 100644 --- a/tests/phpt/server/static/020-static-extra-headers-and-mime.phpt +++ b/tests/phpt/server/static/020-static-extra-headers-and-mime.phpt @@ -27,7 +27,9 @@ register_shutdown_function(function() use ($root) { @unlink("$root/page.mdx"); @rmdir($root); }); -$port = 20040 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $h = (new StaticHandler('/s/', $root)) diff --git a/tests/phpt/server/static/021-static-symlink-reject-nofollow.phpt b/tests/phpt/server/static/021-static-symlink-reject-nofollow.phpt index b2569cc2..d95174ee 100644 --- a/tests/phpt/server/static/021-static-symlink-reject-nofollow.phpt +++ b/tests/phpt/server/static/021-static-symlink-reject-nofollow.phpt @@ -46,7 +46,9 @@ register_shutdown_function(function() use ($root, $secret) { @unlink($secret); }); -$port = 19970 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port); $server = new HttpServer($config); $server->addStaticHandler( diff --git a/tests/phpt/server/tls/002-tls-config.phpt b/tests/phpt/server/tls/002-tls-config.phpt index eef50d61..fe6dce81 100644 --- a/tests/phpt/server/tls/002-tls-config.phpt +++ b/tests/phpt/server/tls/002-tls-config.phpt @@ -29,9 +29,9 @@ if (!tls_gen_cert($key_path, $cert_path)) { exit(1); } -$port = 19960 + getmypid() % 30; +require_once __DIR__ . '/../_free_port.inc'; -// --- Case 1: TLS listener with valid cert/key starts and stops cleanly. +$port = tas_free_port_span(2); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) // tls=true ->enableTls(true) diff --git a/tests/phpt/server/tls/003-tls-handshake.phpt b/tests/phpt/server/tls/003-tls-handshake.phpt index 81b29799..cf169f27 100644 --- a/tests/phpt/server/tls/003-tls-handshake.phpt +++ b/tests/phpt/server/tls/003-tls-handshake.phpt @@ -29,8 +29,9 @@ if (!tls_gen_cert($key_path, $cert_path)) { exit(1); } -$port = 19970 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) // tls=true ->enableTls(true) diff --git a/tests/phpt/server/tls/004-tls-get.phpt b/tests/phpt/server/tls/004-tls-get.phpt index 3e221db7..5ddd7cc5 100644 --- a/tests/phpt/server/tls/004-tls-get.phpt +++ b/tests/phpt/server/tls/004-tls-get.phpt @@ -26,8 +26,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19940 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/005-tls-body.phpt b/tests/phpt/server/tls/005-tls-body.phpt index 447fa3e1..0e55d43c 100644 --- a/tests/phpt/server/tls/005-tls-body.phpt +++ b/tests/phpt/server/tls/005-tls-body.phpt @@ -40,8 +40,9 @@ $buf = substr($buf, 0, $size); file_put_contents($payload, $buf); $expected_sha1 = sha1($buf); -$port = 19920 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/006-tls-keepalive.phpt b/tests/phpt/server/tls/006-tls-keepalive.phpt index 21171ace..01bd5d4c 100644 --- a/tests/phpt/server/tls/006-tls-keepalive.phpt +++ b/tests/phpt/server/tls/006-tls-keepalive.phpt @@ -25,8 +25,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19880 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/007-tls-pipelining.phpt b/tests/phpt/server/tls/007-tls-pipelining.phpt index 00ac4438..eda0ef6f 100644 --- a/tests/phpt/server/tls/007-tls-pipelining.phpt +++ b/tests/phpt/server/tls/007-tls-pipelining.phpt @@ -34,8 +34,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19860 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/008-tls-telemetry.phpt b/tests/phpt/server/tls/008-tls-telemetry.phpt index 5d5ac4a9..156af92c 100644 --- a/tests/phpt/server/tls/008-tls-telemetry.phpt +++ b/tests/phpt/server/tls/008-tls-telemetry.phpt @@ -25,8 +25,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19820 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/009-tls-resumption.phpt b/tests/phpt/server/tls/009-tls-resumption.phpt index acd90c4b..c243d13e 100644 --- a/tests/phpt/server/tls/009-tls-resumption.phpt +++ b/tests/phpt/server/tls/009-tls-resumption.phpt @@ -32,8 +32,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19800 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/010-tls-ktls.phpt b/tests/phpt/server/tls/010-tls-ktls.phpt index 2b42e35a..ca911ede 100644 --- a/tests/phpt/server/tls/010-tls-ktls.phpt +++ b/tests/phpt/server/tls/010-tls-ktls.phpt @@ -25,8 +25,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19780 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/011-tls-parse-error.phpt b/tests/phpt/server/tls/011-tls-parse-error.phpt index 32b5aec3..196e87f1 100644 --- a/tests/phpt/server/tls/011-tls-parse-error.phpt +++ b/tests/phpt/server/tls/011-tls-parse-error.phpt @@ -26,8 +26,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19960 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/012-tls-security-level.phpt b/tests/phpt/server/tls/012-tls-security-level.phpt index 90b28ca9..490fd723 100644 --- a/tests/phpt/server/tls/012-tls-security-level.phpt +++ b/tests/phpt/server/tls/012-tls-security-level.phpt @@ -27,7 +27,9 @@ if (!tls_gen_cert($key_path, $cert_path, 1024, 'sha1')) { exit(1); } -$port = 19990 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/013-tls-abrupt-and-plain-on-tls.phpt b/tests/phpt/server/tls/013-tls-abrupt-and-plain-on-tls.phpt index d4b2b338..f0398f83 100644 --- a/tests/phpt/server/tls/013-tls-abrupt-and-plain-on-tls.phpt +++ b/tests/phpt/server/tls/013-tls-abrupt-and-plain-on-tls.phpt @@ -26,8 +26,9 @@ if (!tls_gen_cert($key, $cert)) { exit(1); } -$port = 19990 + getmypid() % 9; +require_once __DIR__ . '/../_free_port.inc'; +$port = tas_free_port(); $server = new HttpServer((new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/server/tls/014-tls-h2-large-body-bio-park.phpt b/tests/phpt/server/tls/014-tls-h2-large-body-bio-park.phpt index 2f076c9a..97ae49b6 100644 --- a/tests/phpt/server/tls/014-tls-h2-large-body-bio-park.phpt +++ b/tests/phpt/server/tls/014-tls-h2-large-body-bio-park.phpt @@ -48,7 +48,9 @@ register_shutdown_function(function () use ($tmp, $cert, $key) { @unlink($cert); @unlink($key); @rmdir($tmp); }); -$port = 19930 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $size = 256 * 1024; /* 8x the 32 KiB gather cap */ $body = str_repeat("\0", $size); for ($i = 0; $i < $size; $i++) { $body[$i] = chr(($i * 31 + 7) & 0xff); } diff --git a/tests/phpt/server/tls/015-tls-buffer-setter.phpt b/tests/phpt/server/tls/015-tls-buffer-setter.phpt index 5a0d72d8..b28ff4a7 100644 --- a/tests/phpt/server/tls/015-tls-buffer-setter.phpt +++ b/tests/phpt/server/tls/015-tls-buffer-setter.phpt @@ -47,7 +47,9 @@ $key = sys_get_temp_dir() . '/tls015-' . getmypid() . '.key'; if (!tls_gen_cert($key, $cert)) { echo "cert gen failed\n"; exit(1); } register_shutdown_function(function () use ($cert, $key) { @unlink($cert); @unlink($key); }); -$port = 19945 + getmypid() % 20; +require_once __DIR__ . '/../_free_port.inc'; + +$port = tas_free_port(); $size = 200000; /* > one record → exercises park path */ $body = str_repeat("\0", $size); for ($i = 0; $i < $size; $i++) { $body[$i] = chr(($i * 31 + 7) & 0xff); } diff --git a/tests/phpt/websocket/005-handshake-101.phpt b/tests/phpt/websocket/005-handshake-101.phpt index 26358cb6..ad4f67f9 100644 --- a/tests/phpt/websocket/005-handshake-101.phpt +++ b/tests/phpt/websocket/005-handshake-101.phpt @@ -12,8 +12,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19850 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/006-recv-text-frame.phpt b/tests/phpt/websocket/006-recv-text-frame.phpt index 007737bd..cd3bee78 100644 --- a/tests/phpt/websocket/006-recv-text-frame.phpt +++ b/tests/phpt/websocket/006-recv-text-frame.phpt @@ -13,8 +13,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19860 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/007-send-echo.phpt b/tests/phpt/websocket/007-send-echo.phpt index 6386cc7c..aee822c5 100644 --- a/tests/phpt/websocket/007-send-echo.phpt +++ b/tests/phpt/websocket/007-send-echo.phpt @@ -12,8 +12,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19870 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/008-close-handshake.phpt b/tests/phpt/websocket/008-close-handshake.phpt index 2e75b855..2ea93737 100644 --- a/tests/phpt/websocket/008-close-handshake.phpt +++ b/tests/phpt/websocket/008-close-handshake.phpt @@ -13,8 +13,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19880 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/009-binary-frames.phpt b/tests/phpt/websocket/009-binary-frames.phpt index 14d5b0b1..2a66d02e 100644 --- a/tests/phpt/websocket/009-binary-frames.phpt +++ b/tests/phpt/websocket/009-binary-frames.phpt @@ -12,8 +12,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19890 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/010-fragmented-message.phpt b/tests/phpt/websocket/010-fragmented-message.phpt index fc562e8e..a2946c85 100644 --- a/tests/phpt/websocket/010-fragmented-message.phpt +++ b/tests/phpt/websocket/010-fragmented-message.phpt @@ -12,8 +12,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19900 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/011-concurrent-recv.phpt b/tests/phpt/websocket/011-concurrent-recv.phpt index 5e2f66d1..0e36db28 100644 --- a/tests/phpt/websocket/011-concurrent-recv.phpt +++ b/tests/phpt/websocket/011-concurrent-recv.phpt @@ -14,8 +14,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19910 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/012-upgrade-reject.phpt b/tests/phpt/websocket/012-upgrade-reject.phpt index 4da53010..16a0500f 100644 --- a/tests/phpt/websocket/012-upgrade-reject.phpt +++ b/tests/phpt/websocket/012-upgrade-reject.phpt @@ -13,8 +13,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19920 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/013-upgrade-subprotocol.phpt b/tests/phpt/websocket/013-upgrade-subprotocol.phpt index 96dc8c71..bf601f2a 100644 --- a/tests/phpt/websocket/013-upgrade-subprotocol.phpt +++ b/tests/phpt/websocket/013-upgrade-subprotocol.phpt @@ -13,8 +13,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19930 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/015-keepalive-ping.phpt b/tests/phpt/websocket/015-keepalive-ping.phpt index a1bc423d..86c41e8a 100644 --- a/tests/phpt/websocket/015-keepalive-ping.phpt +++ b/tests/phpt/websocket/015-keepalive-ping.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19940 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/016-auto-pong.phpt b/tests/phpt/websocket/016-auto-pong.phpt index b60b705c..bc6d5960 100644 --- a/tests/phpt/websocket/016-auto-pong.phpt +++ b/tests/phpt/websocket/016-auto-pong.phpt @@ -12,8 +12,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19960 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/017-remote-address.phpt b/tests/phpt/websocket/017-remote-address.phpt index 1c440a83..a137e091 100644 --- a/tests/phpt/websocket/017-remote-address.phpt +++ b/tests/phpt/websocket/017-remote-address.phpt @@ -12,8 +12,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19980 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/018-wss-echo.phpt b/tests/phpt/websocket/018-wss-echo.phpt index b74d660d..4bfe666c 100644 --- a/tests/phpt/websocket/018-wss-echo.phpt +++ b/tests/phpt/websocket/018-wss-echo.phpt @@ -52,8 +52,9 @@ echo "$status op=0x", dechex($op), " payload=$p\n"; CLIENT ); -$port = 19920 + getmypid() % 30; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port, true) ->enableTls(true) diff --git a/tests/phpt/websocket/019-h2-extended-connect.phpt b/tests/phpt/websocket/019-h2-extended-connect.phpt index edddc024..59be653a 100644 --- a/tests/phpt/websocket/019-h2-extended-connect.phpt +++ b/tests/phpt/websocket/019-h2-extended-connect.phpt @@ -13,8 +13,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19890 + getmypid() % 50; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/websocket/020-permessage-deflate-echo.phpt b/tests/phpt/websocket/020-permessage-deflate-echo.phpt index 932f883b..21a0a31c 100644 --- a/tests/phpt/websocket/020-permessage-deflate-echo.phpt +++ b/tests/phpt/websocket/020-permessage-deflate-echo.phpt @@ -21,8 +21,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19880 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/021-permessage-deflate-bomb.phpt b/tests/phpt/websocket/021-permessage-deflate-bomb.phpt index 866a03f1..a91a06ed 100644 --- a/tests/phpt/websocket/021-permessage-deflate-bomb.phpt +++ b/tests/phpt/websocket/021-permessage-deflate-bomb.phpt @@ -21,8 +21,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19890 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/022-h2-permessage-deflate.phpt b/tests/phpt/websocket/022-h2-permessage-deflate.phpt index 6877d032..3b15c50d 100644 --- a/tests/phpt/websocket/022-h2-permessage-deflate.phpt +++ b/tests/phpt/websocket/022-h2-permessage-deflate.phpt @@ -22,8 +22,9 @@ use TrueAsync\HttpRequest; use function Async\spawn; use function Async\await; -$port = 19940 + getmypid() % 50; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(10) diff --git a/tests/phpt/websocket/023-backpressure-trysend.phpt b/tests/phpt/websocket/023-backpressure-trysend.phpt index b7162790..fd0d03f0 100644 --- a/tests/phpt/websocket/023-backpressure-trysend.phpt +++ b/tests/phpt/websocket/023-backpressure-trysend.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19990 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/024-backpressure-send-blocks.phpt b/tests/phpt/websocket/024-backpressure-send-blocks.phpt index ee004cb8..2ac6dac0 100644 --- a/tests/phpt/websocket/024-backpressure-send-blocks.phpt +++ b/tests/phpt/websocket/024-backpressure-send-blocks.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19790 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/025-spawned-writer-teardown.phpt b/tests/phpt/websocket/025-spawned-writer-teardown.phpt index 2e5cd18d..607189ee 100644 --- a/tests/phpt/websocket/025-spawned-writer-teardown.phpt +++ b/tests/phpt/websocket/025-spawned-writer-teardown.phpt @@ -32,7 +32,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19680 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; + +$port = tas_free_port(); $N = 4; $config = (new HttpServerConfig()) diff --git a/tests/phpt/websocket/026-pong-timeout-close.phpt b/tests/phpt/websocket/026-pong-timeout-close.phpt index 67839fc3..e815de5d 100644 --- a/tests/phpt/websocket/026-pong-timeout-close.phpt +++ b/tests/phpt/websocket/026-pong-timeout-close.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19960 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig()) ->addListener('127.0.0.1', $port) ->setReadTimeout(5) diff --git a/tests/phpt/websocket/027-handshake-bad-version.phpt b/tests/phpt/websocket/027-handshake-bad-version.phpt index 9f71462e..287d9a93 100644 --- a/tests/phpt/websocket/027-handshake-bad-version.phpt +++ b/tests/phpt/websocket/027-handshake-bad-version.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19970 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/028-handshake-missing-key.phpt b/tests/phpt/websocket/028-handshake-missing-key.phpt index eea590b2..62f99d49 100644 --- a/tests/phpt/websocket/028-handshake-missing-key.phpt +++ b/tests/phpt/websocket/028-handshake-missing-key.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19980 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/029-handshake-bad-method.phpt b/tests/phpt/websocket/029-handshake-bad-method.phpt index 645aa654..81c4d3ca 100644 --- a/tests/phpt/websocket/029-handshake-bad-method.phpt +++ b/tests/phpt/websocket/029-handshake-bad-method.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19990 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/030-utf8-failfast-teardown.phpt b/tests/phpt/websocket/030-utf8-failfast-teardown.phpt index 1ac78837..a0461b57 100644 --- a/tests/phpt/websocket/030-utf8-failfast-teardown.phpt +++ b/tests/phpt/websocket/030-utf8-failfast-teardown.phpt @@ -19,8 +19,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19730 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/031-outbound-fragmentation.phpt b/tests/phpt/websocket/031-outbound-fragmentation.phpt index 42d9f644..f15aef50 100644 --- a/tests/phpt/websocket/031-outbound-fragmentation.phpt +++ b/tests/phpt/websocket/031-outbound-fragmentation.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19740 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5) ->setWsMaxFrameSize(128); // cap each frame at 128 bytes (setter minimum) diff --git a/tests/phpt/websocket/032-closed-exception-code.phpt b/tests/phpt/websocket/032-closed-exception-code.phpt index 48e3aaa2..d06a098d 100644 --- a/tests/phpt/websocket/032-closed-exception-code.phpt +++ b/tests/phpt/websocket/032-closed-exception-code.phpt @@ -14,8 +14,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19750 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/033-foreach-iterator.phpt b/tests/phpt/websocket/033-foreach-iterator.phpt index 1d4b01f4..52676e90 100644 --- a/tests/phpt/websocket/033-foreach-iterator.phpt +++ b/tests/phpt/websocket/033-foreach-iterator.phpt @@ -13,8 +13,9 @@ use function Async\spawn; use function Async\await; use function Async\delay; -$port = 19760 + getmypid() % 100; +require_once __DIR__ . '/../server/_free_port.inc'; +$port = tas_free_port(); $config = (new HttpServerConfig())->addListener('127.0.0.1', $port) ->setReadTimeout(5)->setWriteTimeout(5); $server = new HttpServer($config); diff --git a/tests/phpt/websocket/034-hot-reload-ws.phpt b/tests/phpt/websocket/034-hot-reload-ws.phpt new file mode 100644 index 00000000..5a054e2e --- /dev/null +++ b/tests/phpt/websocket/034-hot-reload-ws.phpt @@ -0,0 +1,146 @@ +--TEST-- +WebSocket: pool reload with a live WS connection — old conn retired, fresh worker serves WS (#93) +--EXTENSIONS-- +true_async_server +true_async +--SKIPIF-- + +--FILE-- +addListener('127.0.0.1', $port) + ->setReadTimeout(5) + ->setWriteTimeout(5) + ->setShutdownTimeout(1) /* keep the drain grace short for the rotation */ + ->setWorkers(2); + +$server = new HttpServer($config); + +$server->addWebSocketHandler(function (WebSocket $ws, HttpRequest $req) { + while (($msg = $ws->recv()) !== null) { + $ws->send('echo: ' . $msg->data); + } +}); + +$server->addHttpHandler(function ($req, $resp) { + $resp->setStatusCode(404)->end(); +}); + +function ws_client_text_frame(string $payload): string { + $mask = "abcd"; + $masked = ''; + for ($i = 0, $n = strlen($payload); $i < $n; $i++) { + $masked .= chr(ord($payload[$i]) ^ ord($mask[$i & 3])); + } + return chr(0x81) . chr(0x80 | strlen($payload)) . $mask . $masked; +} + +function read_server_frame($fp): array { + $hdr = ''; + while (strlen($hdr) < 2) { + $c = fread($fp, 2 - strlen($hdr)); + if ($c === false || $c === '') return ['opcode' => -1, 'data' => '']; + $hdr .= $c; + } + $opcode = ord($hdr[0]) & 0x0f; + $len = ord($hdr[1]) & 0x7f; + if ($len === 126) { + $extra = fread($fp, 2); + $len = (ord($extra[0]) << 8) | ord($extra[1]); + } + $data = ''; + while (strlen($data) < $len) { + $c = fread($fp, $len - strlen($data)); + if ($c === false || $c === '') break; + $data .= $c; + } + return ['opcode' => $opcode, 'data' => $data]; +} + +function ws_connect(int $port) { + $fp = @stream_socket_client("tcp://127.0.0.1:$port", $errno, $errstr, 2); + if ($fp === false) return false; + stream_set_timeout($fp, 5); + fwrite($fp, + "GET / HTTP/1.1\r\nHost: x\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n" + . "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n"); + $hs = ''; + while (!str_contains($hs, "\r\n\r\n")) { + $c = fread($fp, 4096); + if ($c === false || $c === '') return false; + $hs .= $c; + } + return str_contains($hs, ' 101 ') ? $fp : false; +} + +spawn(function () use ($server, $port) { + usleep(400000); /* let the pool come up */ + + /* Live WS connection on the old cohort. */ + $fp = false; + for ($i = 0; $i < 25 && $fp === false; $i++) { + usleep(200000); + $fp = ws_connect($port); + } + + if ($fp === false) { + echo "pre_ws=fail\n"; + posix_kill(getmypid(), SIGKILL); + } + + fwrite($fp, ws_client_text_frame('one')); + $r1 = read_server_frame($fp); + echo "pre_echo=", $r1['data'], "\n"; + + /* Rotate while the connection is open. */ + $ok = $server->reload(); + echo "reload=", var_export($ok, true), "\n"; + + /* The retired worker must end our connection: close frame (8) or EOF (-1). */ + $r2 = read_server_frame($fp); + echo "old_conn_ended=", (int)($r2['opcode'] === 8 || $r2['opcode'] === -1), "\n"; + fclose($fp); + + /* Fresh cohort serves new WS connections on the same port. */ + $fp2 = false; + for ($i = 0; $i < 50 && $fp2 === false; $i++) { + usleep(200000); + $fp2 = ws_connect($port); + } + + if ($fp2 !== false) { + fwrite($fp2, ws_client_text_frame('two')); + $r3 = read_server_frame($fp2); + echo "post_echo=", $r3['data'], "\n"; + fclose($fp2); + } else { + echo "post_echo=fail\n"; + } + + posix_kill(getmypid(), SIGKILL); +}); + +$server->start(); +?> +--EXPECTF-- +pre_echo=echo: one +%A +reload=true +old_conn_ended=1 +post_echo=echo: two%A