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
4648frankenphp_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
11682void 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 */
387240static 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
846685static 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