Skip to content

Conversation

@psumbera
Copy link
Contributor

The SysV shared memory allocator in OPcache hardcodes a maximum segment size of 32MB (SEG_ALLOC_SIZE_MAX). With JIT enabled, OPcache reserves 64MB (ZEND_JIT_DEFAULT_BUFFER_SIZE) from the last segment, causing startup to fail with "Insufficient shared memory!".

This patch increases SEG_ALLOC_SIZE_MAX to 64MB so the reserved JIT buffer fits in a single segment. Behavior on other platforms using mmap remains unaffected.

Fixes #20718.

The SysV shared memory allocator in OPcache hardcodes a maximum segment
size of 32MB (SEG_ALLOC_SIZE_MAX). With JIT enabled, OPcache reserves
64MB (ZEND_JIT_DEFAULT_BUFFER_SIZE) from the last segment, causing
startup to fail with "Insufficient shared memory!".

This patch increases SEG_ALLOC_SIZE_MAX to 64MB so the reserved JIT buffer
fits in a single segment. Behavior on other platforms using mmap remains
unaffected.

Fixes php#20718.
@iluuu1994
Copy link
Member

Won't this issue just immediately re-appear when configuring a larger buffer size? E.g. add -d opcache.jit_buffer_size=128M and it's back.

@iluuu1994
Copy link
Member

PHP 8.3 has the same issue, it's just enabled in a different way (opcache.jit_buffer_size has to be set).

@iluuu1994
Copy link
Member

iluuu1994 commented Dec 16, 2025

So, I think the maximum segment size should be bumped way up, at least on 64-bit architectures. Alternatively, we can request a bigger segment size only when JIT is enabled (though I don't really see a benefit over just always allocating bigger chunks). Something like:

diff --git a/ext/opcache/shared_alloc_shm.c b/ext/opcache/shared_alloc_shm.c
index 09a357d189e..2f7c9ba4217 100644
--- a/ext/opcache/shared_alloc_shm.c
+++ b/ext/opcache/shared_alloc_shm.c
@@ -20,6 +20,9 @@
 */
 
 #include "zend_shared_alloc.h"
+#ifdef HAVE_JIT
+# include "jit/zend_jit.h"
+#endif
 
 #ifdef USE_SHM
 
@@ -50,6 +53,23 @@ typedef struct  {
     int shm_id;
 } zend_shared_segment_shm;
 
+#ifdef HAVE_JIT
+static size_t ceil_to_power_of_2(size_t n)
+{
+	ZEND_ASSERT(n);
+
+	size_t m = n;
+	uint32_t r = 0;
+	while (m >>= 1) {
+		r++;
+	}
+	if (n != (((size_t)1) << r)) {
+		r++;
+	}
+	return ((size_t)1) << r;
+}
+#endif
+
 static int create_segments(size_t requested_size, zend_shared_segment_shm ***shared_segments_p, int *shared_segments_count, const char **error_in)
 {
 	int i;
@@ -61,6 +81,12 @@ static int create_segments(size_t requested_size, zend_shared_segment_shm ***sha
 	zend_shared_segment_shm *shared_segments;
 
 	seg_allocate_size = SEG_ALLOC_SIZE_MAX;
+#ifdef HAVE_JIT
+	if (JIT_G(on) && JIT_G(buffer_size) > seg_allocate_size) {
+		/* The JIT buffer must be allocated in a contiguous chunk. */
+		seg_allocate_size = ceil_to_power_of_2(JIT_G(buffer_size));
+	}
+#endif
 	/* determine segment size we _really_ need:
 	 * no more than to include requested_size
 	 */

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants