From 826660ead2678c02a7fbc85c7a864501ec0d94e1 Mon Sep 17 00:00:00 2001 From: Appla Date: Tue, 15 Sep 2026 14:42:03 +0800 Subject: [PATCH 1/2] Add `v8js.thread_pool_size` INI directive to control V8 thread pool size Allows configuring the background worker thread pool size passed to v8::platform::NewDefaultPlatform(), preventing thread explosion in multi-process environments like PHP-FPM. --- php_v8js_macros.h | 3 +++ v8js_main.cc | 30 ++++++++++++++++++++++++++++++ v8js_v8.cc | 6 +++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/php_v8js_macros.h b/php_v8js_macros.h index 7f0d019e..c65403e3 100644 --- a/php_v8js_macros.h +++ b/php_v8js_macros.h @@ -159,6 +159,9 @@ struct _v8js_process_globals { /* Path to icudtl.dat file */ char *icudtl_dat_path; + /* Number of V8 platform background worker threads */ + int thread_pool_size; + std::unique_ptr v8_platform; }; diff --git a/v8js_main.cc b/v8js_main.cc index 627949a0..01574c18 100644 --- a/v8js_main.cc +++ b/v8js_main.cc @@ -112,11 +112,41 @@ static ZEND_INI_MH(v8js_OnUpdateUseArrayAccess) /* {{{ */ } /* }}} */ +static ZEND_INI_MH(v8js_OnUpdateThreadPoolSize) /* {{{ */ +{ +#if PHP_VERSION_ID < 80200 + zend_long tmp = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); +#else + zend_long tmp = zend_ini_parse_quantity_warn(new_value, entry->name); +#endif + /* V8 has hard limit of 16 threads for now, we allow up to 64 */ + if (tmp < 0 || tmp > 64) { + return FAILURE; + } + +#ifdef ZTS + std::lock_guard lock(v8js_process_globals.lock); + if (v8js_process_globals.v8_initialized) { + return FAILURE; + } +#else + /* V8 already has been initialized -> cannot be changed anymore */ + if (V8JSG(v8_initialized)) { + return FAILURE; + } +#endif + + v8js_process_globals.thread_pool_size = static_cast(tmp); + return SUCCESS; +} +/* }}} */ + ZEND_INI_BEGIN() /* {{{ */ ZEND_INI_ENTRY("v8js.flags", NULL, ZEND_INI_ALL, v8js_OnUpdateV8Flags) ZEND_INI_ENTRY("v8js.icudtl_dat_path", NULL, ZEND_INI_ALL, v8js_OnUpdateIcudatPath) ZEND_INI_ENTRY("v8js.use_date", "0", ZEND_INI_ALL, v8js_OnUpdateUseDate) ZEND_INI_ENTRY("v8js.use_array_access", "0", ZEND_INI_ALL, v8js_OnUpdateUseArrayAccess) + ZEND_INI_ENTRY("v8js.thread_pool_size", "0", ZEND_INI_ALL, v8js_OnUpdateThreadPoolSize) ZEND_INI_END() /* }}} */ diff --git a/v8js_v8.cc b/v8js_v8.cc index 467f201b..cba06e21 100644 --- a/v8js_v8.cc +++ b/v8js_v8.cc @@ -68,7 +68,11 @@ void v8js_v8_init() /* {{{ */ #endif #endif - v8js_process_globals.v8_platform = v8::platform::NewDefaultPlatform(); + if (v8js_process_globals.thread_pool_size > 0) { + v8js_process_globals.v8_platform = v8::platform::NewDefaultPlatform(v8js_process_globals.thread_pool_size); + } else { + v8js_process_globals.v8_platform = v8::platform::NewDefaultPlatform(); + } v8::V8::InitializePlatform(v8js_process_globals.v8_platform.get()); #ifdef V8_HAS_INITIALIZE_SANDBOX From ef89faebcb5762da2bad6005196f2420fca4fe06 Mon Sep 17 00:00:00 2001 From: Appla Date: Wed, 16 Sep 2026 11:24:14 +0800 Subject: [PATCH 2/2] test: add PHPT test coverage for v8js.thread_pool_size directive --- tests/thread_pool_size.phpt | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/thread_pool_size.phpt diff --git a/tests/thread_pool_size.phpt b/tests/thread_pool_size.phpt new file mode 100644 index 00000000..f9cf1412 --- /dev/null +++ b/tests/thread_pool_size.phpt @@ -0,0 +1,76 @@ +--TEST-- +v8js.thread_pool_size INI directive validation and lifecycle +--SKIPIF-- + +--INI-- +v8js.thread_pool_size=0 +--FILE-- +executeString('1 + 2')); + +// 5. Attempts to change setting after initialization must fail +var_dump(ini_set('v8js.thread_pool_size', '2')); +var_dump(ini_set('v8js.thread_pool_size', '0')); +var_dump(ini_set('v8js.thread_pool_size', '64')); +var_dump(ini_get('v8js.thread_pool_size')); + +// 6. Attempts to change setting after destroying instance must still fail +unset($v8); +var_dump(ini_set('v8js.thread_pool_size', '8')); +var_dump(ini_set('v8js.thread_pool_size', '0')); +var_dump(ini_get('v8js.thread_pool_size')); +?> +===EOF=== +--EXPECT-- +string(1) "0" +string(1) "0" +string(1) "1" +string(1) "1" +string(2) "64" +string(2) "64" +string(1) "0" +string(1) "0" +string(1) "4" +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +string(1) "4" +int(3) +bool(false) +bool(false) +bool(false) +string(1) "4" +bool(false) +bool(false) +string(1) "4" +===EOF===