Skip to content

Commit b8621b1

Browse files
committed
fix(worker): reset ini and session if changed during worker
1 parent d9ba18f commit b8621b1

6 files changed

Lines changed: 746 additions & 0 deletions

File tree

frankenphp.c

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <Zend/zend_interfaces.h>
55
#include <Zend/zend_types.h>
66
#include <errno.h>
7+
#include <ext/session/php_session.h>
78
#include <ext/spl/spl_exceptions.h>
89
#include <ext/standard/head.h>
910
#include <inttypes.h>
@@ -73,6 +74,22 @@ bool should_filter_var = 0;
7374
__thread uintptr_t thread_index;
7475
__thread bool is_worker_thread = false;
7576
__thread zval *os_environment = NULL;
77+
__thread HashTable *worker_ini_snapshot = NULL;
78+
79+
/* Session user handler names (same structure as PS(mod_user_names)) */
80+
typedef struct {
81+
zval ps_open;
82+
zval ps_close;
83+
zval ps_read;
84+
zval ps_write;
85+
zval ps_destroy;
86+
zval ps_gc;
87+
zval ps_create_sid;
88+
zval ps_validate_sid;
89+
zval ps_update_timestamp;
90+
} session_user_handlers;
91+
92+
__thread session_user_handlers *worker_session_handlers_snapshot = NULL;
7693

7794
void frankenphp_update_local_thread_context(bool is_worker) {
7895
is_worker_thread = is_worker;
@@ -166,6 +183,193 @@ static void frankenphp_release_temporary_streams() {
166183
ZEND_HASH_FOREACH_END();
167184
}
168185

186+
/* Destructor for INI snapshot hash table entries */
187+
static void ini_snapshot_dtor(zval *zv) {
188+
zend_string_release((zend_string *)Z_PTR_P(zv));
189+
}
190+
191+
/* Save the current state of modified INI entries.
192+
* This captures INI values set by the framework before the worker loop. */
193+
static void frankenphp_snapshot_ini(void) {
194+
if (worker_ini_snapshot != NULL) {
195+
return; /* Already snapshotted */
196+
}
197+
198+
ALLOC_HASHTABLE(worker_ini_snapshot);
199+
zend_hash_init(worker_ini_snapshot, 8, NULL, ini_snapshot_dtor, 0);
200+
201+
if (EG(modified_ini_directives) == NULL) {
202+
return; /* No modifications to snapshot */
203+
}
204+
205+
zend_ini_entry *ini_entry;
206+
ZEND_HASH_FOREACH_PTR(EG(modified_ini_directives), ini_entry) {
207+
if (ini_entry->value) {
208+
zend_hash_add_ptr(worker_ini_snapshot, ini_entry->name,
209+
zend_string_copy(ini_entry->value));
210+
}
211+
}
212+
ZEND_HASH_FOREACH_END();
213+
}
214+
215+
/* Restore INI values to the state captured by frankenphp_snapshot_ini().
216+
* - Entries in snapshot with changed values: restore to snapshot value
217+
* - Entries not in snapshot: restore to startup default */
218+
static void frankenphp_restore_ini(void) {
219+
if (worker_ini_snapshot == NULL || EG(modified_ini_directives) == NULL) {
220+
return;
221+
}
222+
223+
zend_ini_entry *ini_entry;
224+
zend_string *snapshot_value;
225+
zend_string *entry_name;
226+
227+
/* Collect entries to restore to default in a separate array.
228+
* We cannot call zend_restore_ini_entry() during iteration because
229+
* it calls zend_hash_del() on EG(modified_ini_directives). */
230+
zend_string **entries_to_restore = NULL;
231+
size_t restore_count = 0;
232+
size_t restore_capacity = 0;
233+
234+
ZEND_HASH_FOREACH_STR_KEY_PTR(EG(modified_ini_directives), entry_name,
235+
ini_entry) {
236+
snapshot_value = zend_hash_find_ptr(worker_ini_snapshot, entry_name);
237+
238+
if (snapshot_value == NULL) {
239+
/* Entry was not in snapshot: collect for restore to startup default */
240+
if (restore_count >= restore_capacity) {
241+
restore_capacity = restore_capacity ? restore_capacity * 2 : 8;
242+
entries_to_restore =
243+
erealloc(entries_to_restore, restore_capacity * sizeof(zend_string *));
244+
}
245+
entries_to_restore[restore_count++] = zend_string_copy(entry_name);
246+
} else if (!zend_string_equals(ini_entry->value, snapshot_value)) {
247+
/* Entry was in snapshot but value changed: restore to snapshot value.
248+
* zend_alter_ini_entry() does not delete from modified_ini_directives. */
249+
zend_alter_ini_entry(entry_name, snapshot_value, PHP_INI_USER,
250+
PHP_INI_STAGE_RUNTIME);
251+
}
252+
/* else: Entry in snapshot with same value, nothing to do */
253+
}
254+
ZEND_HASH_FOREACH_END();
255+
256+
/* Now restore entries to default outside of iteration */
257+
for (size_t i = 0; i < restore_count; i++) {
258+
zend_restore_ini_entry(entries_to_restore[i], PHP_INI_STAGE_RUNTIME);
259+
zend_string_release(entries_to_restore[i]);
260+
}
261+
if (entries_to_restore) {
262+
efree(entries_to_restore);
263+
}
264+
}
265+
266+
/* Save session user handlers set before the worker loop.
267+
* This allows frameworks to define custom session handlers that persist. */
268+
static void frankenphp_snapshot_session_handlers(void) {
269+
if (worker_session_handlers_snapshot != NULL) {
270+
return; /* Already snapshotted */
271+
}
272+
273+
/* Check if session module is loaded */
274+
if (zend_hash_str_find_ptr(&module_registry, "session",
275+
sizeof("session") - 1) == NULL) {
276+
return; /* Session module not available */
277+
}
278+
279+
/* Check if user session handlers are defined */
280+
if (Z_ISUNDEF(PS(mod_user_names).ps_open)) {
281+
return; /* No user handlers to snapshot */
282+
}
283+
284+
worker_session_handlers_snapshot = malloc(sizeof(session_user_handlers));
285+
if (worker_session_handlers_snapshot == NULL) {
286+
return; /* Memory allocation failed */
287+
}
288+
289+
/* Copy each handler zval with incremented reference count */
290+
#define SNAPSHOT_HANDLER(name) \
291+
if (!Z_ISUNDEF(PS(mod_user_names).name)) { \
292+
ZVAL_COPY(&worker_session_handlers_snapshot->name, \
293+
&PS(mod_user_names).name); \
294+
} else { \
295+
ZVAL_UNDEF(&worker_session_handlers_snapshot->name); \
296+
}
297+
298+
SNAPSHOT_HANDLER(ps_open);
299+
SNAPSHOT_HANDLER(ps_close);
300+
SNAPSHOT_HANDLER(ps_read);
301+
SNAPSHOT_HANDLER(ps_write);
302+
SNAPSHOT_HANDLER(ps_destroy);
303+
SNAPSHOT_HANDLER(ps_gc);
304+
SNAPSHOT_HANDLER(ps_create_sid);
305+
SNAPSHOT_HANDLER(ps_validate_sid);
306+
SNAPSHOT_HANDLER(ps_update_timestamp);
307+
308+
#undef SNAPSHOT_HANDLER
309+
}
310+
311+
/* Restore session user handlers from snapshot after RSHUTDOWN freed them. */
312+
static void frankenphp_restore_session_handlers(void) {
313+
if (worker_session_handlers_snapshot == NULL) {
314+
return;
315+
}
316+
317+
/* Restore each handler zval */
318+
#define RESTORE_HANDLER(name) \
319+
if (!Z_ISUNDEF(worker_session_handlers_snapshot->name)) { \
320+
if (!Z_ISUNDEF(PS(mod_user_names).name)) { \
321+
zval_ptr_dtor(&PS(mod_user_names).name); \
322+
} \
323+
ZVAL_COPY(&PS(mod_user_names).name, \
324+
&worker_session_handlers_snapshot->name); \
325+
}
326+
327+
RESTORE_HANDLER(ps_open);
328+
RESTORE_HANDLER(ps_close);
329+
RESTORE_HANDLER(ps_read);
330+
RESTORE_HANDLER(ps_write);
331+
RESTORE_HANDLER(ps_destroy);
332+
RESTORE_HANDLER(ps_gc);
333+
RESTORE_HANDLER(ps_create_sid);
334+
RESTORE_HANDLER(ps_validate_sid);
335+
RESTORE_HANDLER(ps_update_timestamp);
336+
337+
#undef RESTORE_HANDLER
338+
}
339+
340+
/* Free worker state when the worker script terminates. */
341+
static void frankenphp_cleanup_worker_state(void) {
342+
/* Free INI snapshot */
343+
if (worker_ini_snapshot != NULL) {
344+
zend_hash_destroy(worker_ini_snapshot);
345+
FREE_HASHTABLE(worker_ini_snapshot);
346+
worker_ini_snapshot = NULL;
347+
}
348+
349+
/* Free session handlers snapshot */
350+
if (worker_session_handlers_snapshot != NULL) {
351+
#define FREE_HANDLER(name) \
352+
if (!Z_ISUNDEF(worker_session_handlers_snapshot->name)) { \
353+
zval_ptr_dtor(&worker_session_handlers_snapshot->name); \
354+
}
355+
356+
FREE_HANDLER(ps_open);
357+
FREE_HANDLER(ps_close);
358+
FREE_HANDLER(ps_read);
359+
FREE_HANDLER(ps_write);
360+
FREE_HANDLER(ps_destroy);
361+
FREE_HANDLER(ps_gc);
362+
FREE_HANDLER(ps_create_sid);
363+
FREE_HANDLER(ps_validate_sid);
364+
FREE_HANDLER(ps_update_timestamp);
365+
366+
#undef FREE_HANDLER
367+
368+
free(worker_session_handlers_snapshot);
369+
worker_session_handlers_snapshot = NULL;
370+
}
371+
}
372+
169373
/* Adapted from php_request_shutdown */
170374
static void frankenphp_worker_request_shutdown() {
171375
/* Flush all output buffers */
@@ -200,6 +404,12 @@ bool frankenphp_shutdown_dummy_request(void) {
200404
return false;
201405
}
202406

407+
/* Snapshot INI and session handlers BEFORE shutdown.
408+
* The framework has set these up before the worker loop, and we want
409+
* to preserve them. Session RSHUTDOWN will free the handlers. */
410+
frankenphp_snapshot_ini();
411+
frankenphp_snapshot_session_handlers();
412+
203413
frankenphp_worker_request_shutdown();
204414

205415
return true;
@@ -255,6 +465,12 @@ static int frankenphp_worker_request_startup() {
255465

256466
frankenphp_reset_super_globals();
257467

468+
/* Restore INI values changed during the previous request back to their
469+
* snapshot state (captured in frankenphp_shutdown_dummy_request).
470+
* This ensures framework settings persist while request-level changes
471+
* are reset. */
472+
frankenphp_restore_ini();
473+
258474
const char **module_name;
259475
zend_module_entry *module;
260476
for (module_name = MODULES_TO_RELOAD; *module_name; module_name++) {
@@ -264,6 +480,12 @@ static int frankenphp_worker_request_startup() {
264480
module->request_startup_func(module->type, module->module_number);
265481
}
266482
}
483+
484+
/* Restore session handlers AFTER session RINIT.
485+
* Session RSHUTDOWN frees mod_user_names callbacks, so we must restore
486+
* them before user code runs. This must happen after RINIT because
487+
* session RINIT may reset some state. */
488+
frankenphp_restore_session_handlers();
267489
}
268490
zend_catch { retval = FAILURE; }
269491
zend_end_try();
@@ -610,6 +832,9 @@ static zend_module_entry frankenphp_module = {
610832

611833
static void frankenphp_request_shutdown() {
612834
frankenphp_free_request_context();
835+
if (is_worker_thread) {
836+
frankenphp_cleanup_worker_state();
837+
}
613838
php_request_shutdown((void *)0);
614839
}
615840

0 commit comments

Comments
 (0)