Skip to content

Commit 681aae6

Browse files
dunglasxavierleuneclaude
committed
fix(worker): revert ini reset, keep session fixes (#2139)
Revert the INI snapshot/restore mechanism from #2139 which caused issues with frameworks that lazily set ini values like session.save_path (#2185). Replace the session handler snapshot/restore with a simpler direct session state reset from #2193, which preserves mod_user_names across requests without requiring session module reload. Co-Authored-By: Xavier Leune <xavier.leune@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 036aa2b commit 681aae6

4 files changed

Lines changed: 44 additions & 427 deletions

File tree

frankenphp.c

Lines changed: 40 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
#include <Zend/zend_interfaces.h>
55
#include <Zend/zend_types.h>
66
#include <errno.h>
7-
#include <ext/session/php_session.h>
87
#include <ext/spl/spl_exceptions.h>
98
#include <ext/standard/head.h>
9+
#ifdef HAVE_PHP_SESSION
10+
#include <ext/session/php_session.h>
11+
#endif
1012
#include <inttypes.h>
1113
#include <php.h>
1214
#include <php_config.h>
@@ -41,7 +43,7 @@ ZEND_TSRMLS_CACHE_DEFINE()
4143
*
4244
* @see https://github.com/DataDog/dd-trace-php/pull/3169 for an example
4345
*/
44-
static const char *MODULES_TO_RELOAD[] = {"filter", "session", NULL};
46+
static const char *MODULES_TO_RELOAD[] = {"filter", NULL};
4547

4648
frankenphp_version frankenphp_get_version() {
4749
return (frankenphp_version){
@@ -76,42 +78,6 @@ bool original_user_abort_setting = 0;
7678
__thread uintptr_t thread_index;
7779
__thread bool is_worker_thread = false;
7880
__thread zval *os_environment = NULL;
79-
__thread HashTable *worker_ini_snapshot = NULL;
80-
81-
/* Session user handler names (same structure as PS(mod_user_names)).
82-
* In PHP 8.2, mod_user_names is a union with .name.ps_* access.
83-
* In PHP 8.3+, mod_user_names is a direct struct with .ps_* access. */
84-
typedef struct {
85-
zval ps_open;
86-
zval ps_close;
87-
zval ps_read;
88-
zval ps_write;
89-
zval ps_destroy;
90-
zval ps_gc;
91-
zval ps_create_sid;
92-
zval ps_validate_sid;
93-
zval ps_update_timestamp;
94-
} session_user_handlers;
95-
96-
/* Macro to access PS(mod_user_names) handlers across PHP versions */
97-
#if PHP_VERSION_ID >= 80300
98-
#define PS_MOD_USER_NAMES(handler) PS(mod_user_names).handler
99-
#else
100-
#define PS_MOD_USER_NAMES(handler) PS(mod_user_names).name.handler
101-
#endif
102-
103-
#define FOR_EACH_SESSION_HANDLER(op) \
104-
op(ps_open); \
105-
op(ps_close); \
106-
op(ps_read); \
107-
op(ps_write); \
108-
op(ps_destroy); \
109-
op(ps_gc); \
110-
op(ps_create_sid); \
111-
op(ps_validate_sid); \
112-
op(ps_update_timestamp)
113-
114-
__thread session_user_handlers *worker_session_handlers_snapshot = NULL;
11581

11682
void frankenphp_update_local_thread_context(bool is_worker) {
11783
is_worker_thread = is_worker;
@@ -223,165 +189,52 @@ static void frankenphp_release_temporary_streams() {
223189
ZEND_HASH_FOREACH_END();
224190
}
225191

226-
/* Destructor for INI snapshot hash table entries */
227-
static void frankenphp_ini_snapshot_dtor(zval *zv) {
228-
zend_string_release((zend_string *)Z_PTR_P(zv));
229-
}
230-
231-
/* Save the current state of modified INI entries.
232-
* This captures INI values set by the framework before the worker loop. */
233-
static void frankenphp_snapshot_ini(void) {
234-
if (worker_ini_snapshot != NULL) {
235-
return; /* Already snapshotted */
236-
}
237-
238-
if (EG(modified_ini_directives) == NULL) {
239-
/* Allocate empty table to mark as snapshotted */
240-
ALLOC_HASHTABLE(worker_ini_snapshot);
241-
zend_hash_init(worker_ini_snapshot, 0, NULL, frankenphp_ini_snapshot_dtor,
242-
0);
243-
return;
244-
}
245-
246-
uint32_t num_modified = zend_hash_num_elements(EG(modified_ini_directives));
247-
ALLOC_HASHTABLE(worker_ini_snapshot);
248-
zend_hash_init(worker_ini_snapshot, num_modified, NULL,
249-
frankenphp_ini_snapshot_dtor, 0);
250-
251-
zend_ini_entry *ini_entry;
252-
ZEND_HASH_FOREACH_PTR(EG(modified_ini_directives), ini_entry) {
253-
if (ini_entry->value) {
254-
zend_hash_add_ptr(worker_ini_snapshot, ini_entry->name,
255-
zend_string_copy(ini_entry->value));
256-
}
257-
}
258-
ZEND_HASH_FOREACH_END();
259-
}
260-
261-
/* Restore INI values to the state captured by frankenphp_snapshot_ini().
262-
* - Entries in snapshot with changed values: restore to snapshot value
263-
* - Entries not in snapshot: restore to startup default */
264-
static void frankenphp_restore_ini(void) {
265-
if (worker_ini_snapshot == NULL || EG(modified_ini_directives) == NULL) {
266-
return;
192+
#ifdef HAVE_PHP_SESSION
193+
/* Reset session state between worker requests, preserving user handlers.
194+
* Based on php_rshutdown_session_globals() + php_rinit_session_globals(). */
195+
static void frankenphp_reset_session_state(void) {
196+
if (PS(session_status) == php_session_active) {
197+
php_session_flush(1);
267198
}
268199

269-
zend_ini_entry *ini_entry;
270-
zend_string *snapshot_value;
271-
zend_string *entry_name;
272-
273-
/* Collect entries to restore to default in a separate array.
274-
* We cannot call zend_restore_ini_entry() during iteration because
275-
* it calls zend_hash_del() on EG(modified_ini_directives). */
276-
uint32_t max_entries = zend_hash_num_elements(EG(modified_ini_directives));
277-
zend_string **entries_to_restore =
278-
max_entries ? emalloc(max_entries * sizeof(zend_string *)) : NULL;
279-
size_t restore_count = 0;
280-
281-
ZEND_HASH_FOREACH_STR_KEY_PTR(EG(modified_ini_directives), entry_name,
282-
ini_entry) {
283-
snapshot_value = zend_hash_find_ptr(worker_ini_snapshot, entry_name);
284-
285-
if (snapshot_value == NULL) {
286-
/* Entry was not in snapshot: collect for restore to startup default */
287-
entries_to_restore[restore_count++] = zend_string_copy(entry_name);
288-
} else if (!zend_string_equals(ini_entry->value, snapshot_value)) {
289-
/* Entry was in snapshot but value changed: restore to snapshot value.
290-
* zend_alter_ini_entry() does not delete from modified_ini_directives. */
291-
zend_alter_ini_entry(entry_name, snapshot_value, PHP_INI_USER,
292-
PHP_INI_STAGE_RUNTIME);
293-
}
294-
/* else: Entry in snapshot with same value, nothing to do */
200+
if (!Z_ISUNDEF(PS(http_session_vars))) {
201+
zval_ptr_dtor(&PS(http_session_vars));
202+
ZVAL_UNDEF(&PS(http_session_vars));
295203
}
296-
ZEND_HASH_FOREACH_END();
297-
298-
/* Now restore entries to default outside of iteration */
299-
for (size_t i = 0; i < restore_count; i++) {
300-
zend_restore_ini_entry(entries_to_restore[i], PHP_INI_STAGE_RUNTIME);
301-
zend_string_release(entries_to_restore[i]);
302-
}
303-
if (entries_to_restore) {
304-
efree(entries_to_restore);
305-
}
306-
}
307204

308-
/* Save session user handlers set before the worker loop.
309-
* This allows frameworks to define custom session handlers that persist. */
310-
static void frankenphp_snapshot_session_handlers(void) {
311-
if (worker_session_handlers_snapshot != NULL) {
312-
return; /* Already snapshotted */
205+
if (PS(mod_data) || PS(mod_user_implemented)) {
206+
zend_try { PS(mod)->s_close(&PS(mod_data)); }
207+
zend_end_try();
313208
}
314209

315-
/* Check if session module is loaded */
316-
if (zend_hash_str_find_ptr(&module_registry, "session",
317-
sizeof("session") - 1) == NULL) {
318-
return; /* Session module not available */
210+
if (PS(id)) {
211+
zend_string_release_ex(PS(id), 0);
212+
PS(id) = NULL;
319213
}
320214

321-
/* Check if user session handlers are defined */
322-
if (Z_ISUNDEF(PS_MOD_USER_NAMES(ps_open))) {
323-
return; /* No user handlers to snapshot */
215+
if (PS(session_vars)) {
216+
zend_string_release_ex(PS(session_vars), 0);
217+
PS(session_vars) = NULL;
324218
}
325219

326-
worker_session_handlers_snapshot = emalloc(sizeof(session_user_handlers));
220+
/* PS(mod_user_class_name) and PS(mod_user_names) are preserved */
327221

328-
/* Copy each handler zval with incremented reference count */
329-
#define SNAPSHOT_HANDLER(h) \
330-
if (!Z_ISUNDEF(PS_MOD_USER_NAMES(h))) { \
331-
ZVAL_COPY(&worker_session_handlers_snapshot->h, &PS_MOD_USER_NAMES(h)); \
332-
} else { \
333-
ZVAL_UNDEF(&worker_session_handlers_snapshot->h); \
334-
}
335-
336-
FOR_EACH_SESSION_HANDLER(SNAPSHOT_HANDLER);
337-
338-
#undef SNAPSHOT_HANDLER
339-
}
340-
341-
/* Restore session user handlers from snapshot after RSHUTDOWN freed them. */
342-
static void frankenphp_restore_session_handlers(void) {
343-
if (worker_session_handlers_snapshot == NULL) {
344-
return;
345-
}
346-
347-
/* Restore each handler zval.
348-
* Session RSHUTDOWN already freed the handlers via zval_ptr_dtor and set
349-
* them to UNDEF, so we don't need to destroy them again. We simply copy
350-
* from the snapshot (which holds its own reference). */
351-
#define RESTORE_HANDLER(h) \
352-
if (!Z_ISUNDEF(worker_session_handlers_snapshot->h)) { \
353-
ZVAL_COPY(&PS_MOD_USER_NAMES(h), &worker_session_handlers_snapshot->h); \
354-
}
355-
356-
FOR_EACH_SESSION_HANDLER(RESTORE_HANDLER);
357-
358-
#undef RESTORE_HANDLER
359-
}
360-
361-
/* Free worker state when the worker script terminates. */
362-
static void frankenphp_cleanup_worker_state(void) {
363-
/* Free INI snapshot */
364-
if (worker_ini_snapshot != NULL) {
365-
zend_hash_destroy(worker_ini_snapshot);
366-
FREE_HASHTABLE(worker_ini_snapshot);
367-
worker_ini_snapshot = NULL;
368-
}
369-
370-
/* Free session handlers snapshot */
371-
if (worker_session_handlers_snapshot != NULL) {
372-
#define FREE_HANDLER(h) \
373-
if (!Z_ISUNDEF(worker_session_handlers_snapshot->h)) { \
374-
zval_ptr_dtor(&worker_session_handlers_snapshot->h); \
222+
#if PHP_VERSION_ID >= 80300
223+
if (PS(session_started_filename)) {
224+
zend_string_release(PS(session_started_filename));
225+
PS(session_started_filename) = NULL;
226+
PS(session_started_lineno) = 0;
375227
}
228+
#endif
376229

377-
FOR_EACH_SESSION_HANDLER(FREE_HANDLER);
378-
379-
#undef FREE_HANDLER
380-
381-
efree(worker_session_handlers_snapshot);
382-
worker_session_handlers_snapshot = NULL;
383-
}
230+
PS(session_status) = php_session_none;
231+
PS(in_save_handler) = 0;
232+
PS(set_handler) = 0;
233+
PS(mod_data) = NULL;
234+
PS(mod_user_is_open) = 0;
235+
PS(define_sid) = 1;
384236
}
237+
#endif
385238

386239
/* Adapted from php_request_shutdown */
387240
static void frankenphp_worker_request_shutdown() {
@@ -398,6 +251,10 @@ static void frankenphp_worker_request_shutdown() {
398251
}
399252
}
400253

254+
#ifdef HAVE_PHP_SESSION
255+
frankenphp_reset_session_state();
256+
#endif
257+
401258
/* Shutdown output layer (send the set HTTP headers, cleanup output handlers,
402259
* etc.) */
403260
zend_try { php_output_deactivate(); }
@@ -417,12 +274,6 @@ bool frankenphp_shutdown_dummy_request(void) {
417274
return false;
418275
}
419276

420-
/* Snapshot INI and session handlers BEFORE shutdown.
421-
* The framework has set these up before the worker loop, and we want
422-
* to preserve them. Session RSHUTDOWN will free the handlers. */
423-
frankenphp_snapshot_ini();
424-
frankenphp_snapshot_session_handlers();
425-
426277
frankenphp_worker_request_shutdown();
427278

428279
return true;
@@ -478,12 +329,6 @@ static int frankenphp_worker_request_startup() {
478329

479330
frankenphp_reset_super_globals();
480331

481-
/* Restore INI values changed during the previous request back to their
482-
* snapshot state (captured in frankenphp_shutdown_dummy_request).
483-
* This ensures framework settings persist while request-level changes
484-
* are reset. */
485-
frankenphp_restore_ini();
486-
487332
const char **module_name;
488333
zend_module_entry *module;
489334
for (module_name = MODULES_TO_RELOAD; *module_name; module_name++) {
@@ -493,12 +338,6 @@ static int frankenphp_worker_request_startup() {
493338
module->request_startup_func(module->type, module->module_number);
494339
}
495340
}
496-
497-
/* Restore session handlers AFTER session RINIT.
498-
* Session RSHUTDOWN frees mod_user_names callbacks, so we must restore
499-
* them before user code runs. This must happen after RINIT because
500-
* session RINIT may reset some state. */
501-
frankenphp_restore_session_handlers();
502341
}
503342
zend_catch { retval = FAILURE; }
504343
zend_end_try();
@@ -844,9 +683,6 @@ static zend_module_entry frankenphp_module = {
844683
STANDARD_MODULE_PROPERTIES};
845684

846685
static void frankenphp_request_shutdown() {
847-
if (is_worker_thread) {
848-
frankenphp_cleanup_worker_state();
849-
}
850686
php_request_shutdown((void *)0);
851687
frankenphp_free_request_context();
852688
}

0 commit comments

Comments
 (0)