Commit ee3035bc660 for php

commit ee3035bc66040d6fe400d1731b6a4b6b9b55e195
Author: Levi Morrison <levi.morrison@datadoghq.com>
Date:   Mon Sep 28 07:28:42 2026 -0600

    Fix persistent stream context lifetime during shutdown (#23853)

    * Add persistent stream context shutdown reproducer

    * Clear persistent stream request pointers after shutdown

diff --git a/NEWS b/NEWS
index 686eedc4645..4dedb89b0d3 100644
--- a/NEWS
+++ b/NEWS
@@ -115,6 +115,7 @@ PHP                                                                        NEWS
   . Fixed three Windows-only proc_open() defects: an uninitialized
     PROCESS_INFORMATION, an indeterminate comspec pointer after a failed
     lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky)
+  . Fix persistent stream context lifetime during shutdown (Levi Morrison)

 - XSL:
   . Fixed bug GH-23730 (use-after-free when XSLTProcessor::importStylesheet()
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index a1bf86d0bcf..ae1a1ef8666 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -157,7 +157,9 @@ zend_module_entry basic_functions_module = { /* {{{ */
 	PHP_RSHUTDOWN(basic),		/* request shutdown */
 	PHP_MINFO(basic),			/* extension info */
 	PHP_STANDARD_VERSION,		/* extension version */
-	STANDARD_MODULE_PROPERTIES
+	NO_MODULE_GLOBALS,
+	ZEND_MODULE_POST_ZEND_DEACTIVATE_N(streams),
+	STANDARD_MODULE_PROPERTIES_EX
 };
 /* }}} */

@@ -463,7 +465,6 @@ PHP_RSHUTDOWN_FUNCTION(basic) /* {{{ */
 #endif
 	BASIC_RSHUTDOWN_SUBMODULE(assert)
 	BASIC_RSHUTDOWN_SUBMODULE(url_scanner_ex)
-	BASIC_RSHUTDOWN_SUBMODULE(streams)
 #ifdef PHP_WIN32
 	BASIC_RSHUTDOWN_SUBMODULE(win32_core_globals)
 #endif
diff --git a/ext/standard/tests/streams/persistent_stream_context_shutdown.phpt b/ext/standard/tests/streams/persistent_stream_context_shutdown.phpt
new file mode 100644
index 00000000000..34a0b42a9f4
--- /dev/null
+++ b/ext/standard/tests/streams/persistent_stream_context_shutdown.phpt
@@ -0,0 +1,63 @@
+--TEST--
+Persistent stream contexts created during resource shutdown are detached
+--FILE--
+<?php
+
+if (($argv[1] ?? null) !== 'child') {
+    // Wait for the trigger's final status because the crash occurs after stdout closes.
+    $process = proc_open(
+        [PHP_BINARY, '-n', __FILE__, 'child'],
+        [
+            0 => ['pipe', 'r'],
+            1 => ['pipe', 'w'],
+            2 => ['pipe', 'w'],
+        ],
+        $pipes,
+    );
+    fclose($pipes[0]);
+    stream_get_contents($pipes[1]);
+    fclose($pipes[1]);
+    stream_get_contents($pipes[2]);
+    fclose($pipes[2]);
+    var_dump(proc_close($process));
+    return;
+}
+
+final class LateContextWrapper
+{
+    public $context;
+    private string $address;
+
+    public function stream_open($path, $mode, $options, &$opened_path): bool
+    {
+        $this->address = substr($path, strlen('late-context://'));
+        return true;
+    }
+
+    public function stream_close(): void
+    {
+        $context = stream_context_create([
+            'socket' => ['tcp_nodelay' => true],
+        ]);
+        stream_socket_client(
+            $this->address,
+            $errno,
+            $error,
+            1,
+            STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
+            $context,
+        );
+    }
+}
+
+$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $error);
+if (!$server) {
+    die("server failed: $error ($errno)\n");
+}
+
+stream_wrapper_register('late-context', LateContextWrapper::class);
+$trigger = fopen('late-context://tcp://' . stream_socket_get_name($server, false), 'r');
+
+?>
+--EXPECT--
+int(0)
diff --git a/main/php_streams.h b/main/php_streams.h
index 1c4141c939f..fc1585ed713 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -586,7 +586,7 @@ END_EXTERN_C()
 int php_init_stream_wrappers(int module_number);
 void php_shutdown_stream_wrappers(int module_number);
 void php_shutdown_stream_hashes(void);
-PHP_RSHUTDOWN_FUNCTION(streams);
+ZEND_MODULE_POST_ZEND_DEACTIVATE_D(streams);

 BEGIN_EXTERN_C()
 PHPAPI zend_result php_register_url_stream_wrapper(const char *protocol, const php_stream_wrapper *wrapper);
diff --git a/main/streams/streams.c b/main/streams/streams.c
index 368de1a6477..a37546a4ad2 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -79,17 +79,14 @@ static int forget_persistent_resource_id_numbers(zval *el)
 fprintf(stderr, "forget_persistent: %s:%p\n", stream->ops->label, stream);
 #endif

+	/* Request resources have been destroyed; clear their stale pointers. */
 	stream->res = NULL;
-
-	if (stream->ctx) {
-		zend_list_delete(stream->ctx);
-		stream->ctx = NULL;
-	}
+	stream->ctx = NULL;

 	return 0;
 }

-PHP_RSHUTDOWN_FUNCTION(streams)
+ZEND_MODULE_POST_ZEND_DEACTIVATE_D(streams)
 {
 	zval *el;