Skip to content

Commit 60fe096

Browse files
committed
fix(worker): reset ini and session if changed during worker
1 parent 86b9ffc commit 60fe096

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;
@@ -174,6 +191,193 @@ static void frankenphp_release_temporary_streams() {
174191
ZEND_HASH_FOREACH_END();
175192
}
176193

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

415+
/* Snapshot INI and session handlers BEFORE shutdown.
416+
* The framework has set these up before the worker loop, and we want
417+
* to preserve them. Session RSHUTDOWN will free the handlers. */
418+
frankenphp_snapshot_ini();
419+
frankenphp_snapshot_session_handlers();
420+
211421
frankenphp_worker_request_shutdown();
212422

213423
return true;
@@ -263,6 +473,12 @@ static int frankenphp_worker_request_startup() {
263473

264474
frankenphp_reset_super_globals();
265475

476+
/* Restore INI values changed during the previous request back to their
477+
* snapshot state (captured in frankenphp_shutdown_dummy_request).
478+
* This ensures framework settings persist while request-level changes
479+
* are reset. */
480+
frankenphp_restore_ini();
481+
266482
const char **module_name;
267483
zend_module_entry *module;
268484
for (module_name = MODULES_TO_RELOAD; *module_name; module_name++) {
@@ -272,6 +488,12 @@ static int frankenphp_worker_request_startup() {
272488
module->request_startup_func(module->type, module->module_number);
273489
}
274490
}
491+
492+
/* Restore session handlers AFTER session RINIT.
493+
* Session RSHUTDOWN frees mod_user_names callbacks, so we must restore
494+
* them before user code runs. This must happen after RINIT because
495+
* session RINIT may reset some state. */
496+
frankenphp_restore_session_handlers();
275497
}
276498
zend_catch { retval = FAILURE; }
277499
zend_end_try();
@@ -617,6 +839,9 @@ static zend_module_entry frankenphp_module = {
617839
STANDARD_MODULE_PROPERTIES};
618840

619841
static void frankenphp_request_shutdown() {
842+
if (is_worker_thread) {
843+
frankenphp_cleanup_worker_state();
844+
}
620845
php_request_shutdown((void *)0);
621846
frankenphp_free_request_context();
622847
}

0 commit comments

Comments
 (0)