From 7fe37af0429df3ba94e838ed486f1781abe2b762 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 16 Oct 2023 21:26:20 +0200 Subject: [PATCH 001/101] Run tests on PHP 8.3 --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index a7d9e874..e6ca89e4 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -4,7 +4,7 @@ jobs: ubuntu: strategy: matrix: - version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2"] + version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3"] runs-on: ubuntu-latest steps: - name: Checkout apcu From 8468e85c5d1976233c4b8e78d0cd2e4f8027f485 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 16 Oct 2023 21:32:25 +0200 Subject: [PATCH 002/101] Update changelog --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index 358786c7..26f81d90 100644 --- a/package.xml +++ b/package.xml @@ -44,6 +44,9 @@ - Reduce memory usage when serializing packed arrays (e.g. lists) in PHP 8.2+ with `apc.serializer=default`. - Speed up serializing arrays with `apc.serializer=default`. - Reduce memory usage when unserializing instances of the empty array in PHP 7.3+. +- Removed no longer working apcue extension. +- Increased limit on maximum number of slots. +- Made tests compatible with PHP 8.3. From 1b725692b80b4856e8d705a7eda3ac416951aa1f Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 11 Nov 2023 18:37:59 +0100 Subject: [PATCH 003/101] Release apcu 5.1.23 --- package.xml | 4 ++-- php_apc.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.xml b/package.xml index 26f81d90..2fe324b4 100644 --- a/package.xml +++ b/package.xml @@ -28,9 +28,9 @@ nikic@php.net yes - 2022-09-19 + 2023-11-11 - 5.1.23dev + 5.1.23 5.1.18 diff --git a/php_apc.h b/php_apc.h index 4456f3f9..97c5c05e 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.23-dev" +#define PHP_APCU_VERSION "5.1.23" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From 98ab20d63f664aa65d78817c525a0efdcb499813 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 11 Nov 2023 18:47:27 +0100 Subject: [PATCH 004/101] Back to dev --- package.xml | 33 ++++++++++++++++++++++++--------- php_apc.h | 2 +- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/package.xml b/package.xml index 2fe324b4..25433c18 100644 --- a/package.xml +++ b/package.xml @@ -30,7 +30,7 @@ 2023-11-11 - 5.1.23 + 5.1.24dev 5.1.18 @@ -39,14 +39,7 @@ PHP License -- Revert use of monotonic clock for TTL (#451) -- Fix a crash when serializing packed arrays (e.g. lists) in PHP 8.2+ with `apc.serializer=default`. -- Reduce memory usage when serializing packed arrays (e.g. lists) in PHP 8.2+ with `apc.serializer=default`. -- Speed up serializing arrays with `apc.serializer=default`. -- Reduce memory usage when unserializing instances of the empty array in PHP 7.3+. -- Removed no longer working apcue extension. -- Increased limit on maximum number of slots. -- Made tests compatible with PHP 8.3. +- TBD @@ -192,6 +185,28 @@ + + 2023-11-11 + + 5.1.23 + 5.1.18 + + + stable + stable + + PHP License + +- Revert use of monotonic clock for TTL (#451) +- Fix a crash when serializing packed arrays (e.g. lists) in PHP 8.2+ with `apc.serializer=default`. +- Reduce memory usage when serializing packed arrays (e.g. lists) in PHP 8.2+ with `apc.serializer=default`. +- Speed up serializing arrays with `apc.serializer=default`. +- Reduce memory usage when unserializing instances of the empty array in PHP 7.3+. +- Removed no longer working apcue extension. +- Increased limit on maximum number of slots. +- Made tests compatible with PHP 8.3. + + 2022-09-19 diff --git a/php_apc.h b/php_apc.h index 97c5c05e..4456f3f9 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.23" +#define PHP_APCU_VERSION "5.1.23-dev" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From d9b2a5a6022f27925db46c2e7cc3d963d0fee4c7 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 29 Nov 2023 22:27:39 +0100 Subject: [PATCH 005/101] Build on PHP 8.3 on Windows Now that setup-php-sdk has been moved to the php org and is maintained again. --- .github/workflows/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index e6ca89e4..c577c62f 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -31,7 +31,7 @@ jobs: shell: cmd strategy: matrix: - version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2"] + version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3"] arch: [x64] ts: [nts, ts] runs-on: windows-latest @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@v2 - name: Setup PHP id: setup-php - uses: cmb69/setup-php-sdk@v0.6 + uses: php/setup-php-sdk@v0.8 with: version: ${{matrix.version}} arch: ${{matrix.arch}} From acc48ed6eff19f56877a2a8c50c38b2a17890fe1 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 29 Nov 2023 23:07:16 +0100 Subject: [PATCH 006/101] Use actions/checkout@v4 --- .github/workflows/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index c577c62f..7de5c42b 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout apcu - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: @@ -37,7 +37,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout apcu - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP id: setup-php uses: php/setup-php-sdk@v0.8 From 02b0ba72f29bf004e1b7f18ea46c75856c2da33e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 9 Dec 2023 20:55:22 +0100 Subject: [PATCH 007/101] Fix format specifiers in apc_cache.c Fixes #501. --- apc_cache.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 2ea5b521..095f541a 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -212,8 +212,8 @@ static void apc_cache_wlocked_gc(apc_cache_t* cache) /* good ol' whining */ if (dead->ref_count > 0) { apc_debug( - "GC cache entry '%s' was on gc-list for %ld seconds", - ZSTR_VAL(dead->key), gc_sec + "GC cache entry '%s' was on gc-list for %lld seconds", + ZSTR_VAL(dead->key), (long long) gc_sec ); } @@ -275,7 +275,7 @@ PHP_APCU_API int APC_UNSERIALIZER_NAME(php) (APC_UNSERIALIZER_ARGS) BG(serialize_lock)--; if (!result) { - php_error_docref(NULL, E_NOTICE, "Error at offset %ld of %ld bytes", (zend_long)(tmp - buf), (zend_long)buf_len); + php_error_docref(NULL, E_NOTICE, "Error at offset %td of %zd bytes", tmp - buf, buf_len); ZVAL_NULL(value); return 0; } From 0797ccc2c63717958b6e927c6ed54c9a9c5f3ab5 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 9 Dec 2023 20:58:58 +0100 Subject: [PATCH 008/101] Fix format specifier in apc_shm.c Fixes #500. --- apc_shm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apc_shm.c b/apc_shm.c index 3f312810..132c9aaf 100644 --- a/apc_shm.c +++ b/apc_shm.c @@ -53,7 +53,7 @@ int apc_shm_create(int proj, size_t size) oflag = IPC_CREAT | SHM_R | SHM_A; if ((shmid = shmget(key, size, oflag)) < 0) { - zend_error_noreturn(E_CORE_ERROR, "apc_shm_create: shmget(%d, %zd, %d) failed: %s. It is possible that the chosen SHM segment size is higher than the operation system allows. Linux has usually a default limit of 32MB per segment.", key, size, oflag, strerror(errno)); + zend_error_noreturn(E_CORE_ERROR, "apc_shm_create: shmget(%ld, %zd, %d) failed: %s. It is possible that the chosen SHM segment size is higher than the operation system allows. Linux has usually a default limit of 32MB per segment.", (long) key, size, oflag, strerror(errno)); } return shmid; From 7cfb405cb5bec493867ac87b6055c98cd5b7feeb Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 9 Dec 2023 22:50:30 +0100 Subject: [PATCH 009/101] Preserve search parameter when deleting entry Closes #437. --- apc.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apc.php b/apc.php index af6b8515..99ba65d9 100644 --- a/apc.php +++ b/apc.php @@ -1054,7 +1054,10 @@ function block_sort($array1, $array2) echo '', date(DATE_FORMAT,$entry['deletion_time']), ''; } else if ($MYREQUEST['OB'] == OB_USER_CACHE) { echo ''; - echo '[Delete Now]'; + echo '[Delete Now]'; echo ''; } else { echo '   '; From 1ba5a2dade72d44ded54ebcb56b5392e2960c436 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 9 Dec 2023 22:59:51 +0100 Subject: [PATCH 010/101] Allow arbitrary search regex Closes #492. --- apc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apc.php b/apc.php index 99ba65d9..dddb4544 100644 --- a/apc.php +++ b/apc.php @@ -87,7 +87,7 @@ function defaults($d,$v) { 'SORT1' => '/^[AHSMCDTZ]$/', // first sort key 'SORT2' => '/^[DA]$/', // second sort key 'AGGR' => '/^\d+$/', // aggregation by dir level - 'SEARCH' => '~^[a-zA-Z0-9/_.-]*$~' // aggregation by dir level + 'SEARCH' => '/^.*$/' // search regex ); // cache scope @@ -961,7 +961,7 @@ function block_sort($array1, $array2) '', '', '', - '  Search: ', + '  Search: ', ' ', ''; From 9dad016db50cc46321afec592ea9b49520c1cf13 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 13 Jul 2024 09:59:12 +0200 Subject: [PATCH 011/101] Make apc_entry_002.phpt compatible with PHP 8.4 Fixes #510. --- tests/apc_entry_002.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/apc_entry_002.phpt b/tests/apc_entry_002.phpt index 61f833a8..baf9aafb 100644 --- a/tests/apc_entry_002.phpt +++ b/tests/apc_entry_002.phpt @@ -14,7 +14,7 @@ $value = apcu_entry("test", function($key){ --EXPECTF-- Fatal error: Uncaught Exception: test in %s:3 Stack trace: -#0 [internal function]: {closure}('test') +#0 [internal function]: {closure%S}('test') #1 %s(%d): apcu_entry('test', Object(Closure)) #2 {main} thrown in %s on line 3 From 5afe2eedb11a8e84e0069bd073978bf59a534cb5 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 13 Jul 2024 10:14:40 +0200 Subject: [PATCH 012/101] Try to fix windows ci --- .github/workflows/config.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 7de5c42b..67537875 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -31,10 +31,19 @@ jobs: shell: cmd strategy: matrix: - version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3"] - arch: [x64] - ts: [nts, ts] - runs-on: windows-latest + version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3"] + arch: [x64] + ts: [nts, ts] + os: [windows-2019, windows-2022] + exclude: + - { os: windows-2019, php: "8.3" } + - { os: windows-2019, php: "8.2" } + - { os: windows-2019, php: "8.1" } + - { os: windows-2019, php: "8.0" } + - { os: windows-2022, php: "7.4" } + - { os: windows-2022, php: "7.3" } + - { os: windows-2022, php: "7.2" } + runs-on: ${{matrix.os}} steps: - name: Checkout apcu uses: actions/checkout@v4 From 1d80077901073bf8f6f31e70125d5f1dd61643c2 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 13 Jul 2024 10:16:08 +0200 Subject: [PATCH 013/101] Fix matrix --- .github/workflows/config.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 67537875..0f46b055 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -36,13 +36,13 @@ jobs: ts: [nts, ts] os: [windows-2019, windows-2022] exclude: - - { os: windows-2019, php: "8.3" } - - { os: windows-2019, php: "8.2" } - - { os: windows-2019, php: "8.1" } - - { os: windows-2019, php: "8.0" } - - { os: windows-2022, php: "7.4" } - - { os: windows-2022, php: "7.3" } - - { os: windows-2022, php: "7.2" } + - { os: windows-2019, version: "8.3" } + - { os: windows-2019, version: "8.2" } + - { os: windows-2019, version: "8.1" } + - { os: windows-2019, version: "8.0" } + - { os: windows-2022, version: "7.4" } + - { os: windows-2022, version: "7.3" } + - { os: windows-2022, version: "7.2" } runs-on: ${{matrix.os}} steps: - name: Checkout apcu From 2b0e77929196553cce926f93c5c5cf61f46022d2 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 13 Jul 2024 10:21:53 +0200 Subject: [PATCH 014/101] Add PHP 8.4 to Linux CI matrix --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 0f46b055..5d90c3e9 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -4,7 +4,7 @@ jobs: ubuntu: strategy: matrix: - version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3"] + version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] runs-on: ubuntu-latest steps: - name: Checkout apcu From 15766e615264620427c2db37061ca9614d3b7319 Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Sat, 10 Aug 2024 22:59:31 +0200 Subject: [PATCH 015/101] tests: Fix PHP ZTS detection PHP 8.4 changed the PHP_ZTS constant to bool: https://www.github.com/php/php-src/pull/13079 As a result, the tests that fail with ZTS would no longer be skipped. --- tests/apc54_014.phpt | 2 +- tests/ghbug176.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/apc54_014.phpt b/tests/apc54_014.phpt index e3591c39..fcfb92b6 100644 --- a/tests/apc54_014.phpt +++ b/tests/apc54_014.phpt @@ -3,7 +3,7 @@ APC: Bug #61742 preload_path does not work due to incorrect string length (varia --SKIPIF-- diff --git a/tests/ghbug176.phpt b/tests/ghbug176.phpt index 8b1dd05d..23649cf6 100644 --- a/tests/ghbug176.phpt +++ b/tests/ghbug176.phpt @@ -3,7 +3,7 @@ APC: GH Bug #176 preload_path segfaults with bad data --SKIPIF-- From 103bb842f4dc5e9a52137277ddfb12937b30c3cd Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Sat, 31 Aug 2024 19:12:49 +0200 Subject: [PATCH 016/101] Autotools: Replace APCU_CFLAGS Makefile variable with shell variable This replaces the "hacky" Makefile variable usage in config.m4 with a regular shell variable. Issue initially noted at https://github.com/php/php-src/pull/15642 --- config.m4 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config.m4 b/config.m4 index a6e5c991..2b8782a7 100644 --- a/config.m4 +++ b/config.m4 @@ -246,9 +246,8 @@ if test "$PHP_APCU" != "no"; then apc_persist.c" PHP_CHECK_LIBRARY(rt, shm_open, [PHP_ADD_LIBRARY(rt,,APCU_SHARED_LIBADD)]) - PHP_NEW_EXTENSION(apcu, $apc_sources, $ext_shared,, \\$(APCU_CFLAGS)) + PHP_NEW_EXTENSION([apcu], [$apc_sources], [$ext_shared],, [$APCU_CFLAGS]) PHP_SUBST(APCU_SHARED_LIBADD) - PHP_SUBST(APCU_CFLAGS) PHP_SUBST(PHP_LDFLAGS) PHP_INSTALL_HEADERS(ext/apcu, [php_apc.h apc.h apc_api.h apc_cache.h apc_globals.h apc_iterator.h apc_lock.h apc_mutex.h apc_sma.h apc_serializer.h apc_stack.h apc_arginfo.h php_apc_legacy_arginfo.h]) AC_DEFINE(HAVE_APCU, 1, [ ]) From 3b1f4661c54922c318ff3b8eddef0402340b372c Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Sun, 1 Sep 2024 00:50:23 +0200 Subject: [PATCH 017/101] Autotools: Sort configure options help texts with AS_HELP_STRING --- config.m4 | 60 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/config.m4 b/config.m4 index 2b8782a7..62f4ec4f 100644 --- a/config.m4 +++ b/config.m4 @@ -1,9 +1,12 @@ -PHP_ARG_ENABLE(apcu, whether to enable APCu support, -[ --enable-apcu Enable APCu support]) +PHP_ARG_ENABLE([apcu], + [whether to enable APCu support], + [AS_HELP_STRING([--enable-apcu], + [Enable APCu support])]) AC_MSG_CHECKING(if APCu should be allowed to use rwlocks) -AC_ARG_ENABLE(apcu-rwlocks, -[ --disable-apcu-rwlocks Disable rwlocks in APCu], +AC_ARG_ENABLE([apcu-rwlocks], + [AS_HELP_STRING([--disable-apcu-rwlocks], + [Disable rwlocks in APCu])], [ PHP_APCU_RWLOCKS=$enableval AC_MSG_RESULT($enableval) @@ -14,19 +17,17 @@ AC_ARG_ENABLE(apcu-rwlocks, ]) AC_MSG_CHECKING(if APCu should be built in debug mode) -AC_ARG_ENABLE(apcu-debug, -[ --enable-apcu-debug Enable APCu debugging], -[ - PHP_APCU_DEBUG=$enableval -], -[ - PHP_APCU_DEBUG=no -]) +AC_ARG_ENABLE([apcu-debug], + [AS_HELP_STRING([--enable-apcu-debug], + [Enable APCu debugging])], + [PHP_APCU_DEBUG=$enableval], + [PHP_APCU_DEBUG=no]) AC_MSG_RESULT($PHP_APCU_DEBUG) AC_MSG_CHECKING(if APCu should clear on SIGUSR1) -AC_ARG_ENABLE(apcu-clear-signal, -[ --enable-apcu-clear-signal Enable SIGUSR1 clearing handler], +AC_ARG_ENABLE([apcu-clear-signal], + [AS_HELP_STRING([--enable-apcu-clear-signal], + [Enable SIGUSR1 clearing handler])], [ AC_DEFINE(APC_CLEAR_SIGNAL, 1, [ ]) AC_MSG_RESULT(yes) @@ -37,8 +38,9 @@ AC_ARG_ENABLE(apcu-clear-signal, PHP_APCU_MMAP=yes AC_MSG_CHECKING([if APCu will use mmap (or shm)]) -AC_ARG_ENABLE(apcu-mmap, -[ --disable-apcu-mmap Disable mmap, falls back on shm], +AC_ARG_ENABLE([apcu-mmap], + [AS_HELP_STRING([--disable-apcu-mmap], + [Disable mmap, falls back on shm])], [ if test "x$enableval" = "xno"; then PHP_APCU_MMAP=no @@ -50,8 +52,9 @@ AC_MSG_RESULT($PHP_APCU_MMAP) PHP_APCU_SPINLOCK=no AC_MSG_CHECKING(if APCu should utilize spinlocks before flocks) -AC_ARG_ENABLE(apcu-spinlocks, -[ --enable-apcu-spinlocks Use spinlocks before flocks], +AC_ARG_ENABLE([apcu-spinlocks], + [AS_HELP_STRING([--enable-apcu-spinlocks], + [Use spinlocks before flocks])], [ if test "x$enableval" = "xno"; then PHP_APCU_SPINLOCK=no else @@ -219,16 +222,15 @@ if test "$PHP_APCU" != "no"; then AC_DEFINE(HAVE_SEMUN, 0, [ ]) fi - AC_ARG_ENABLE(valgrind-checks, - [ --disable-valgrind-checks - Disable valgrind based memory checks], - [ - PHP_APCU_VALGRIND=no - ], [ + AC_ARG_ENABLE([valgrind-checks], + [AS_HELP_STRING([--disable-valgrind-checks], + [Disable Valgrind based memory checks])], + [PHP_APCU_VALGRIND=no], + [ PHP_APCU_VALGRIND=yes AC_CHECK_HEADER(valgrind/memcheck.h, [AC_DEFINE([HAVE_VALGRIND_MEMCHECK_H],1, [enable valgrind memchecks])]) - ]) + ]) for i in -Wall -Wextra -Wno-unused-parameter; do AX_CHECK_COMPILE_FLAG([$i], [APCU_CFLAGS="$APCU_CFLAGS $i"]) @@ -253,8 +255,12 @@ if test "$PHP_APCU" != "no"; then AC_DEFINE(HAVE_APCU, 1, [ ]) fi -PHP_ARG_ENABLE(coverage, whether to include code coverage symbols, -[ --enable-coverage DEVELOPERS ONLY!!], no, no) +PHP_ARG_ENABLE([coverage], + [whether to include code coverage symbols], + [AS_HELP_STRING([--enable-coverage], + [Include code coverage symbols (DEVELOPERS ONLY!!)])], + [no], + [no]) if test "$PHP_COVERAGE" = "yes"; then From 2b299d6ee7faddbfa288327178e5642ecb1b3648 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Sun, 1 Sep 2024 22:36:04 +0200 Subject: [PATCH 018/101] Autotools: Refactor --enable-apcu-rwlocks option to PHP_ARG_ENABLE - Instead of AC_ARG_ENABLE the PHP_ARG_ENABLE works in similar manner and outputs the 2nd argument as the check message. Result variable is the same (PHP_APCU_RWLOCKS). By default the option is enabled like before. The 4th argument sets whether the option is enabled, the 5th argument indicates that this is not direct extension related configuration option (build system internal handling stuff). - AC_CACHE_CHECK check refactored: Entire check moved inside the main "extension-enabled" if block, AS_VAR_IF used, redundant double Autoconf quotes reduced. - AC_MSG_FAILURE works the same as AC_MSG_ERROR except that it also outputs the "See config.log for details" message at the end in case of failure, directing the user where to check for possible failure reason. --- config.m4 | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/config.m4 b/config.m4 index 62f4ec4f..28595f22 100644 --- a/config.m4 +++ b/config.m4 @@ -3,18 +3,12 @@ PHP_ARG_ENABLE([apcu], [AS_HELP_STRING([--enable-apcu], [Enable APCu support])]) -AC_MSG_CHECKING(if APCu should be allowed to use rwlocks) -AC_ARG_ENABLE([apcu-rwlocks], +PHP_ARG_ENABLE([apcu-rwlocks], + [if APCu should be allowed to use rwlocks], [AS_HELP_STRING([--disable-apcu-rwlocks], [Disable rwlocks in APCu])], -[ - PHP_APCU_RWLOCKS=$enableval - AC_MSG_RESULT($enableval) -], -[ - PHP_APCU_RWLOCKS=yes - AC_MSG_RESULT(yes) -]) + [yes], + [no]) AC_MSG_CHECKING(if APCu should be built in debug mode) AC_ARG_ENABLE([apcu-debug], @@ -63,23 +57,20 @@ AC_ARG_ENABLE([apcu-spinlocks], ]) AC_MSG_RESULT($PHP_APCU_SPINLOCK) -if test "$PHP_APCU_RWLOCKS" != "no"; then - AC_CACHE_CHECK([whether the target compiler supports builtin atomics], PHP_cv_APCU_GCC_ATOMICS, [ - - AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[ +if test "$PHP_APCU" != "no"; then + AS_VAR_IF([PHP_APCU_RWLOCKS], [no], [], [ + AC_CACHE_CHECK([whether the target compiler supports builtin atomics], + [PHP_cv_APCU_GCC_ATOMICS], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([], [ int foo = 0; __sync_add_and_fetch(&foo, 1); __sync_sub_and_fetch(&foo, 1); return 0; - ]])],[PHP_cv_APCU_GCC_ATOMICS=yes],[PHP_cv_APCU_GCC_ATOMICS=no]) + ])], [PHP_cv_APCU_GCC_ATOMICS=yes], [PHP_cv_APCU_GCC_ATOMICS=no])]) + AS_VAR_IF([PHP_cv_APCU_GCC_ATOMICS], [no], + [AC_MSG_FAILURE([Compiler does not support atomics])]) ]) - if test "x${PHP_cv_APCU_GCC_ATOMICS}" != "xyes"; then - AC_MSG_ERROR([Compiler does not support atomics]) - fi -fi - -if test "$PHP_APCU" != "no"; then if test "$PHP_APCU_DEBUG" != "no"; then AC_DEFINE(APC_DEBUG, 1, [ ]) fi From 0cc6471af8878a16c8432c3c8372de95662f3cd2 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Sun, 1 Sep 2024 22:55:07 +0200 Subject: [PATCH 019/101] Autotools: Remove union semun check The HAVE_SEMUN usage was removed via fcf43c91206c354d2db4e171e9f7fa471512cb19 and the check is redundant at this point. --- config.m4 | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/config.m4 b/config.m4 index 28595f22..eb270f87 100644 --- a/config.m4 +++ b/config.m4 @@ -195,23 +195,6 @@ if test "$PHP_APCU" != "no"; then fi AC_CHECK_FUNCS(sigaction) - AC_CACHE_CHECK(for union semun, php_cv_semun, - [ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#include -#include -#include - ]], [[union semun x; x.val=1]])],[ - php_cv_semun=yes - ],[ - php_cv_semun=no - ]) - ]) - if test "$php_cv_semun" = "yes"; then - AC_DEFINE(HAVE_SEMUN, 1, [ ]) - else - AC_DEFINE(HAVE_SEMUN, 0, [ ]) - fi AC_ARG_ENABLE([valgrind-checks], [AS_HELP_STRING([--disable-valgrind-checks], From 4c4326801c502fbd41faea40e188fd0dcea9a909 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Sun, 1 Sep 2024 23:36:33 +0200 Subject: [PATCH 020/101] Autotools: Refactor --enable-apcu-spinlocks - This uses the PHP_ARG_ENABLE instead of core AC_ARG_ENABLE to have check message automatically output and result variable PHP_APCU_SPINLOCKS set. - AS_VAR_IF used - AC_DEFINE arguments quoted and some simplistic help message text added. --- config.m4 | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/config.m4 b/config.m4 index eb270f87..c14402c9 100644 --- a/config.m4 +++ b/config.m4 @@ -44,18 +44,12 @@ AC_ARG_ENABLE([apcu-mmap], ]) AC_MSG_RESULT($PHP_APCU_MMAP) -PHP_APCU_SPINLOCK=no -AC_MSG_CHECKING(if APCu should utilize spinlocks before flocks) -AC_ARG_ENABLE([apcu-spinlocks], +PHP_ARG_ENABLE([apcu-spinlocks], + [if APCu should utilize spinlocks before flocks], [AS_HELP_STRING([--enable-apcu-spinlocks], [Use spinlocks before flocks])], -[ if test "x$enableval" = "xno"; then - PHP_APCU_SPINLOCK=no - else - PHP_APCU_SPINLOCK=yes - fi -]) -AC_MSG_RESULT($PHP_APCU_SPINLOCK) + [no], + [no]) if test "$PHP_APCU" != "no"; then AS_VAR_IF([PHP_APCU_RWLOCKS], [no], [], [ @@ -184,13 +178,15 @@ if test "$PHP_APCU" != "no"; then if test "$PHP_APCU_RWLOCKS" = "no"; then if test "$PHP_APCU_MUTEX" = "no"; then - if test "$PHP_APCU_SPINLOCK" != "no"; then - AC_DEFINE(APC_SPIN_LOCK, 1, [ ]) - AC_MSG_WARN([APCu spin locking enabled]) - else - AC_DEFINE(APC_FCNTL_LOCK, 1, [ ]) - AC_MSG_WARN([APCu file locking enabled]) - fi + AS_VAR_IF([PHP_APCU_SPINLOCKS], [no], [ + AC_DEFINE([APC_FCNTL_LOCK], [1], + [Define to 1 if APCu file locking is enabled.]) + AC_MSG_WARN([APCu file locking enabled]) + ], [ + AC_DEFINE([APC_SPIN_LOCK], [1], + [Define to 1 if APCu spin locking is enabled.]) + AC_MSG_WARN([APCu spin locking enabled]) + ]) fi fi From cf43c190ad23f868bdc7eb5ce7ddd058d536fce7 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Tue, 3 Sep 2024 00:41:18 +0200 Subject: [PATCH 021/101] Fix -Wundef warnings - ZTS - PHP_WIN32 - HAVE_SIGACTION are either undefined or defined to 1. This fixes possible -Wundef (Windows C4668 warning code) warnings if such compiler configuration is set. --- apc_cache.c | 4 ++-- apc_signal.c | 2 +- apc_sma.c | 2 +- php_apc.c | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 095f541a..baa19ce0 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -273,7 +273,7 @@ PHP_APCU_API int APC_UNSERIALIZER_NAME(php) (APC_UNSERIALIZER_ARGS) result = php_var_unserialize(value, &tmp, buf + buf_len, &var_hash); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); BG(serialize_lock)--; - + if (!result) { php_error_docref(NULL, E_NOTICE, "Error at offset %td of %zd bytes", tmp - buf, buf_len); ZVAL_NULL(value); @@ -1210,7 +1210,7 @@ PHP_APCU_API zend_bool apc_cache_defense(apc_cache_t *cache, zend_string *key, t last->len == ZSTR_LEN(key) && last->mtime == t && (last->owner_pid != owner_pid -#if ZTS +#ifdef ZTS || last->owner_thread != owner_thread #endif ) diff --git a/apc_signal.c b/apc_signal.c index 8bf58147..bb47f77f 100644 --- a/apc_signal.c +++ b/apc_signal.c @@ -34,7 +34,7 @@ #include "apc.h" -#if HAVE_SIGACTION +#ifdef HAVE_SIGACTION #include #include "apc_globals.h" #include "apc_sma.h" diff --git a/apc_sma.c b/apc_sma.c index 51a3198d..751c325c 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -331,7 +331,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex #else { int j = apc_shm_create(i, sma->size); -#if PHP_WIN32 +#ifdef PHP_WIN32 /* TODO remove the line below after 7.1 EOL. */ SetLastError(0); #endif diff --git a/php_apc.c b/php_apc.c index 081b62a5..6193d89a 100644 --- a/php_apc.c +++ b/php_apc.c @@ -58,7 +58,7 @@ #include "SAPI.h" #include "php_apc.h" -#if HAVE_SIGACTION +#ifdef HAVE_SIGACTION #include "apc_signal.h" #endif @@ -300,7 +300,7 @@ static PHP_MSHUTDOWN_FUNCTION(apcu) APCG(initialized) = 0; } -#if HAVE_SIGACTION +#ifdef HAVE_SIGACTION apc_shutdown_signals(); #endif } @@ -325,7 +325,7 @@ static PHP_RINIT_FUNCTION(apcu) apc_cache_serializer(apc_user_cache, APCG(serializer_name)); } -#if HAVE_SIGACTION +#ifdef HAVE_SIGACTION apc_set_signals(); #endif } From f7fe4cc67d85c6004a6bc595182a3ff5183bdd62 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Tue, 3 Sep 2024 00:09:44 +0200 Subject: [PATCH 022/101] Autotools: Sync configure options - AC_ARG_ENABLE replaced with PHP_ARG_ENABLE - checks sorted - AS_VAR_IF macros used - For checking the Valgrind header, the AC_CHECK_HEADERS is used which defines the HAVE_VALGRIND_MEMCHECK_H preprocessor macro by default - The APC_MMAP preprocessor macro is either undefined or defined to 1 so the `#if` checks are also replaced with `#ifdef` to not cause possible -Wundef warnings emitted if such compiler configuration is set --- apc_cache.c | 2 +- apc_globals.h | 2 +- apc_mmap.c | 2 +- apc_mmap.h | 2 +- apc_sma.c | 6 ++-- config.m4 | 94 +++++++++++++++++++++++---------------------------- php_apc.c | 8 ++--- 7 files changed, 54 insertions(+), 62 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index baa19ce0..142f0d4f 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -1100,7 +1100,7 @@ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool add_assoc_long(info, "start_time", cache->header->stime); array_add_double(info, apc_str_mem_size, (double) cache->header->mem_size); -#if APC_MMAP +#ifdef APC_MMAP add_assoc_stringl(info, "memory_type", "mmap", sizeof("mmap")-1); #else add_assoc_stringl(info, "memory_type", "IPC shared", sizeof("IPC shared")-1); diff --git a/apc_globals.h b/apc_globals.h index c0a4436d..8ad84a30 100644 --- a/apc_globals.h +++ b/apc_globals.h @@ -44,7 +44,7 @@ ZEND_BEGIN_MODULE_GLOBALS(apcu) zend_long ttl; /* parameter to apc_cache_create */ zend_long smart; /* smart value */ -#if APC_MMAP +#ifdef APC_MMAP char *mmap_file_mask; /* mktemp-style file-mask to pass to mmap */ #endif diff --git a/apc_mmap.c b/apc_mmap.c index 9337d452..faabf93f 100644 --- a/apc_mmap.c +++ b/apc_mmap.c @@ -29,7 +29,7 @@ #include "apc_mmap.h" #include "apc_lock.h" -#if APC_MMAP +#ifdef APC_MMAP #include #include diff --git a/apc_mmap.h b/apc_mmap.h index 16ababcf..30eec9f2 100644 --- a/apc_mmap.h +++ b/apc_mmap.h @@ -35,7 +35,7 @@ /* Wrapper functions for shared memory mapped files */ -#if APC_MMAP +#ifdef APC_MMAP apc_segment_t apc_mmap(char *file_mask, size_t size); void apc_unmap(apc_segment_t* segment); #endif diff --git a/apc_sma.c b/apc_sma.c index 751c325c..b0bbbc86 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -299,7 +299,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex sma->expunge = expunge; sma->data = data; -#if APC_MMAP +#ifdef APC_MMAP /* * I don't think multiple anonymous mmaps makes any sense * so force sma_numseg to 1 in this case @@ -324,7 +324,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex block_t *first, *empty, *last; void* shmaddr; -#if APC_MMAP +#ifdef APC_MMAP sma->segs[i] = apc_mmap(mask, sma->size); if(sma->num != 1) memcpy(&mask[strlen(mask)-6], "XXXXXX", 6); @@ -388,7 +388,7 @@ PHP_APCU_API void apc_sma_detach(apc_sma_t* sma) { sma->initialized = 0; for (i = 0; i < sma->num; i++) { -#if APC_MMAP +#ifdef APC_MMAP apc_unmap(&sma->segs[i]); #else apc_shm_detach(&sma->segs[i]); diff --git a/config.m4 b/config.m4 index c14402c9..47edf366 100644 --- a/config.m4 +++ b/config.m4 @@ -10,39 +10,26 @@ PHP_ARG_ENABLE([apcu-rwlocks], [yes], [no]) -AC_MSG_CHECKING(if APCu should be built in debug mode) -AC_ARG_ENABLE([apcu-debug], +PHP_ARG_ENABLE([apcu-debug], + [if APCu should be built in debug mode], [AS_HELP_STRING([--enable-apcu-debug], [Enable APCu debugging])], - [PHP_APCU_DEBUG=$enableval], - [PHP_APCU_DEBUG=no]) -AC_MSG_RESULT($PHP_APCU_DEBUG) + [no], + [no]) -AC_MSG_CHECKING(if APCu should clear on SIGUSR1) -AC_ARG_ENABLE([apcu-clear-signal], +PHP_ARG_ENABLE([apcu-clear-signal], + [if APCu should clear on SIGUSR1], [AS_HELP_STRING([--enable-apcu-clear-signal], [Enable SIGUSR1 clearing handler])], -[ - AC_DEFINE(APC_CLEAR_SIGNAL, 1, [ ]) - AC_MSG_RESULT(yes) -], -[ - AC_MSG_RESULT(no) -]) - -PHP_APCU_MMAP=yes -AC_MSG_CHECKING([if APCu will use mmap (or shm)]) -AC_ARG_ENABLE([apcu-mmap], + [no], + [no]) + +PHP_ARG_ENABLE([apcu-mmap], + [if APCu should use mmap instead of shm], [AS_HELP_STRING([--disable-apcu-mmap], [Disable mmap, falls back on shm])], -[ - if test "x$enableval" = "xno"; then - PHP_APCU_MMAP=no - else - PHP_APCU_MMAP=yes - fi -]) -AC_MSG_RESULT($PHP_APCU_MMAP) + [yes], + [no]) PHP_ARG_ENABLE([apcu-spinlocks], [if APCu should utilize spinlocks before flocks], @@ -51,7 +38,37 @@ PHP_ARG_ENABLE([apcu-spinlocks], [no], [no]) +PHP_ARG_ENABLE([valgrind-checks], + [whether to enable Valgrind-based memory checks], + [AS_HELP_STRING([--disable-valgrind-checks], + [Disable Valgrind-based memory checks])], + [yes], + [no]) + +PHP_ARG_ENABLE([coverage], + [whether to include code coverage symbols], + [AS_HELP_STRING([--enable-coverage], + [Include code coverage symbols (DEVELOPERS ONLY!!)])], + [no], + [no]) + if test "$PHP_APCU" != "no"; then + AS_VAR_IF([PHP_APCU_DEBUG], [no], [], + [AC_DEFINE([APC_DEBUG], [1], + [Define to 1 if APCu debugging mode is enabled.])]) + + AS_VAR_IF([PHP_APCU_CLEAR_SIGNAL], [no], [], + [AC_DEFINE([APC_CLEAR_SIGNAL], [1], + [Define to 1 if SIGUSR1 clearing handler is enabled.])]) + + AS_VAR_IF([PHP_APCU_MMAP], [no], [], + [AC_DEFINE([APC_MMAP], [1], + [Define to 1 if APCu uses mmap instead of shm.])]) + + AS_VAR_IF([PHP_VALGRIND_CHECKS], [no], [], + [AC_CHECK_HEADERS([valgrind/memcheck.h], [], + [AC_MSG_NOTICE([Valgrind-based memory checks are disabled.])])]) + AS_VAR_IF([PHP_APCU_RWLOCKS], [no], [], [ AC_CACHE_CHECK([whether the target compiler supports builtin atomics], [PHP_cv_APCU_GCC_ATOMICS], @@ -65,14 +82,6 @@ if test "$PHP_APCU" != "no"; then [AC_MSG_FAILURE([Compiler does not support atomics])]) ]) - if test "$PHP_APCU_DEBUG" != "no"; then - AC_DEFINE(APC_DEBUG, 1, [ ]) - fi - - if test "$PHP_APCU_MMAP" != "no"; then - AC_DEFINE(APC_MMAP, 1, [ ]) - fi - if test "$PHP_APCU_RWLOCKS" != "no"; then orig_LIBS="$LIBS" LIBS="$LIBS -lpthread" @@ -192,16 +201,6 @@ if test "$PHP_APCU" != "no"; then AC_CHECK_FUNCS(sigaction) - AC_ARG_ENABLE([valgrind-checks], - [AS_HELP_STRING([--disable-valgrind-checks], - [Disable Valgrind based memory checks])], - [PHP_APCU_VALGRIND=no], - [ - PHP_APCU_VALGRIND=yes - AC_CHECK_HEADER(valgrind/memcheck.h, - [AC_DEFINE([HAVE_VALGRIND_MEMCHECK_H],1, [enable valgrind memchecks])]) - ]) - for i in -Wall -Wextra -Wno-unused-parameter; do AX_CHECK_COMPILE_FLAG([$i], [APCU_CFLAGS="$APCU_CFLAGS $i"]) done @@ -225,13 +224,6 @@ if test "$PHP_APCU" != "no"; then AC_DEFINE(HAVE_APCU, 1, [ ]) fi -PHP_ARG_ENABLE([coverage], - [whether to include code coverage symbols], - [AS_HELP_STRING([--enable-coverage], - [Include code coverage symbols (DEVELOPERS ONLY!!)])], - [no], - [no]) - if test "$PHP_COVERAGE" = "yes"; then if test "$GCC" != "yes"; then diff --git a/php_apc.c b/php_apc.c index 6193d89a..d00b5f3a 100644 --- a/php_apc.c +++ b/php_apc.c @@ -94,7 +94,7 @@ static void php_apc_init_globals(zend_apcu_globals* apcu_globals) static PHP_INI_MH(OnUpdateShmSegments) /* {{{ */ { zend_long shm_segments = ZEND_STRTOL(new_value->val, NULL, 10); -#if APC_MMAP +#ifdef APC_MMAP if (shm_segments != 1) { php_error_docref(NULL, E_WARNING, "apc.shm_segments setting ignored in MMAP mode"); } @@ -139,7 +139,7 @@ STD_PHP_INI_ENTRY("apc.entries_hint", "4096", PHP_INI_SYSTEM, OnUpdateLong, STD_PHP_INI_ENTRY("apc.gc_ttl", "3600", PHP_INI_SYSTEM, OnUpdateLong, gc_ttl, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.ttl", "0", PHP_INI_SYSTEM, OnUpdateLong, ttl, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.smart", "0", PHP_INI_SYSTEM, OnUpdateLong, smart, zend_apcu_globals, apcu_globals) -#if APC_MMAP +#ifdef APC_MMAP STD_PHP_INI_ENTRY("apc.mmap_file_mask", NULL, PHP_INI_SYSTEM, OnUpdateString, mmap_file_mask, zend_apcu_globals, apcu_globals) #endif STD_PHP_INI_BOOLEAN("apc.enable_cli", "0", PHP_INI_SYSTEM, OnUpdateBool, enable_cli, zend_apcu_globals, apcu_globals) @@ -168,7 +168,7 @@ static PHP_MINFO_FUNCTION(apcu) #else php_info_print_table_row(2, "APCu Debugging", "Disabled"); #endif -#if APC_MMAP +#ifdef APC_MMAP php_info_print_table_row(2, "MMAP Support", "Enabled"); php_info_print_table_row(2, "MMAP File Mask", APCG(mmap_file_mask)); #else @@ -235,7 +235,7 @@ static PHP_MINIT_FUNCTION(apcu) if (APCG(enabled)) { if (!APCG(initialized)) { -#if APC_MMAP +#ifdef APC_MMAP char *mmap_file_mask = APCG(mmap_file_mask); #else char *mmap_file_mask = NULL; From 004c7b40850acbe58ed6ec11c098732412db22c1 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Tue, 3 Sep 2024 00:36:09 +0200 Subject: [PATCH 023/101] Remove unused WIN32_ONLY_COMPILER compile definition --- config.w32 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.w32 b/config.w32 index 8ed38851..ebece42c 100644 --- a/config.w32 +++ b/config.w32 @@ -14,7 +14,7 @@ if(PHP_APCU != 'no') 'apc_signal.c ' + 'apc_time.c ' + 'apc_iterator.c ' + - 'apc_persist.c'; + 'apc_persist.c'; if(PHP_APCU_DEBUG != 'no') { @@ -24,7 +24,7 @@ if(PHP_APCU != 'no') /* XXX srwlock kernel as most BC compatible for now */ AC_DEFINE('APC_SRWLOCK_KERNEL', 1); AC_DEFINE('HAVE_APCU', 1); - ADD_FLAG('CFLAGS_APCU', '/D WIN32_ONLY_COMPILER=1 /DAPC_SRWLOCK_KERNEL=1'); + ADD_FLAG('CFLAGS_APCU', '/DAPC_SRWLOCK_KERNEL=1'); PHP_INSTALL_HEADERS("ext/apcu", "php_apc.h apc.h apc_api.h apc_cache.h apc_globals.h apc_iterator.h apc_lock.h apc_mutex.h apc_sma.h apc_serializer.h apc_stack.h apc_windows_srwlock_kernel.h apc_arginfo.h php_apc_legacy_arginfo.h"); EXTENSION('apcu', apc_sources, PHP_APCU_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); From 0f840e973f9473797e23a6f3d700fa635aec3c63 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Thu, 5 Sep 2024 20:47:23 +0200 Subject: [PATCH 024/101] Autotools: Refactor rt library check (#520) This appends the -lrt only as needed to the LIBS (if apcu is built as static) or to the APCU_SHARED_LIBADD variable (when APCu is built as shared). Modern *nix systems have shared memory operations located in default libraries and explicit linking with -lrt is redundant. --- config.m4 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config.m4 b/config.m4 index 47edf366..5682ca2d 100644 --- a/config.m4 +++ b/config.m4 @@ -216,7 +216,12 @@ if test "$PHP_APCU" != "no"; then apc_iterator.c \ apc_persist.c" - PHP_CHECK_LIBRARY(rt, shm_open, [PHP_ADD_LIBRARY(rt,,APCU_SHARED_LIBADD)]) + LIBS_SAVED=$LIBS; LIBS= + AC_SEARCH_LIBS([shm_open], [rt]) + LIBS=$LIBS_SAVED + AS_CASE([$ac_cv_search_shm_open], ["none required"|no], [], + [PHP_EVAL_LIBLINE([$ac_cv_search_shm_open], [APCU_SHARED_LIBADD])]) + PHP_NEW_EXTENSION([apcu], [$apc_sources], [$ext_shared],, [$APCU_CFLAGS]) PHP_SUBST(APCU_SHARED_LIBADD) PHP_SUBST(PHP_LDFLAGS) From 380c7c744598fdd0f9fe3c8ddc0873ae75f0d5d4 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Sat, 14 Sep 2024 22:35:01 +0100 Subject: [PATCH 025/101] Remove broken Travis badge from readme Follows-up https://github.com/krakjoe/apcu/pull/449. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a89c553f..776356e8 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ APCu only supports userland caching of variables. APCu is APC stripped of opcode caching. See [APCu Backwards Compatibility Module](https://github.com/krakjoe/apcu-bc) which provides a drop in replacement for APC. -[![Build Status](https://travis-ci.org/krakjoe/apcu.svg?branch=master)](https://travis-ci.org/krakjoe/apcu) -[![Build status](https://ci.appveyor.com/api/projects/status/om63glh4g24gi1p9/branch/master?svg=true)](https://ci.appveyor.com/project/krakjoe/apcu/branch/master) +[![Build status: GitHub](https://github.com/krakjoe/apcu/actions/workflows/config.yml/badge.svg)](https://github.com/krakjoe/apcu/actions/workflows/config.yml) +[![Build status: AppVeyor](https://ci.appveyor.com/api/projects/status/om63glh4g24gi1p9/branch/master?svg=true)](https://ci.appveyor.com/project/krakjoe/apcu/branch/master) Documentation ============ From f6d934d3b99c42db9f13542ba11030aef0598802 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sun, 15 Sep 2024 10:07:00 +0200 Subject: [PATCH 026/101] Use newer upload-artifact version --- .github/workflows/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 5d90c3e9..f5df58c9 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -49,7 +49,7 @@ jobs: uses: actions/checkout@v4 - name: Setup PHP id: setup-php - uses: php/setup-php-sdk@v0.8 + uses: php/setup-php-sdk@v0.9 with: version: ${{matrix.version}} arch: ${{matrix.arch}} @@ -79,7 +79,7 @@ jobs: copy %prefix%\php_apcu.dll .install copy %prefix%\php_apcu.pdb .install - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: apcu-${{matrix.version}}-${{matrix.arch}}-${{matrix.ts}} path: .install From 3299efc0987499d468693a3aa5c7e9d5548c4525 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sun, 15 Sep 2024 12:10:58 +0200 Subject: [PATCH 027/101] Try a fix for the setup-php-sdk issue --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index f5df58c9..14e7c251 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -49,7 +49,7 @@ jobs: uses: actions/checkout@v4 - name: Setup PHP id: setup-php - uses: php/setup-php-sdk@v0.9 + uses: php/setup-php-sdk@1248285eaa27e8fe99a03ad2108cf062669ac3f4 with: version: ${{matrix.version}} arch: ${{matrix.arch}} From 4f92c02c93dc175733341b033e603fb40f6b298f Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 18 Sep 2024 21:48:29 +0100 Subject: [PATCH 028/101] Added a composer.json to enable support for PIE --- composer.json | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..fd07afc1 --- /dev/null +++ b/composer.json @@ -0,0 +1,46 @@ +{ + "name": "krakjoe/apcu", + "type": "php-ext", + "license": "PHP-3.01", + "description": "APCu - APC User Cache", + "require": { + "php": ">= 7.0.0" + }, + "php-ext": { + "extension-name": "apcu", + "configure-options": [ + { + "name": "enable-apcu", + "description": "Enable APCu support" + }, + { + "name": "disable-apcu-rwlocks", + "description": "Disable rwlocks in APCu" + }, + { + "name": "enable-apcu-debug", + "description": "Enable APCu debugging" + }, + { + "name": "enable-apcu-clear-signal", + "description": "Enable SIGUSR1 clearing handler" + }, + { + "name": "disable-apcu-mmap", + "description": "Disable mmap, falls back on shm" + }, + { + "name": "enable-apcu-spinlocks", + "description": "Use spinlocks before flocks" + }, + { + "name": "disable-valgrind-checks", + "description": "Disable Valgrind-based memory checks" + }, + { + "name": "enable-coverage", + "description": "Include code coverage symbols (DEVELOPERS ONLY!!)" + } + ] + } +} From 12e92f85f24f82fe112f4c2b27934651e7343682 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 19 Sep 2024 07:21:46 +0100 Subject: [PATCH 029/101] Update vendor name to apcu --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fd07afc1..c9e8e80d 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "krakjoe/apcu", + "name": "apcu/apcu", "type": "php-ext", "license": "PHP-3.01", "description": "APCu - APC User Cache", From 62e67989a35247263c370b5ecebb4e69b73b0709 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 21 Sep 2024 21:07:45 +0200 Subject: [PATCH 030/101] Release apcu 5.1.24 --- package.xml | 8 +++++--- php_apc.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.xml b/package.xml index 25433c18..34716dee 100644 --- a/package.xml +++ b/package.xml @@ -28,9 +28,9 @@ nikic@php.net yes - 2023-11-11 + 2024-09-21 - 5.1.24dev + 5.1.24 5.1.18 @@ -39,7 +39,9 @@ PHP License -- TBD +- Make compatible with PHP 8.4. +- apc.php: Preserve search parameter when deleting entry. +- apc.PHP: Allow arbitrary search regex. diff --git a/php_apc.h b/php_apc.h index 4456f3f9..44ee5619 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.23-dev" +#define PHP_APCU_VERSION "5.1.24" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From b1163e83cc80aa9eb79dd7cfe480efd335bcca5d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 21 Sep 2024 21:18:01 +0200 Subject: [PATCH 031/101] Back to dev --- package.xml | 23 +++++++++++++++++++---- php_apc.h | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/package.xml b/package.xml index 34716dee..f9c90514 100644 --- a/package.xml +++ b/package.xml @@ -30,7 +30,7 @@ 2024-09-21 - 5.1.24 + 5.1.25-dev 5.1.18 @@ -39,9 +39,7 @@ PHP License -- Make compatible with PHP 8.4. -- apc.php: Preserve search parameter when deleting entry. -- apc.PHP: Allow arbitrary search regex. +- TBD @@ -187,6 +185,23 @@ + + 2024-09-21 + + 5.1.24 + 5.1.18 + + + stable + stable + + PHP License + +- Make compatible with PHP 8.4. +- apc.php: Preserve search parameter when deleting entry. +- apc.php: Allow arbitrary search regex. + + 2023-11-11 diff --git a/php_apc.h b/php_apc.h index 44ee5619..3ca816fb 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.24" +#define PHP_APCU_VERSION "5.1.25-dev" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From 808ab43fec912c08a4583280daf20d36bafd1523 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Sun, 29 Sep 2024 12:28:34 +0200 Subject: [PATCH 032/101] Run Windows CI against PHP 8.4, too We also change the setup-php-sdk branch to a commit on the main branch. This should be changed again when a new setup-php-sdk release is made. --- .github/workflows/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 14e7c251..57beec9c 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -31,11 +31,12 @@ jobs: shell: cmd strategy: matrix: - version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3"] + version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] arch: [x64] ts: [nts, ts] os: [windows-2019, windows-2022] exclude: + - { os: windows-2019, version: "8.4" } - { os: windows-2019, version: "8.3" } - { os: windows-2019, version: "8.2" } - { os: windows-2019, version: "8.1" } @@ -49,7 +50,7 @@ jobs: uses: actions/checkout@v4 - name: Setup PHP id: setup-php - uses: php/setup-php-sdk@1248285eaa27e8fe99a03ad2108cf062669ac3f4 + uses: php/setup-php-sdk@8d9e79bdbce494591e33d542377778613cfca59f with: version: ${{matrix.version}} arch: ${{matrix.arch}} From 5d9a52e4134cfdd46e63b09ed2b8a2c6e1b53add Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Sun, 29 Sep 2024 12:41:59 +0200 Subject: [PATCH 033/101] Fix Windows CI artifact upload As of actions/upload-artifact@v4, hidden files are excluded from uploading by default, so the `.install` folder is no longer uploaded. Instead of changing the default, we change the name of the folder to `win-install` which generally appears to make more sense than a hidden folder. --- .github/workflows/config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 57beec9c..b16e9ce8 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -70,17 +70,17 @@ jobs: run: nmake test TESTS="--show-diff tests" - name: package run: | - md .install - copy LICENSE .install + md win-install + copy LICENSE win-install if exist x64 ( if exist x64\Release (set prefix=x64\Release) else set prefix=x64\Release_TS ) else ( if exist Release (set prefix=Release) else set prefix=Release_TS ) - copy %prefix%\php_apcu.dll .install - copy %prefix%\php_apcu.pdb .install + copy %prefix%\php_apcu.dll win-install + copy %prefix%\php_apcu.pdb win-install - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: apcu-${{matrix.version}}-${{matrix.arch}}-${{matrix.ts}} - path: .install + path: win-install From f1aed54162c60ffcd4049436d465edd2167d0bc8 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 30 Sep 2024 22:14:24 +0200 Subject: [PATCH 034/101] Use caching for Windows CI and add PHP 7.0/7.1 to matrix (#528) --- .github/workflows/config.yml | 7 ++- appveyor.yml | 117 ----------------------------------- 2 files changed, 5 insertions(+), 119 deletions(-) delete mode 100644 appveyor.yml diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index b16e9ce8..065d8018 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -31,7 +31,7 @@ jobs: shell: cmd strategy: matrix: - version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] + version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] arch: [x64] ts: [nts, ts] os: [windows-2019, windows-2022] @@ -44,17 +44,20 @@ jobs: - { os: windows-2022, version: "7.4" } - { os: windows-2022, version: "7.3" } - { os: windows-2022, version: "7.2" } + - { os: windows-2022, version: "7.1" } + - { os: windows-2022, version: "7.0" } runs-on: ${{matrix.os}} steps: - name: Checkout apcu uses: actions/checkout@v4 - name: Setup PHP id: setup-php - uses: php/setup-php-sdk@8d9e79bdbce494591e33d542377778613cfca59f + uses: php/setup-php-sdk@v0.10 with: version: ${{matrix.version}} arch: ${{matrix.arch}} ts: ${{matrix.ts}} + cache: true - name: Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@v1 with: diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 5d414d8b..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,117 +0,0 @@ -version: "{branch}.build.{build}" -skip_tags: true - -branches: - only: - - master - -clone_folder: c:\projects\apcu - -install: - ps: .appveyor\install.ps1 - -cache: - c:\build-cache -> appveyor.yml, .appveyor\install.ps1 - -environment: - BIN_SDK_VER: 2.2.0 - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - ARCH: x64 - VC: vc14 - PHP_VER: 7.0 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - ARCH: x64 - VC: vc14 - PHP_VER: 7.1 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x64 - VC: vc15 - PHP_VER: 7.2 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x64 - VC: vc15 - PHP_VER: 7.2 - TS: 1 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x86 - VC: vc15 - PHP_VER: 7.2 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x86 - VC: vc15 - PHP_VER: 7.2 - TS: 1 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x64 - VC: vc15 - PHP_VER: 7.3 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x64 - VC: vc15 - PHP_VER: 7.3 - TS: 1 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x86 - VC: vc15 - PHP_VER: 7.3 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x86 - VC: vc15 - PHP_VER: 7.3 - TS: 1 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x64 - VC: vc15 - PHP_VER: 7.4 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x64 - VC: vc15 - PHP_VER: 7.4 - TS: 1 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x86 - VC: vc15 - PHP_VER: 7.4 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - ARCH: x86 - VC: vc15 - PHP_VER: 7.4 - TS: 1 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - ARCH: x64 - VC: vs16 - PHP_VER: 8.0 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - ARCH: x64 - VC: vs16 - PHP_VER: 8.0 - TS: 1 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - ARCH: x86 - VC: vs16 - PHP_VER: 8.0 - TS: 0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - ARCH: x86 - VC: vs16 - PHP_VER: 8.0 - TS: 1 - -build_script: - ps: .appveyor\build.ps1 - -after_build: - ps: .appveyor\package.ps1 - -test_script: - ps: .appveyor\test.ps1 From 2b2eeca6106a5da4ec681ca978bc4c302d826d4a Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 30 Sep 2024 22:19:46 +0200 Subject: [PATCH 035/101] Try to restore a dummy appveyor.yml I can't disable it, so avoid failing every time? --- appveyor.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..d7f96c81 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,11 @@ +version: "{branch}.build.{build}" +skip_tags: true + +branches: + only: + - master + +clone_folder: c:\projects\apcu + +test_script: + - echo Dummy From 9235333e60b4d544267832a0c9a935db282c6ea7 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 30 Sep 2024 22:21:46 +0200 Subject: [PATCH 036/101] Try build: off --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index d7f96c81..cd69f7ec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,5 +7,7 @@ branches: clone_folder: c:\projects\apcu +build: off + test_script: - echo Dummy From 635899cfe32496323d03e051e15be220f8b53d68 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 30 Sep 2024 22:23:40 +0200 Subject: [PATCH 037/101] Remove .appveyor directory --- .appveyor/build.ps1 | 31 ---------------------- .appveyor/install.ps1 | 61 ------------------------------------------- .appveyor/package.ps1 | 20 -------------- .appveyor/test.ps1 | 9 ------- 4 files changed, 121 deletions(-) delete mode 100644 .appveyor/build.ps1 delete mode 100644 .appveyor/install.ps1 delete mode 100644 .appveyor/package.ps1 delete mode 100644 .appveyor/test.ps1 diff --git a/.appveyor/build.ps1 b/.appveyor/build.ps1 deleted file mode 100644 index b175e575..00000000 --- a/.appveyor/build.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -$ErrorActionPreference = "Stop" - -Set-Location 'c:\projects\apcu' - -$task = New-Item 'task.bat' -Force -Add-Content $task "call phpize 2>&1" -Add-Content $task "call configure --enable-apcu --enable-debug-pack 2>&1" -Add-Content $task "nmake /nologo 2>&1" -Add-Content $task "exit %errorlevel%" -& "c:\build-cache\php-sdk-$env:BIN_SDK_VER\phpsdk-$env:VC-$env:ARCH.bat" -t $task -if (-not $?) { - throw "build failed with errorlevel $LastExitCode" -} - -$source = '' -if ($env:ARCH -eq 'x64') { - $source += 'x64\' -} -$source += 'Release'; -if ($env:TS -eq '1') { - $source += '_TS' -} - -$file = Get-Command php | Select-Object -ExpandProperty Definition -$dest = (Get-Item $file).Directory.FullName - -Copy-Item "$source\php_apcu.dll" "$dest\ext\php_apcu.dll" - -$ini = New-Item "$dest\php.ini" -Force -Add-Content $ini "extension_dir=$dest\ext" -Add-Content $ini 'extension=php_apcu.dll' diff --git a/.appveyor/install.ps1 b/.appveyor/install.ps1 deleted file mode 100644 index 6ee46d7f..00000000 --- a/.appveyor/install.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -$ErrorActionPreference = "Stop" - -if (-not (Test-Path 'c:\build-cache')) { - [void](New-Item 'c:\build-cache' -ItemType 'directory') -} - -$bname = "php-sdk-$env:BIN_SDK_VER.zip" -Write-Host $bname -if (-not (Test-Path c:\build-cache\$bname)) { - Invoke-WebRequest "https://github.com/microsoft/php-sdk-binary-tools/archive/$bname" -OutFile "c:\build-cache\$bname" -} -$dname0 = "php-sdk-binary-tools-php-sdk-$env:BIN_SDK_VER" -$dname1 = "php-sdk-$env:BIN_SDK_VER" -if (-not (Test-Path 'c:\build-cache\$dname1')) { - Expand-Archive "c:\build-cache\$bname" "c:\build-cache" - Move-Item "c:\build-cache\$dname0" "c:\build-cache\$dname1" -} - -$releases = @{ - '7.0' = '7.0.33'; - '7.1' = '7.1.33'; - '7.2' = '7.2.34'; - '7.3' = '7.3.33'; -} -if ($releases.ContainsKey($env:PHP_VER)) { - $phpversion = $releases.$env:PHP_VER; - $base_url = 'http://windows.php.net/downloads/releases/archives'; -} else { - $releases = Invoke-WebRequest https://windows.php.net/downloads/releases/releases.json | ConvertFrom-Json - $phpversion = $releases.$env:PHP_VER.version - $base_url = 'http://windows.php.net/downloads/releases'; -} - -$ts_part = '' -if ($env:TS -eq '0') { - $ts_part += '-nts' -} - -$bname = "php-devel-pack-$phpversion$ts_part-Win32-$env:VC-$env:ARCH.zip" -if (-not (Test-Path "c:\build-cache\$bname")) { - Invoke-WebRequest "$base_url/$bname" -OutFile "c:\build-cache\$bname" -} -$dname0 = "php-$phpversion-devel-$env:VC-$env:ARCH" -$dname1 = "php-$phpversion$ts_part-devel-$env:VC-$env:ARCH" -if (-not (Test-Path "c:\build-cache\$dname1")) { - Expand-Archive "c:\build-cache\$bname" "c:\build-cache" - if ($dname0 -ne $dname1) { - Move-Item "c:\build-cache\$dname0" "c:\build-cache\$dname1" - } -} -$env:PATH = "c:\build-cache\$dname1;$env:PATH" - -$bname = "php-$phpversion$ts_part-Win32-$env:VC-$env:ARCH.zip" -if (-not (Test-Path "c:\build-cache\$bname")) { - Invoke-WebRequest "$base_url/$bname" -OutFile "c:\build-cache\$bname" -} -$dname = "php-$phpversion$ts_part-$env:VC-$env:ARCH" -if (-not (Test-Path "c:\build-cache\$dname")) { - Expand-Archive "c:\build-cache\$bname" "c:\build-cache\$dname" -} -$env:PATH = "c:\build-cache\$dname;$env:PATH" diff --git a/.appveyor/package.ps1 b/.appveyor/package.ps1 deleted file mode 100644 index 51eb5845..00000000 --- a/.appveyor/package.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -$ErrorActionPreference = "Stop" - -$ts_part = 'ts' -if ($env:TS -eq '0') { - $ts_part += 'nts' -} -$commit = $env:APPVEYOR_REPO_COMMIT.substring(0, 8) -$zip_bname = "php_apcu-$commit-$env:PHP_VER-$ts_part-$env:VC-$env:ARCH.zip" - -$dir = 'c:\projects\apcu\' -if ($env:ARCH -eq 'x64') { - $dir += 'x64\' -} -$dir += 'Release' -if ($env:TS -eq '1') { - $dir += '_TS' -} - -Compress-Archive @("$dir\php_apcu.dll", "$dir\php_apcu.pdb", "c:\projects\apcu\LICENSE") "c:\$zip_bname" -Push-AppveyorArtifact "c:\$zip_bname" diff --git a/.appveyor/test.ps1 b/.appveyor/test.ps1 deleted file mode 100644 index 481f3e92..00000000 --- a/.appveyor/test.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -$ErrorActionPreference = "Stop" - -Set-Location 'c:\projects\apcu' - -$env:TEST_PHP_EXECUTABLE = Get-Command 'php' | Select-Object -ExpandProperty 'Definition' -& $env:TEST_PHP_EXECUTABLE 'run-tests.php' --show-diff tests -if (-not $?) { - throw "tests failed with errorlevel $LastExitCode" -} From 0415f0ac386c168419b585ec7958fd1c1b106b0c Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:29:19 +0100 Subject: [PATCH 038/101] Try to cleanup cache entries with explicit ttl when apc.ttl is 0 --- apc_cache.c | 52 ++++++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 142f0d4f..3544c097 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -760,40 +760,32 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) /* get available */ available = apc_sma_get_avail_mem(cache->sma); - /* perform expunge processing */ - if (!cache->ttl) { - /* check it is necessary to expunge */ - if (available < suitable) { - apc_cache_wlocked_real_expunge(cache); - } - } else { - /* check that expunge is necessary */ - if (available < suitable) { - size_t i; - - /* look for junk */ - for (i = 0; i < cache->nslots; i++) { - apc_cache_entry_t **entry = &cache->slots[i]; - while (*entry) { - if (apc_cache_entry_expired(cache, *entry, t)) { - apc_cache_wlocked_remove_entry(cache, entry); - continue; - } - - /* grab next entry */ - entry = &(*entry)->next; + /* check that expunge is necessary */ + if (available < suitable) { + size_t i; + + /* look for junk */ + for (i = 0; i < cache->nslots; i++) { + apc_cache_entry_t **entry = &cache->slots[i]; + while (*entry) { + if (apc_cache_entry_expired(cache, *entry, t)) { + apc_cache_wlocked_remove_entry(cache, entry); + continue; } - } - /* if the cache now has space, then reset last key */ - if (apc_sma_get_avail_size(cache->sma, size)) { - /* wipe lastkey */ - memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t)); - } else { - /* with not enough space left in cache, we are forced to expunge */ - apc_cache_wlocked_real_expunge(cache); + /* grab next entry */ + entry = &(*entry)->next; } } + + /* if the cache now has space, then reset last key */ + if (apc_sma_get_avail_size(cache->sma, size)) { + /* wipe lastkey */ + memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t)); + } else { + /* with not enough space left in cache, we are forced to expunge */ + apc_cache_wlocked_real_expunge(cache); + } } apc_cache_wunlock(cache); From f26d563f205c11fdc84d47cbd527e7a77f1eb8bc Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Wed, 26 Feb 2025 20:58:28 +0100 Subject: [PATCH 039/101] Fix apc_sma_get_avail_size() to look for a contiguous block of memory to make expunge more reliable (#532) apc_sma_get_avail_size() is used by apc_cache_default_expunge() to check whether enough memory is available to store a new cache entry after expired entries are removed. If there is not enough memory available, a real expunge is performed to be able to store the new entry. The problem is that apc_sma_get_avail_size() only checks the total amount of available memory, which is usually divided into several fragments of memory. However, we need a contiguous block of memory to store a cache entry. This pull request fixes apc_sma_get_avail_size() to check if a continuous block of memory is available to store a new entry, which solves the problem. Prior to this fix, the result was that in situations with fragmentation, new cache entries cannot be saved and PHP functions such as apcu_store() return false, which is not the expected behavior. --- apc_sma.c | 24 +++++++++++++++++++++--- apc_sma.h | 2 +- tests/apc_020.phpt | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apc_sma.c b/apc_sma.c index b0bbbc86..a4534012 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -631,11 +631,29 @@ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { int32_t i; for (i = 0; i < sma->num; i++) { - sma_header_t* header = SMA_HDR(sma, i); - if (header->avail > size) { - return 1; + sma_header_t *shmaddr = SMA_HDR(sma, i); + + /* If total size of available memory is too small, we can skip the contiguous-block check */ + if (shmaddr->avail < size) { + continue; + } + + SMA_LOCK(sma, i); + block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + + /* Look for a contiguous block of memory */ + while (cur->fnext) { + cur = BLOCKAT(cur->fnext); + + if (cur->size >= size) { + SMA_UNLOCK(sma, i); + return 1; + } } + + SMA_UNLOCK(sma, i); } + return 0; } diff --git a/apc_sma.h b/apc_sma.h index b4891f5e..6e4998e1 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -138,7 +138,7 @@ PHP_APCU_API void apc_sma_free_info(apc_sma_t* sma, apc_sma_info_t* info); PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma); /* -* apc_sma_api_get_avail_size will return true if at least size bytes are available to the sma +* apc_sma_api_get_avail_size will return true if at least size contiguous bytes are available to the sma */ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size); diff --git a/tests/apc_020.phpt b/tests/apc_020.phpt index ea388d31..7c74ee3d 100644 --- a/tests/apc_020.phpt +++ b/tests/apc_020.phpt @@ -17,7 +17,7 @@ apc.shm_size=1M apcu_store("no_ttl_unaccessed", 12); apcu_store("no_ttl_accessed", 24); apcu_store("ttl", 42, 3); -apcu_store("dummy", "xxx"); +apcu_store("dummy", str_repeat('x', 1000)); apcu_inc_request_time(1); apcu_fetch("no_ttl_accessed"); From 5f37033f4ba6aa1de76ccbedd68dec7b4e869dac Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Sat, 1 Mar 2025 23:17:39 +0100 Subject: [PATCH 040/101] Fix memory size passed to expunge() The function apc_sma_get_avail_size() now takes care of computing the real size to achieve better separation of layers. In addition, the computation of the real size has been fixed. --- apc_sma.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apc_sma.c b/apc_sma.c index a4534012..8811b104 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -449,7 +449,7 @@ PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated /* Expunge cache in hope of freeing up memory, but only once */ if (!nuked) { - sma->expunge(*sma->data, n+fragment); + sma->expunge(*sma->data, n); nuked = 1; goto restart; } @@ -629,12 +629,13 @@ PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma) { PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { int32_t i; + size_t realsize = ALIGNWORD(size + ALIGNWORD(sizeof(struct block_t))); for (i = 0; i < sma->num; i++) { sma_header_t *shmaddr = SMA_HDR(sma, i); /* If total size of available memory is too small, we can skip the contiguous-block check */ - if (shmaddr->avail < size) { + if (shmaddr->avail < realsize) { continue; } @@ -645,7 +646,7 @@ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { while (cur->fnext) { cur = BLOCKAT(cur->fnext); - if (cur->size >= size) { + if (cur->size >= realsize) { SMA_UNLOCK(sma, i); return 1; } From fbeaaf6376e0bd45a71fd980e87af49294a14c55 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:57:45 +0100 Subject: [PATCH 041/101] config.m4: Add -Wno-clobbered to fix compiler warnings This fixes compiler warnings in out-of-tree builds. --- config.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.m4 b/config.m4 index 5682ca2d..5e497853 100644 --- a/config.m4 +++ b/config.m4 @@ -201,7 +201,7 @@ if test "$PHP_APCU" != "no"; then AC_CHECK_FUNCS(sigaction) - for i in -Wall -Wextra -Wno-unused-parameter; do + for i in -Wall -Wextra -Wno-clobbered -Wno-unused-parameter; do AX_CHECK_COMPILE_FLAG([$i], [APCU_CFLAGS="$APCU_CFLAGS $i"]) done From 0649eb110e95f3f94baeb3e819d997ed51dd3a6a Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Fri, 7 Mar 2025 19:06:54 +0100 Subject: [PATCH 042/101] Cleanup of entries should not be skipped when using default settings By default (apc.smart = 0), apc_cache_default_expunge() should not skip cleanup of old entries. This may cause insertion of new entries to fail unnecessarily, which is unexpected behavior. --- apc_cache.c | 52 +++++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 3544c097..9652a03d 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -735,8 +735,7 @@ PHP_APCU_API void apc_cache_clear(apc_cache_t* cache) PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) { time_t t; - size_t suitable = 0L; - size_t available = 0L; + size_t i; if (!cache) { return; @@ -751,43 +750,38 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) return; } - /* make suitable selection */ - suitable = (cache->smart > 0L) ? (size_t) (cache->smart * size) : (size_t) (cache->sma->size/2); - /* gc */ apc_cache_wlocked_gc(cache); - /* get available */ - available = apc_sma_get_avail_mem(cache->sma); - /* check that expunge is necessary */ - if (available < suitable) { - size_t i; - - /* look for junk */ - for (i = 0; i < cache->nslots; i++) { - apc_cache_entry_t **entry = &cache->slots[i]; - while (*entry) { - if (apc_cache_entry_expired(cache, *entry, t)) { - apc_cache_wlocked_remove_entry(cache, entry); - continue; - } + if (cache->smart > 0L && apc_sma_get_avail_mem(cache->sma) >= (size_t) (cache->smart * size)) { + apc_cache_wunlock(cache); + return; + } - /* grab next entry */ - entry = &(*entry)->next; + /* look for junk */ + for (i = 0; i < cache->nslots; i++) { + apc_cache_entry_t **entry = &cache->slots[i]; + while (*entry) { + if (apc_cache_entry_expired(cache, *entry, t)) { + apc_cache_wlocked_remove_entry(cache, entry); + continue; } - } - /* if the cache now has space, then reset last key */ - if (apc_sma_get_avail_size(cache->sma, size)) { - /* wipe lastkey */ - memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t)); - } else { - /* with not enough space left in cache, we are forced to expunge */ - apc_cache_wlocked_real_expunge(cache); + /* grab next entry */ + entry = &(*entry)->next; } } + /* if the cache now has space, then reset last key */ + if (apc_sma_get_avail_size(cache->sma, size)) { + /* wipe lastkey */ + memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t)); + } else { + /* with not enough space left in cache, we are forced to expunge */ + apc_cache_wlocked_real_expunge(cache); + } + apc_cache_wunlock(cache); } /* }}} */ From 89618de5319acc7f27e8cc4d34e2d1f6872e6a46 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Mon, 17 Mar 2025 03:02:53 +0100 Subject: [PATCH 043/101] Change behavior of smart configuration setting The smart configuration setting can now be used to increase the chance of full cache wipes during periods of high memory pressure. The idea is to use smart values > 1 to avoid performance problems when the expunge() hook is executed too often. The previous behavior was changed because it could unpredictably cause the insertion of new cache entries to fail, making the smart setting nearly useless. --- apc_cache.c | 8 +++----- apc_cache.h | 16 ++++------------ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 9652a03d..b7c1bd03 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -753,11 +753,9 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) /* gc */ apc_cache_wlocked_gc(cache); - /* check that expunge is necessary */ - if (cache->smart > 0L && apc_sma_get_avail_mem(cache->sma) >= (size_t) (cache->smart * size)) { - apc_cache_wunlock(cache); - return; - } + /* smart > 1 increases the probability of a full cache wipe, + * so expunge() is called less often when memory is low. */ + size = (cache->smart > 0L) ? (size_t) (cache->smart * size) : size; /* look for junk */ for (i = 0; i < cache->nslots; i++) { diff --git a/apc_cache.h b/apc_cache.h index e987a546..2ee06169 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -251,20 +251,12 @@ PHP_APCU_API void apc_cache_serializer(apc_cache_t* cache, const char* name); /* {{{ apc_cache_default_expunge * Where smart is not set: -* Where no ttl is set on cache: -* 1) Perform cleanup of stale entries -* 2) Expunge if available memory is less than sma->size/2 -* Where ttl is set on cache: -* 1) Perform cleanup of stale entries -* 2) If available memory if less than the size requested, run full expunge +* 1) Perform cleanup of stale entries +* 2) If available memory is less than the size requested, run full expunge * * Where smart is set: -* Where no ttl is set on cache: -* 1) Perform cleanup of stale entries -* 2) Expunge is available memory is less than size * smart -* Where ttl is set on cache: -* 1) Perform cleanup of stale entries -* 2) If available memory if less than the size requested, run full expunge +* 1) Perform cleanup of stale entries +* 2) If available memory is less than the size requested * smart, run full expunge * * The TTL of an entry takes precedence over the TTL of a cache */ From 3c23c473422caae17587d0b0e5c42e34173cee2e Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Thu, 27 Mar 2025 19:31:13 +0100 Subject: [PATCH 044/101] Remove unused function apc_cache_find() --- TECHNOTES.txt | 5 ++--- apc_cache.c | 20 -------------------- apc_cache.h | 9 +-------- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/TECHNOTES.txt b/TECHNOTES.txt index f993b61d..a48e1899 100644 --- a/TECHNOTES.txt +++ b/TECHNOTES.txt @@ -233,9 +233,8 @@ form of a quick-start guide to start hacking on APCu. deletion occurs depends on runtime parameters, see INSTALL for runtime parameter configuration details. - apc_cache_find() simply hashes and returns the entry if it is there. If it is there - but older than the mtime in the entry we are looking for, we delete the one that is - there and return indicating we didn't find it. + apc_cache_rlocked_find() simply hashes and returns the entry if it is there. If it is there + but older than the mtime in the entry we are looking for, we return indicating we didn't find it. API users are advised to use apc_cache_fetch over find for simplicity, this ensures correct operation, fetch sets up the call to find and takes care of copying and releasing diff --git a/apc_cache.c b/apc_cache.c index b7c1bd03..f6822b3f 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -784,26 +784,6 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) } /* }}} */ -/* {{{ apc_cache_find */ -PHP_APCU_API apc_cache_entry_t *apc_cache_find(apc_cache_t* cache, zend_string *key, time_t t) -{ - apc_cache_entry_t *entry; - - if (!cache) { - return NULL; - } - - if (!apc_cache_rlock(cache)) { - return NULL; - } - - entry = apc_cache_rlocked_find_incref(cache, key, t); - apc_cache_runlock(cache); - - return entry; -} -/* }}} */ - /* {{{ apc_cache_fetch */ PHP_APCU_API zend_bool apc_cache_fetch(apc_cache_t* cache, zend_string *key, time_t t, zval *dst) { diff --git a/apc_cache.h b/apc_cache.h index 2ee06169..b26dd0d1 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -167,13 +167,6 @@ PHP_APCU_API zend_bool apc_cache_atomic_update_long( apc_cache_t *cache, zend_string *key, apc_cache_atomic_updater_t updater, void *data, zend_bool insert_if_not_found, zend_long ttl); -/* - * apc_cache_find searches for a cache entry by its hashed identifier, - * and returns a pointer to the entry if found, NULL otherwise. - * - */ -PHP_APCU_API apc_cache_entry_t* apc_cache_find(apc_cache_t* cache, zend_string *key, time_t t); - /* * apc_cache_fetch fetches an entry from the cache directly into dst * @@ -198,7 +191,7 @@ PHP_APCU_API zend_bool apc_cache_entry_fetch_zval( /* * apc_cache_entry_release decrements the reference count associated with a cache - * entry. Calling apc_cache_find automatically increments the reference count, + * entry. Calling apc_cache_rlocked_find_incref automatically increments the reference count, * and this function must be called post-execution to return the count to its * original value. Failing to do so will prevent the entry from being * garbage-collected. From 426ac326d8d4f23939610bdaad09f74516513a79 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:11:51 +0200 Subject: [PATCH 045/101] Fix minimum block size used by sma layer The minimum allocation size previously used by the sma layer was too small. This could lead to small free blocks that could not be used for any allocation. To keep separation of layers, the expected minimum allocation size is now passed to apc_sma_init(). --- apc_cache.h | 2 ++ apc_sma.c | 22 +++++++++++----------- apc_sma.h | 3 ++- php_apc.c | 2 +- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apc_cache.h b/apc_cache.h index b26dd0d1..a314b5e0 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -299,6 +299,8 @@ static inline void apc_cache_runlock(apc_cache_t *cache) { } } +#define APC_ENTRY_MIN_ALLOC_SIZE (ZEND_MM_ALIGNED_SIZE(sizeof(apc_cache_entry_t)) + ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(1))) + #endif /* diff --git a/apc_sma.c b/apc_sma.c index 8811b104..102b6eb8 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -152,15 +152,15 @@ static inline block_t *find_block(sma_header_t *header, size_t realsize) { } /* {{{ sma_allocate: tries to allocate at least size bytes in a segment */ -static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t fragment, size_t *allocated) +static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t min_block_size, size_t *allocated) { void* shmaddr; /* header of shared memory segment */ block_t* prv; /* block prior to working block */ block_t* cur; /* working block in list */ size_t realsize; /* actual size of block needed, including header */ - size_t block_size = ALIGNWORD(sizeof(struct block_t)); + size_t block_header_size = ALIGNWORD(sizeof(struct block_t)); - realsize = ALIGNWORD(size + block_size); + realsize = ALIGNWORD(size + block_header_size); /* * First, insure that the segment contains at least realsize free bytes, @@ -178,9 +178,9 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t return SIZE_MAX; } - if (cur->size == realsize || (cur->size > realsize && cur->size < (realsize + (MINBLOCKSIZE + fragment)))) { + if (cur->size >= realsize && cur->size < (realsize + min_block_size)) { /* cur is big enough for realsize, but too small to split - unlink it */ - *(allocated) = cur->size - block_size; + *(allocated) = cur->size - block_header_size; prv = BLOCKAT(cur->fprev); prv->fnext = cur->fnext; BLOCKAT(cur->fnext)->fprev = OFFSET(prv); @@ -192,7 +192,7 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t oldsize = cur->size; cur->size = realsize; - *(allocated) = cur->size - block_size; + *(allocated) = cur->size - block_header_size; nxt = NEXT_SBLOCK(cur); nxt->prev_size = 0; /* block is alloc'd */ nxt->size = oldsize - realsize; /* and fix the size */ @@ -221,7 +221,7 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t fprintf(stderr, "allocate(realsize=%d,size=%d,id=%d)\n", (int)(size), (int)(cur->size), cur->id); #endif - return OFFSET(cur) + block_size; + return OFFSET(cur) + block_header_size; } /* }}} */ @@ -288,7 +288,7 @@ static APC_HOTSPOT size_t sma_deallocate(void* shmaddr, size_t offset) /* }}} */ /* {{{ APC SMA API */ -PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, int32_t num, size_t size, char *mask) { +PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, int32_t num, size_t size, size_t min_alloc_size, char *mask) { int32_t i; if (sma->initialized) { @@ -298,6 +298,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex sma->initialized = 1; sma->expunge = expunge; sma->data = data; + sma->min_block_size = min_alloc_size > 0 ? ALIGNWORD(min_alloc_size + ALIGNWORD(sizeof(struct block_t))) : MINBLOCKSIZE; #ifdef APC_MMAP /* @@ -399,7 +400,6 @@ PHP_APCU_API void apc_sma_detach(apc_sma_t* sma) { } PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated) { - size_t fragment = MINBLOCKSIZE; size_t off; int32_t i; zend_bool nuked = 0; @@ -412,7 +412,7 @@ PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated return NULL; } - off = sma_allocate(SMA_HDR(sma, last), n, fragment, allocated); + off = sma_allocate(SMA_HDR(sma, last), n, sma->min_block_size, allocated); if (off != SIZE_MAX) { void* p = (void *)(SMA_ADDR(sma, last) + off); @@ -434,7 +434,7 @@ PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated return NULL; } - off = sma_allocate(SMA_HDR(sma, i), n, fragment, allocated); + off = sma_allocate(SMA_HDR(sma, i), n, sma->min_block_size, allocated); if (off != SIZE_MAX) { void* p = (void *)(SMA_ADDR(sma, i) + off); sma->last = i; diff --git a/apc_sma.h b/apc_sma.h index 6e4998e1..f2c680d9 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -76,6 +76,7 @@ typedef struct _apc_sma_t { int32_t num; /* number of segments */ size_t size; /* segment size */ int32_t last; /* last segment */ + size_t min_block_size; /* expected minimum size of allocated blocks */ /* segments */ apc_segment_t *segs; /* segments */ @@ -88,7 +89,7 @@ typedef struct _apc_sma_t { */ PHP_APCU_API void apc_sma_init( apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, - int32_t num, size_t size, char *mask); + int32_t num, size_t size, size_t min_alloc_size, char *mask); /* * apc_sma_detach will detach from shared memory and cleanup local allocations. diff --git a/php_apc.c b/php_apc.c index d00b5f3a..0ddc71df 100644 --- a/php_apc.c +++ b/php_apc.c @@ -247,7 +247,7 @@ static PHP_MINIT_FUNCTION(apcu) /* initialize shared memory allocator */ apc_sma_init( &apc_sma, (void **) &apc_user_cache, (apc_sma_expunge_f) apc_cache_default_expunge, - APCG(shm_segments), APCG(shm_size), mmap_file_mask); + APCG(shm_segments), APCG(shm_size), APC_ENTRY_MIN_ALLOC_SIZE, mmap_file_mask); REGISTER_LONG_CONSTANT(APC_SERIALIZER_CONSTANT, (zend_long)&_apc_register_serializer, CONST_PERSISTENT | CONST_CS); From 7f84f2b897020fe3f39824e50e7c75426f9c54c4 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Thu, 3 Apr 2025 00:05:11 +0200 Subject: [PATCH 046/101] Remove unused code disabled by define APC_MEMPROTECT --- apc_mmap.c | 24 ---------------------- apc_shm.c | 14 ------------- apc_sma.c | 58 ------------------------------------------------------ apc_sma.h | 13 ------------ 4 files changed, 109 deletions(-) diff --git a/apc_mmap.c b/apc_mmap.c index faabf93f..615f0ea5 100644 --- a/apc_mmap.c +++ b/apc_mmap.c @@ -57,9 +57,6 @@ apc_segment_t apc_mmap(char *file_mask, size_t size) int fd = -1; int flags = MAP_SHARED | MAP_NOSYNC; -#ifdef APC_MEMPROTECT - int remap = 1; -#endif /* If no filename was provided, do an anonymous mmap */ if(!file_mask || (file_mask && !strlen(file_mask))) { @@ -68,18 +65,12 @@ apc_segment_t apc_mmap(char *file_mask, size_t size) #else fd = -1; flags = MAP_SHARED | MAP_ANON; -#ifdef APC_MEMPROTECT - remap = 0; -#endif #endif } else if(!strcmp(file_mask,"/dev/zero")) { fd = open("/dev/zero", O_RDWR, S_IRUSR | S_IWUSR); if(fd == -1) { zend_error_noreturn(E_CORE_ERROR, "apc_mmap: open on /dev/zero failed"); } -#ifdef APC_MEMPROTECT - remap = 0; /* cannot remap */ -#endif } else { /* * Otherwise we do a normal filesystem mmap @@ -99,14 +90,6 @@ apc_segment_t apc_mmap(char *file_mask, size_t size) segment.shmaddr = (void *)mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0); segment.size = size; -#ifdef APC_MEMPROTECT - if(remap) { - segment.roaddr = (void *)mmap(NULL, size, PROT_READ, flags, fd, 0); - } else { - segment.roaddr = NULL; - } -#endif - if ((long)segment.shmaddr == -1) { zend_error_noreturn(E_CORE_ERROR, "apc_mmap: Failed to mmap %zu bytes. Is your apc.shm_size too large?", size); } @@ -127,13 +110,6 @@ void apc_unmap(apc_segment_t *segment) if (munmap(segment->shmaddr, segment->size) < 0) { apc_warning("apc_unmap: munmap failed"); } - -#ifdef APC_MEMPROTECT - if (segment->roaddr && munmap(segment->roaddr, segment->size) < 0) { - apc_warning("apc_unmap: munmap failed"); - } -#endif - } #endif diff --git a/apc_shm.c b/apc_shm.c index 132c9aaf..7b232d0c 100644 --- a/apc_shm.c +++ b/apc_shm.c @@ -73,14 +73,6 @@ apc_segment_t apc_shm_attach(int shmid, size_t size) zend_error_noreturn(E_CORE_ERROR, "apc_shm_attach: shmat failed:"); } -#ifdef APC_MEMPROTECT - - if ((zend_long)(segment.roaddr = shmat(shmid, 0, SHM_RDONLY)) == -1) { - segment.roaddr = NULL; - } - -#endif - segment.size = size; /* @@ -96,12 +88,6 @@ void apc_shm_detach(apc_segment_t* segment) if (shmdt(segment->shmaddr) < 0) { apc_warning("apc_shm_detach: shmdt failed:"); } - -#ifdef APC_MEMPROTECT - if (segment->roaddr && shmdt(segment->roaddr) < 0) { - apc_warning("apc_shm_detach: shmdt failed:"); - } -#endif } /* diff --git a/apc_sma.c b/apc_sma.c index 102b6eb8..986f17ae 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -56,7 +56,6 @@ struct sma_header_t { #define SMA_HDR(sma, i) ((sma_header_t*)((sma->segs[i]).shmaddr)) #define SMA_ADDR(sma, i) ((char*)(SMA_HDR(sma, i))) -#define SMA_RO(sma, i) ((char*)(sma->segs[i]).roaddr) #define SMA_LCK(sma, i) ((SMA_HDR(sma, i))->sma_lock) #define SMA_CREATE_LOCK APC_CREATE_MUTEX @@ -492,63 +491,6 @@ PHP_APCU_API void apc_sma_free(apc_sma_t* sma, void* p) { apc_error("apc_sma_free: could not locate address %p", p); } -#ifdef APC_MEMPROTECT -PHP_APCU_API void* apc_sma_protect(apc_sma_t* sma, void* p) { - unsigned int i = 0; - size_t offset; - - if (p == NULL) { - return NULL; - } - - if(SMA_RO(sma, sma->last) == NULL) return p; - - offset = (size_t)((char *)p - SMA_ADDR(sma, sma->last)); - - if(p >= (void*)SMA_ADDR(sma, sma->last) && offset < sma->size) { - return SMA_RO(sma, sma->last) + offset; - } - - for (i = 0; i < sma->num; i++) { - offset = (size_t)((char *)p - SMA_ADDR(sma, i)); - if (p >= (void*)SMA_ADDR(sma, i) && offset < sma->size) { - return SMA_RO(sma, i) + offset; - } - } - - return NULL; -} - -PHP_APCU_API void* apc_sma_unprotect(apc_sma_t* sma, void* p){ - unsigned int i = 0; - size_t offset; - - if (p == NULL) { - return NULL; - } - - if(SMA_RO(sma, sma->last) == NULL) return p; - - offset = (size_t)((char *)p - SMA_RO(sma, sma->last)); - - if(p >= (void*)SMA_RO(sma, sma->last) && offset < sma->size) { - return SMA_ADDR(sma, sma->last) + offset; - } - - for (i = 0; i < sma->num; i++) { - offset = (size_t)((char *)p - SMA_RO(sma, i)); - if (p >= (void*)SMA_RO(sma, i) && offset < sma->size) { - return SMA_ADDR(sma, i) + offset; - } - } - - return NULL; -} -#else -PHP_APCU_API void* apc_sma_protect(apc_sma_t* sma, void *p) { return p; } -PHP_APCU_API void* apc_sma_unprotect(apc_sma_t* sma, void *p) { return p; } -#endif - PHP_APCU_API apc_sma_info_t *apc_sma_info(apc_sma_t* sma, zend_bool limited) { apc_sma_info_t *info; apc_sma_link_t **link; diff --git a/apc_sma.h b/apc_sma.h index f2c680d9..d0dcf8a8 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -39,9 +39,6 @@ typedef struct _apc_segment_t { size_t size; /* size of this segment */ void* shmaddr; /* address of shared memory */ -#ifdef APC_MEMPROTECT - void* roaddr; /* read only (mprotect'd) address */ -#endif } apc_segment_t; /* }}} */ /* {{{ struct definition: apc_sma_link_t */ @@ -113,16 +110,6 @@ PHP_APCU_API void *apc_sma_malloc_ex( */ PHP_APCU_API void apc_sma_free(apc_sma_t* sma, void* p); -/* -* apc_sma_api_protect will protect p (which should be a pointer to a block allocated from sma) -*/ -PHP_APCU_API void* apc_sma_protect(apc_sma_t* sma, void* p); - -/* -* apc_sma_api_protect will uprotect p (which should be a pointer to a block allocated from sma) -*/ -PHP_APCU_API void* apc_sma_unprotect(apc_sma_t* sma, void *p); - /* * apc_sma_api_info returns information about the allocator */ From 4e4183608f074c2f6e21adaea46a2ab316dd09bd Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Fri, 4 Apr 2025 20:22:56 +0200 Subject: [PATCH 047/101] Cleanup mmap and shm implementation Better separation of the sma layer and the mmap / shm implementation. --- apc_mmap.c | 33 ++++++++++++++------------------- apc_mmap.h | 5 ++--- apc_shm.c | 19 +++++++++---------- apc_shm.h | 8 ++------ apc_sma.c | 15 ++++----------- 5 files changed, 31 insertions(+), 49 deletions(-) diff --git a/apc_mmap.c b/apc_mmap.c index 615f0ea5..ad739818 100644 --- a/apc_mmap.c +++ b/apc_mmap.c @@ -51,32 +51,29 @@ # define MAP_ANON MAP_ANONYMOUS #endif -apc_segment_t apc_mmap(char *file_mask, size_t size) +void *apc_mmap(char *file_mask, size_t size) { - apc_segment_t segment; - + void *shmaddr; int fd = -1; int flags = MAP_SHARED | MAP_NOSYNC; /* If no filename was provided, do an anonymous mmap */ - if(!file_mask || (file_mask && !strlen(file_mask))) { + if (!file_mask || (file_mask && !strlen(file_mask))) { #if !defined(MAP_ANON) zend_error_noreturn(E_CORE_ERROR, "Anonymous mmap does not appear to be available on this system (MAP_ANON/MAP_ANONYMOUS). Please see the apc.mmap_file_mask INI option."); #else fd = -1; flags = MAP_SHARED | MAP_ANON; #endif - } else if(!strcmp(file_mask,"/dev/zero")) { + } else if (!strcmp(file_mask,"/dev/zero")) { fd = open("/dev/zero", O_RDWR, S_IRUSR | S_IWUSR); - if(fd == -1) { + if (fd == -1) { zend_error_noreturn(E_CORE_ERROR, "apc_mmap: open on /dev/zero failed"); } } else { - /* - * Otherwise we do a normal filesystem mmap - */ + /* Otherwise we do a normal filesystem mmap */ fd = mkstemp(file_mask); - if(fd == -1) { + if (fd == -1) { zend_error_noreturn(E_CORE_ERROR, "apc_mmap: mkstemp on %s failed", file_mask); } if (ftruncate(fd, size) < 0) { @@ -87,27 +84,25 @@ apc_segment_t apc_mmap(char *file_mask, size_t size) unlink(file_mask); } - segment.shmaddr = (void *)mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0); - segment.size = size; + shmaddr = (void *)mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0); - if ((long)segment.shmaddr == -1) { + if ((long)shmaddr == -1) { zend_error_noreturn(E_CORE_ERROR, "apc_mmap: Failed to mmap %zu bytes. Is your apc.shm_size too large?", size); } #ifdef MADV_HUGEPAGE - /* enable transparent huge pages to reduce TLB misses (Linux - only) */ - madvise(segment.shmaddr, size, MADV_HUGEPAGE); + /* enable transparent huge pages to reduce TLB misses (Linux only) */ + madvise(shmaddr, size, MADV_HUGEPAGE); #endif if (fd != -1) close(fd); - return segment; + return shmaddr; } -void apc_unmap(apc_segment_t *segment) +void apc_unmap(void *shmaddr, size_t size) { - if (munmap(segment->shmaddr, segment->size) < 0) { + if (munmap(shmaddr, size) < 0) { apc_warning("apc_unmap: munmap failed"); } } diff --git a/apc_mmap.h b/apc_mmap.h index 30eec9f2..142a6e55 100644 --- a/apc_mmap.h +++ b/apc_mmap.h @@ -31,13 +31,12 @@ #include #include "apc.h" -#include "apc_sma.h" /* Wrapper functions for shared memory mapped files */ #ifdef APC_MMAP -apc_segment_t apc_mmap(char *file_mask, size_t size); -void apc_unmap(apc_segment_t* segment); +void *apc_mmap(char *file_mask, size_t size); +void apc_unmap(void *shmaddr, size_t size); #endif #endif diff --git a/apc_shm.c b/apc_shm.c index 7b232d0c..f89311cf 100644 --- a/apc_shm.c +++ b/apc_shm.c @@ -45,7 +45,7 @@ # define SHM_A 0222 /* write permission */ #endif -int apc_shm_create(int proj, size_t size) +static int apc_shm_create(size_t size) { int shmid; /* shared memory id */ int oflag; /* permissions on shm */ @@ -59,33 +59,32 @@ int apc_shm_create(int proj, size_t size) return shmid; } -void apc_shm_destroy(int shmid) +static void apc_shm_destroy(int shmid) { /* we expect this call to fail often, so we do not check */ shmctl(shmid, IPC_RMID, 0); } -apc_segment_t apc_shm_attach(int shmid, size_t size) +void *apc_shm_attach(size_t size) { - apc_segment_t segment; /* shm segment */ + void *shmaddr; + int shmid = apc_shm_create(size); - if ((zend_long)(segment.shmaddr = shmat(shmid, 0, 0)) == -1) { + if ((zend_long)(shmaddr = shmat(shmid, 0, 0)) == -1) { zend_error_noreturn(E_CORE_ERROR, "apc_shm_attach: shmat failed:"); } - segment.size = size; - /* * We set the shmid for removal immediately after attaching to it. The * segment won't disappear until all processes have detached from it. */ apc_shm_destroy(shmid); - return segment; + return shmaddr; } -void apc_shm_detach(apc_segment_t* segment) +void apc_shm_detach(void *shmaddr) { - if (shmdt(segment->shmaddr) < 0) { + if (shmdt(shmaddr) < 0) { apc_warning("apc_shm_detach: shmdt failed:"); } } diff --git a/apc_shm.h b/apc_shm.h index 6dc5acdd..e850cabd 100644 --- a/apc_shm.h +++ b/apc_shm.h @@ -33,14 +33,10 @@ #include #endif -#include "apc_sma.h" - /* Wrapper functions for unix shared memory */ -extern int apc_shm_create(int proj, size_t size); -extern void apc_shm_destroy(int shmid); -extern apc_segment_t apc_shm_attach(int shmid, size_t size); -extern void apc_shm_detach(apc_segment_t* segment); +void *apc_shm_attach(size_t size); +void apc_shm_detach(void *shmaddr); #endif diff --git a/apc_sma.c b/apc_sma.c index 986f17ae..13e60abc 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -325,18 +325,11 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex void* shmaddr; #ifdef APC_MMAP - sma->segs[i] = apc_mmap(mask, sma->size); + sma->segs[i].shmaddr = apc_mmap(mask, sma->size); if(sma->num != 1) memcpy(&mask[strlen(mask)-6], "XXXXXX", 6); #else - { - int j = apc_shm_create(i, sma->size); -#ifdef PHP_WIN32 - /* TODO remove the line below after 7.1 EOL. */ - SetLastError(0); -#endif - sma->segs[i] = apc_shm_attach(j, sma->size); - } + sma->segs[i].shmaddr = apc_shm_attach(sma->size); #endif sma->segs[i].size = sma->size; @@ -389,9 +382,9 @@ PHP_APCU_API void apc_sma_detach(apc_sma_t* sma) { for (i = 0; i < sma->num; i++) { #ifdef APC_MMAP - apc_unmap(&sma->segs[i]); + apc_unmap(sma->segs[i].shmaddr, sma->segs[i].size); #else - apc_shm_detach(&sma->segs[i]); + apc_shm_detach(sma->segs[i].shmaddr); #endif } From 3f28ba828549e6a14a062d478690f41e27493646 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Sat, 5 Apr 2025 18:43:18 +0200 Subject: [PATCH 048/101] Remove unused debug code from sma layer The removed code has been commented out with #if 0 since 2013-03-11 (commit 70aabadc). It has been dead code since the beginning of apcu. --- apc_sma.c | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/apc_sma.c b/apc_sma.c index 13e60abc..839096ab 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -63,14 +63,6 @@ struct sma_header_t { #define SMA_LOCK(sma, i) APC_MUTEX_LOCK(&SMA_LCK(sma, i)) #define SMA_UNLOCK(sma, i) APC_MUTEX_UNLOCK(&SMA_LCK(sma, i)) -#if 0 -/* global counter for identifying blocks - * Technically it is possible to do the same - * using offsets, but double allocations of the - * same offset can happen. */ -static volatile size_t block_id = 0; -#endif - typedef struct block_t block_t; struct block_t { size_t size; /* size of this block */ @@ -80,9 +72,6 @@ struct block_t { #ifdef APC_SMA_CANARIES size_t canary; /* canary to check for memory overwrites */ #endif -#if 0 - size_t id; /* identifier for the memory block */ -#endif }; /* The macros BLOCKAT and OFFSET are used for convenience throughout this @@ -203,9 +192,6 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t nxt->fprev = cur->fprev; BLOCKAT(nxt->fnext)->fprev = OFFSET(nxt); BLOCKAT(nxt->fprev)->fnext = OFFSET(nxt); -#if 0 - nxt->id = -1; -#endif } cur->fnext = 0; @@ -215,11 +201,6 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t SET_CANARY(cur); -#if 0 - cur->id = ++block_id; - fprintf(stderr, "allocate(realsize=%d,size=%d,id=%d)\n", (int)(size), (int)(cur->size), cur->id); -#endif - return OFFSET(cur) + block_header_size; } /* }}} */ @@ -265,11 +246,6 @@ static APC_HOTSPOT size_t sma_deallocate(void* shmaddr, size_t offset) cur->size += nxt->size; CHECK_CANARY(nxt); - -#if 0 - nxt->id = -1; /* assert this or set it ? */ -#endif - RESET_CANARY(nxt); } @@ -347,27 +323,20 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex first->fprev = 0; first->prev_size = 0; SET_CANARY(first); -#if 0 - first->id = -1; -#endif + empty = BLOCKAT(first->fnext); empty->size = header->avail - ALIGNWORD(sizeof(block_t)); empty->fnext = OFFSET(empty) + empty->size; empty->fprev = ALIGNWORD(sizeof(sma_header_t)); empty->prev_size = 0; SET_CANARY(empty); -#if 0 - empty->id = -1; -#endif + last = BLOCKAT(empty->fnext); last->size = 0; last->fnext = 0; last->fprev = OFFSET(empty); last->prev_size = empty->size; SET_CANARY(last); -#if 0 - last->id = -1; -#endif } } From c7c811f8b514f1aebb4fe75f0298dfdc36bdc9f5 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Sun, 6 Apr 2025 20:03:09 +0200 Subject: [PATCH 049/101] Remove unnecessary variables from apc_cache_t and apc_cache_header_t The "state" variable was unused and shmaddr pointed to the same address as header. --- TECHNOTES.txt | 2 -- apc_cache.c | 15 ++++++--------- apc_cache.h | 8 +++----- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/TECHNOTES.txt b/TECHNOTES.txt index a48e1899..8e917608 100644 --- a/TECHNOTES.txt +++ b/TECHNOTES.txt @@ -148,7 +148,6 @@ form of a quick-start guide to start hacking on APCu. /* {{{ struct definition: apc_cache_t */ typedef struct _apc_cache_t { - void* shmaddr; /* process (local) address of shared cache */ apc_cache_header_t* header; /* cache header (stored in SHM) */ apc_cache_entry_t** slots; /* array of cache slots (stored in SHM) */ apc_sma_t* sma; /* shared memory allocator */ @@ -176,7 +175,6 @@ form of a quick-start guide to start hacking on APCu. zend_long nentries; /* entry count */ zend_long mem_size; /* used */ time_t stime; /* start time */ - unsigned short state; /* cache state */ apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */ apc_cache_entry_t *gc; /* gc list */ } apc_cache_header_t; /* }}} */ diff --git a/apc_cache.c b/apc_cache.c index f6822b3f..05571619 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -295,32 +295,29 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser cache = pemalloc(sizeof(apc_cache_t), 1); /* calculate cache size for shm allocation */ - cache_size = sizeof(apc_cache_header_t) + nslots*sizeof(apc_cache_entry_t *); + cache_size = sizeof(apc_cache_header_t) + nslots * sizeof(apc_cache_entry_t *); /* allocate shm */ - cache->shmaddr = apc_sma_malloc(sma, cache_size); + cache->header = apc_sma_malloc(sma, cache_size); - if (!cache->shmaddr) { + if (!cache->header) { zend_error_noreturn(E_CORE_ERROR, "Unable to allocate " ZEND_LONG_FMT " bytes of shared memory for cache structures. Either apc.shm_size is too small or apc.entries_hint too large", cache_size); return NULL; } /* zero cache header and hash slots */ - memset(cache->shmaddr, 0, cache_size); - - /* set default header */ - cache->header = (apc_cache_header_t*) cache->shmaddr; + memset(cache->header, 0, cache_size); + /* set header values */ cache->header->nhits = 0; cache->header->nmisses = 0; cache->header->nentries = 0; cache->header->nexpunges = 0; cache->header->gc = NULL; cache->header->stime = time(NULL); - cache->header->state = 0; /* set cache options */ - cache->slots = (apc_cache_entry_t **) (((char*) cache->shmaddr) + sizeof(apc_cache_header_t)); + cache->slots = (apc_cache_entry_t **) (((char*) cache->header) + sizeof(apc_cache_header_t)); cache->sma = sma; cache->serializer = serializer; cache->nslots = nslots; diff --git a/apc_cache.h b/apc_cache.h index a314b5e0..39983410 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -74,22 +74,20 @@ typedef struct _apc_cache_header_t { zend_long nentries; /* entry count */ zend_long mem_size; /* used */ time_t stime; /* start time */ - unsigned short state; /* cache state */ apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */ apc_cache_entry_t *gc; /* gc list */ } apc_cache_header_t; /* }}} */ /* {{{ struct definition: apc_cache_t */ typedef struct _apc_cache_t { - void* shmaddr; /* process (local) address of shared cache */ apc_cache_header_t* header; /* cache header (stored in SHM) */ apc_cache_entry_t** slots; /* array of cache slots (stored in SHM) */ apc_sma_t* sma; /* shared memory allocator */ apc_serializer_t* serializer; /* serializer */ size_t nslots; /* number of slots in cache */ - zend_long gc_ttl; /* maximum time on GC list for a entry */ - zend_long ttl; /* if slot is needed and entry's access time is older than this ttl, remove it */ - zend_long smart; /* smart parameter for gc */ + zend_long gc_ttl; /* maximum time on GC list for a entry */ + zend_long ttl; /* if slot is needed and entry's access time is older than this ttl, remove it */ + zend_long smart; /* smart parameter for gc */ zend_bool defend; /* defense parameter for runtime */ } apc_cache_t; /* }}} */ From 1218ed2043e5393d3847b1f9c34739be7960f49a Mon Sep 17 00:00:00 2001 From: Arndt Kaiser <42686074+madmajestro@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:38:31 +0200 Subject: [PATCH 050/101] Unify shmaddr and header in sma layer The variables shmaddr and header were often used simultaneously in the same context, although both always pointed to the same memory address. This was because the BLOCKAT / OFFSET macros expected shmaddr to be present and header was used to access data of the header struct. Now the variable smaheader is used for both. --- TECHNOTES.txt | 14 +++++------ apc_sma.c | 65 ++++++++++++++++++++------------------------------- 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/TECHNOTES.txt b/TECHNOTES.txt index 8e917608..53782af6 100644 --- a/TECHNOTES.txt +++ b/TECHNOTES.txt @@ -58,12 +58,12 @@ form of a quick-start guide to start hacking on APCu. ( which you can obtain in any compilation unit with apc_sma_api_extern(apc_sma) ) At this point, we have a completely useless 32MB chunk of memory at our disposal, before - it can be used, an apc_cache_header_t is initialized at the beginning of the region of + it can be used, a sma_header_t is initialized at the beginning of the region of mmapp'ed memory. - The header serves as a place to store, among other things, statistical information and a lock. + The smaheader serves as a place to store, among other things, statistical information and a lock. - Immediately after the header comes a zero sized block, immediately after that a single + Immediately after the smaheader comes a zero sized block, immediately after that a single block with a size equal to the remaining size of the shared memory. At this point, the shared memory looks like this: @@ -87,13 +87,13 @@ form of a quick-start guide to start hacking on APCu. The BLOCKAT macro turns an offset into an actual address for you: - #define BLOCKAT(offset) ((block_t*)((char *)shmaddr + offset)) + #define BLOCKAT(offset) ((block_t*)((char *)smaheader + offset)) - where shmaddr = sma->shaddrs[0] + where smaheader = sma->shaddrs[0] And the OFFSET macro goes the other way: - #define OFFSET(block) ((int)(((char*)block) - (char*)shmaddr)) + #define OFFSET(block) ((int)(((char*)block) - (char*)smaheader)) Allocating a block (`sma_allocate`) walks through the linked list of blocks until it finds one that is >= to the requested size. The first call to allocate will hit the second block. We then @@ -110,7 +110,7 @@ form of a quick-start guide to start hacking on APCu. | header | block |------>| block | +--------+-------+ +-------------------------+ - And header->avail along with block->size of the remaining large + And smaheader->avail along with block->size of the remaining large block are updated accordingly. The arrow there representing the link which now points to a block with an offset further along in the segment. diff --git a/apc_sma.c b/apc_sma.c index 839096ab..bcf69a61 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -75,11 +75,10 @@ struct block_t { }; /* The macros BLOCKAT and OFFSET are used for convenience throughout this - * module. Both assume the presence of a variable shmaddr that points to the + * module. Both assume the presence of a variable smaheader that points to the * beginning of the shared memory segment in question. */ - -#define BLOCKAT(offset) ((block_t*)((char *)shmaddr + offset)) -#define OFFSET(block) ((size_t)(((char*)block) - (char*)shmaddr)) +#define BLOCKAT(offset) ((block_t*)((char *)smaheader + offset)) +#define OFFSET(block) ((size_t)(((char*)block) - (char*)smaheader)) /* macros for getting the next or previous sequential block */ #define NEXT_SBLOCK(block) ((block_t*)((char*)block + block->size)) @@ -101,8 +100,7 @@ struct block_t { /* How many extra blocks to check for a better fit */ #define BEST_FIT_LIMIT 3 -static inline block_t *find_block(sma_header_t *header, size_t realsize) { - void *shmaddr = header; +static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) { block_t *cur, *prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); block_t *found = NULL; uint32_t i; @@ -140,27 +138,21 @@ static inline block_t *find_block(sma_header_t *header, size_t realsize) { } /* {{{ sma_allocate: tries to allocate at least size bytes in a segment */ -static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t min_block_size, size_t *allocated) +static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size, size_t min_block_size, size_t *allocated) { - void* shmaddr; /* header of shared memory segment */ block_t* prv; /* block prior to working block */ block_t* cur; /* working block in list */ - size_t realsize; /* actual size of block needed, including header */ + size_t realsize; /* actual size of block needed, including block header */ size_t block_header_size = ALIGNWORD(sizeof(struct block_t)); realsize = ALIGNWORD(size + block_header_size); - /* - * First, insure that the segment contains at least realsize free bytes, - * even if they are not contiguous. - */ - shmaddr = header; - - if (header->avail < realsize) { + /* First, ensure that the segment contains at least realsize free bytes, even if they are not contiguous. */ + if (smaheader->avail < realsize) { return SIZE_MAX; } - cur = find_block(header, realsize); + cur = find_block(smaheader, realsize); if (!cur) { /* No suitable block found */ return SIZE_MAX; @@ -196,8 +188,8 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t cur->fnext = 0; - /* update the block header */ - header->avail -= cur->size; + /* update the segment header */ + smaheader->avail -= cur->size; SET_CANARY(cur); @@ -206,9 +198,8 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t /* }}} */ /* {{{ sma_deallocate: deallocates the block at the given offset */ -static APC_HOTSPOT size_t sma_deallocate(void* shmaddr, size_t offset) +static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset) { - sma_header_t* header; /* header of shared memory segment */ block_t* cur; /* the new block to insert */ block_t* prv; /* the block before cur */ block_t* nxt; /* the block after cur */ @@ -220,9 +211,8 @@ static APC_HOTSPOT size_t sma_deallocate(void* shmaddr, size_t offset) /* find position of new block in free list */ cur = BLOCKAT(offset); - /* update the block header */ - header = (sma_header_t*) shmaddr; - header->avail += cur->size; + /* update the segment header */ + smaheader->avail += cur->size; size = cur->size; if (cur->prev_size != 0) { @@ -296,9 +286,8 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex sma->segs = (apc_segment_t*) pemalloc(sma->num * sizeof(apc_segment_t), 1); for (i = 0; i < sma->num; i++) { - sma_header_t* header; - block_t *first, *empty, *last; - void* shmaddr; + sma_header_t *smaheader; + block_t *first, *empty, *last; #ifdef APC_MMAP sma->segs[i].shmaddr = apc_mmap(mask, sma->size); @@ -310,12 +299,10 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex sma->segs[i].size = sma->size; - shmaddr = sma->segs[i].shmaddr; - - header = (sma_header_t*) shmaddr; - SMA_CREATE_LOCK(&header->sma_lock); - header->segsize = sma->size; - header->avail = sma->size - ALIGNWORD(sizeof(sma_header_t)) - ALIGNWORD(sizeof(block_t)) - ALIGNWORD(sizeof(block_t)); + smaheader = (sma_header_t *) sma->segs[i].shmaddr; + SMA_CREATE_LOCK(&smaheader->sma_lock); + smaheader->segsize = sma->size; + smaheader->avail = sma->size - ALIGNWORD(sizeof(sma_header_t)) - ALIGNWORD(sizeof(block_t)) - ALIGNWORD(sizeof(block_t)); first = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); first->size = 0; @@ -325,7 +312,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex SET_CANARY(first); empty = BLOCKAT(first->fnext); - empty->size = header->avail - ALIGNWORD(sizeof(block_t)); + empty->size = smaheader->avail - ALIGNWORD(sizeof(block_t)); empty->fnext = OFFSET(empty) + empty->size; empty->fprev = ALIGNWORD(sizeof(sma_header_t)); empty->prev_size = 0; @@ -457,7 +444,6 @@ PHP_APCU_API apc_sma_info_t *apc_sma_info(apc_sma_t* sma, zend_bool limited) { apc_sma_info_t *info; apc_sma_link_t **link; int32_t i; - char *shmaddr; block_t *prv; if (!sma->initialized) { @@ -480,7 +466,7 @@ PHP_APCU_API apc_sma_info_t *apc_sma_info(apc_sma_t* sma, zend_bool limited) { /* For each segment */ for (i = 0; i < sma->num; i++) { SMA_LOCK(sma, i); - shmaddr = SMA_ADDR(sma, i); + sma_header_t *smaheader = (sma_header_t *)SMA_ADDR(sma, i); prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); link = &info->list[i]; @@ -525,8 +511,7 @@ PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma) { int32_t i; for (i = 0; i < sma->num; i++) { - sma_header_t* header = SMA_HDR(sma, i); - avail_mem += header->avail; + avail_mem += SMA_HDR(sma, i)->avail; } return avail_mem; } @@ -536,10 +521,10 @@ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { size_t realsize = ALIGNWORD(size + ALIGNWORD(sizeof(struct block_t))); for (i = 0; i < sma->num; i++) { - sma_header_t *shmaddr = SMA_HDR(sma, i); + sma_header_t *smaheader = SMA_HDR(sma, i); /* If total size of available memory is too small, we can skip the contiguous-block check */ - if (shmaddr->avail < realsize) { + if (smaheader->avail < realsize) { continue; } From e313096d46c68b9a490ccf78db8762b628397c9c Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Tue, 8 Apr 2025 21:16:23 +0200 Subject: [PATCH 051/101] Avoid different notations to calculate the size of block_t --- apc_sma.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apc_sma.c b/apc_sma.c index bcf69a61..f12a158c 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -143,7 +143,7 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size, siz block_t* prv; /* block prior to working block */ block_t* cur; /* working block in list */ size_t realsize; /* actual size of block needed, including block header */ - size_t block_header_size = ALIGNWORD(sizeof(struct block_t)); + size_t block_header_size = ALIGNWORD(sizeof(block_t)); realsize = ALIGNWORD(size + block_header_size); @@ -205,8 +205,8 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset) block_t* nxt; /* the block after cur */ size_t size; /* size of deallocated block */ - assert(offset >= ALIGNWORD(sizeof(struct block_t))); - offset -= ALIGNWORD(sizeof(struct block_t)); + assert(offset >= ALIGNWORD(sizeof(block_t))); + offset -= ALIGNWORD(sizeof(block_t)); /* find position of new block in free list */ cur = BLOCKAT(offset); @@ -263,7 +263,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex sma->initialized = 1; sma->expunge = expunge; sma->data = data; - sma->min_block_size = min_alloc_size > 0 ? ALIGNWORD(min_alloc_size + ALIGNWORD(sizeof(struct block_t))) : MINBLOCKSIZE; + sma->min_block_size = min_alloc_size > 0 ? ALIGNWORD(min_alloc_size + ALIGNWORD(sizeof(block_t))) : MINBLOCKSIZE; #ifdef APC_MMAP /* @@ -518,7 +518,7 @@ PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma) { PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { int32_t i; - size_t realsize = ALIGNWORD(size + ALIGNWORD(sizeof(struct block_t))); + size_t realsize = ALIGNWORD(size + ALIGNWORD(sizeof(block_t))); for (i = 0; i < sma->num; i++) { sma_header_t *smaheader = SMA_HDR(sma, i); From 0a21d649a3079a4f9ae345d7b64451c40000d495 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Tue, 8 Apr 2025 21:26:49 +0200 Subject: [PATCH 052/101] Use aligned size for shm to ensure alignment of last block in sma layer Prevent misalignment of the last (0-sized) block when using an unaligned size as configuration value for apc.shm_size (e.g. 1048577). --- apc_sma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apc_sma.c b/apc_sma.c index f12a158c..3cb5d3c4 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -281,7 +281,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex sma->num = num > 0 ? num : DEFAULT_NUMSEG; #endif - sma->size = size > 0 ? size : DEFAULT_SEGSIZE; + sma->size = ALIGNWORD(size > 0 ? size : DEFAULT_SEGSIZE); sma->segs = (apc_segment_t*) pemalloc(sma->num * sizeof(apc_segment_t), 1); From 0e345ca133557add0cfd767c6a3a253f76a1d019 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Thu, 10 Apr 2025 21:36:42 +0200 Subject: [PATCH 053/101] Cleanup unnecessary wrapping of apc_sma_malloc_ex() --- apc_sma.c | 16 ++++------------ apc_sma.h | 7 ------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/apc_sma.c b/apc_sma.c index 3cb5d3c4..91e0d7b7 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -138,7 +138,7 @@ static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) { } /* {{{ sma_allocate: tries to allocate at least size bytes in a segment */ -static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size, size_t min_block_size, size_t *allocated) +static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size, size_t min_block_size) { block_t* prv; /* block prior to working block */ block_t* cur; /* working block in list */ @@ -160,7 +160,6 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size, siz if (cur->size >= realsize && cur->size < (realsize + min_block_size)) { /* cur is big enough for realsize, but too small to split - unlink it */ - *(allocated) = cur->size - block_header_size; prv = BLOCKAT(cur->fprev); prv->fnext = cur->fnext; BLOCKAT(cur->fnext)->fprev = OFFSET(prv); @@ -172,7 +171,6 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size, siz oldsize = cur->size; cur->size = realsize; - *(allocated) = cur->size - block_header_size; nxt = NEXT_SBLOCK(cur); nxt->prev_size = 0; /* block is alloc'd */ nxt->size = oldsize - realsize; /* and fix the size */ @@ -347,7 +345,7 @@ PHP_APCU_API void apc_sma_detach(apc_sma_t* sma) { free(sma->segs); } -PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated) { +PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n) { size_t off; int32_t i; zend_bool nuked = 0; @@ -360,7 +358,7 @@ PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated return NULL; } - off = sma_allocate(SMA_HDR(sma, last), n, sma->min_block_size, allocated); + off = sma_allocate(SMA_HDR(sma, last), n, sma->min_block_size); if (off != SIZE_MAX) { void* p = (void *)(SMA_ADDR(sma, last) + off); @@ -382,7 +380,7 @@ PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated return NULL; } - off = sma_allocate(SMA_HDR(sma, i), n, sma->min_block_size, allocated); + off = sma_allocate(SMA_HDR(sma, i), n, sma->min_block_size); if (off != SIZE_MAX) { void* p = (void *)(SMA_ADDR(sma, i) + off); sma->last = i; @@ -405,12 +403,6 @@ PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated return NULL; } -PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n) -{ - size_t allocated; - return apc_sma_malloc_ex(sma, n, &allocated); -} - PHP_APCU_API void apc_sma_free(apc_sma_t* sma, void* p) { int32_t i; size_t offset; diff --git a/apc_sma.h b/apc_sma.h index d0dcf8a8..01f7ae8d 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -98,13 +98,6 @@ PHP_APCU_API void apc_sma_detach(apc_sma_t* sma); */ PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t size); -/* - * apc_sma_api_malloc_ex will allocate a block from the sma of the given size and - * provide the size of the actual allocation. - */ -PHP_APCU_API void *apc_sma_malloc_ex( - apc_sma_t *sma, size_t size, size_t *allocated); - /* * apc_sma_api_free will free p (which should be a pointer to a block allocated from sma) */ From 2c3f2068e71fbaba2b70472d88c06b105c83bcfe Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Tue, 15 Apr 2025 20:37:05 +0200 Subject: [PATCH 054/101] Optimize ALIGNWORD() ALIGNWORD() now redefines ZEND_MM_ALIGNED_SIZE(), which PHP itself uses to align data structures. It's better optimized and allows the compiler to generate faster code. It also makes sense to use the same alignment in APCU as in PHP. --- apc_sma.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apc_sma.h b/apc_sma.h index 01f7ae8d..7efd2382 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -129,9 +129,7 @@ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size); PHP_APCU_API void apc_sma_check_integrity(apc_sma_t* sma); /* }}} */ /* {{{ ALIGNWORD: pad up x, aligned to the system's word boundary */ -typedef union { void* p; int i; long l; double d; void (*f)(void); } apc_word_t; -#define ALIGNSIZE(x, size) ((size) * (1 + (((x)-1)/(size)))) -#define ALIGNWORD(x) ALIGNSIZE(x, sizeof(apc_word_t)) +#define ALIGNWORD(x) ZEND_MM_ALIGNED_SIZE(x) /* }}} */ #endif From a137dc5458b638891898b535438de662a5478615 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Tue, 8 Apr 2025 21:58:11 +0200 Subject: [PATCH 055/101] Fix size of initial empty block in sma layer The sum of the size of all free blocks is now in sync with smaheader->avail. Additionally, this fixes the position of the last (0-sized) block. Before this change, ALIGNWORD(sizeof(block_t)) bytes remained free at the end of the shared memory. --- apc_sma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apc_sma.c b/apc_sma.c index 91e0d7b7..52a4a118 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -310,7 +310,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex SET_CANARY(first); empty = BLOCKAT(first->fnext); - empty->size = smaheader->avail - ALIGNWORD(sizeof(block_t)); + empty->size = smaheader->avail; empty->fnext = OFFSET(empty) + empty->size; empty->fprev = ALIGNWORD(sizeof(sma_header_t)); empty->prev_size = 0; From 782f04735e6d41101b800f8f88323cf2bebb697d Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Mon, 14 Apr 2025 21:42:47 +0200 Subject: [PATCH 056/101] Update TECHNOTES to current state of sma layer The technotes now describe the currently used structure of the sma layer with the initial 3 blocks and the doubly linked list. --- TECHNOTES.txt | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/TECHNOTES.txt b/TECHNOTES.txt index 53782af6..079f3e7c 100644 --- a/TECHNOTES.txt +++ b/TECHNOTES.txt @@ -63,16 +63,17 @@ form of a quick-start guide to start hacking on APCu. The smaheader serves as a place to store, among other things, statistical information and a lock. - Immediately after the smaheader comes a zero sized block, immediately after that a single - block with a size equal to the remaining size of the shared memory. + Immediately after the smaheader come three blocks. The first and last block are 0-sized + and simplify the handling of the linked list. The block between the 0-sized blocks + contains the remaining size of the shared memory which is available for allocation. At this point, the shared memory looks like this: - +--------+--------+----------------------------------+ - | header | 0-size | shared | - +--------+--------+----------------------------------+ + +--------+--------+-----------------------------------+ + | header | 0-size | free-block | 0-size | + +--------+--------+-----------------------------------+ - The blocks are just a simple offset-based linked list (so no pointers): + The blocks are just a simple offset-based doubly linked list (so no pointers): typedef struct block_t block_t; struct block_t { @@ -95,31 +96,31 @@ form of a quick-start guide to start hacking on APCu. #define OFFSET(block) ((int)(((char*)block) - (char*)smaheader)) - Allocating a block (`sma_allocate`) walks through the linked list of blocks until it finds one that is >= - to the requested size. The first call to allocate will hit the second block. We then - chop up that block so it looks like this: + Allocating a block (`sma_allocate`) walks through the doubly linked list of blocks until it + finds one that is >= to the requested size. The first call to allocate will hit the second block. + We then chop up that block so it looks like this: - +--------+-------+-------+-------------------------+ - | header | block | block | block | - +--------+-------+-------+-------------------------+ + +--------+--------+-------+------------------+--------+ + | header | 0-size | block | free-block | 0-size | + +--------+--------+-------+------------------+--------+ - Then we unlink that block from the linked list so it won't show up - as an available block on the next allocate. So we actually have: + Then we unlink that block from the doubly linked list so it won't show up + as an available block on the next allocate. So we actually have: - +--------+-------+ +-------------------------+ - | header | block |------>| block | - +--------+-------+ +-------------------------+ + +--------+--------+ +------------------+--------+ + | header | 0-size |<----->| free-block | 0-size | + +--------+--------+ +------------------+--------+ And smaheader->avail along with block->size of the remaining large - block are updated accordingly. The arrow there representing the + free block are updated accordingly. The arrow there represents the link which now points to a block with an offset further along in the segment. - When the block is freed the steps are basically just reversed. + When the block is freed the steps are basically just reversed. The block is put back and then the deallocate code looks at the block before and after to see - if the block immediately before and after are free and if so the blocks are combined. So you never - have 2 free blocks next to each other, apart from at the front with that - 0-sized dummy block. This mostly prevents fragmentation. + if the block immediately before and after are free and if so the blocks are combined. So you never + have 2 free blocks next to each other, apart from the 0-sized dummy blocks. + This mostly prevents fragmentation. Block start pointers are aligned to the system's word boundary (usually 8 bytes) with the `ALIGNWORD` macro. From 29e58f04ee934afc03398412626aec400fc25911 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Wed, 23 Apr 2025 22:29:02 +0200 Subject: [PATCH 057/101] Drop support for multiple shared memory segments The support for multiple shared memory segments is not necessary these days, as systems that suffered from shared memory size limitations now use mmap(), which is unaffected by such limitations. In addition, this simplifies the sma layer and makes it easier to refactor the cache layer so that the cache is relocatable. --- apc_globals.h | 3 +- apc_sma.c | 296 ++++++++++++++------------------------- apc_sma.h | 26 +--- php_apc.c | 60 +++----- tests/apcu_sma_info.phpt | 1 - 5 files changed, 131 insertions(+), 255 deletions(-) diff --git a/apc_globals.h b/apc_globals.h index 8ad84a30..0eb1da1f 100644 --- a/apc_globals.h +++ b/apc_globals.h @@ -37,8 +37,7 @@ ZEND_BEGIN_MODULE_GLOBALS(apcu) /* configuration parameters */ zend_bool enabled; /* if true, apc is enabled (defaults to true) */ - zend_long shm_segments; /* number of shared memory segments to use */ - zend_long shm_size; /* size of each shared memory segment (in MB) */ + zend_long shm_size; /* size of the shared memory segment (in MB) */ zend_long entries_hint; /* hint at the number of entries expected */ zend_long gc_ttl; /* parameter to apc_cache_create */ zend_long ttl; /* parameter to apc_cache_create */ diff --git a/apc_sma.c b/apc_sma.c index 52a4a118..3559c545 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -43,25 +43,23 @@ # define APC_SMA_CANARIES 1 #endif -enum { - DEFAULT_NUMSEG=1, - DEFAULT_SEGSIZE=30*1024*1024 }; - typedef struct sma_header_t sma_header_t; struct sma_header_t { - apc_mutex_t sma_lock; /* segment lock */ - size_t segsize; /* size of entire segment */ + apc_mutex_t sma_lock; /* segment lock */ + size_t min_block_size; /* expected minimum size of allocated blocks */ size_t avail; /* bytes available (not necessarily contiguous) */ }; -#define SMA_HDR(sma, i) ((sma_header_t*)((sma->segs[i]).shmaddr)) -#define SMA_ADDR(sma, i) ((char*)(SMA_HDR(sma, i))) -#define SMA_LCK(sma, i) ((SMA_HDR(sma, i))->sma_lock) +#define SMA_DEFAULT_SEGSIZE (30*1024*1024) + +#define SMA_HDR(sma) ((sma_header_t*)sma->shmaddr) +#define SMA_ADDR(sma) ((char *)sma->shmaddr) +#define SMA_LCK(sma) ((SMA_HDR(sma))->sma_lock) #define SMA_CREATE_LOCK APC_CREATE_MUTEX #define SMA_DESTROY_LOCK APC_DESTROY_MUTEX -#define SMA_LOCK(sma, i) APC_MUTEX_LOCK(&SMA_LCK(sma, i)) -#define SMA_UNLOCK(sma, i) APC_MUTEX_UNLOCK(&SMA_LCK(sma, i)) +#define SMA_LOCK(sma) APC_MUTEX_LOCK(&SMA_LCK(sma)) +#define SMA_UNLOCK(sma) APC_MUTEX_UNLOCK(&SMA_LCK(sma)) typedef struct block_t block_t; struct block_t { @@ -76,7 +74,7 @@ struct block_t { /* The macros BLOCKAT and OFFSET are used for convenience throughout this * module. Both assume the presence of a variable smaheader that points to the - * beginning of the shared memory segment in question. */ + * beginning of the shared memory segment. */ #define BLOCKAT(offset) ((block_t*)((char *)smaheader + offset)) #define OFFSET(block) ((size_t)(((char*)block) - (char*)smaheader)) @@ -137,8 +135,8 @@ static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) { return found; } -/* {{{ sma_allocate: tries to allocate at least size bytes in a segment */ -static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size, size_t min_block_size) +/* {{{ sma_allocate: tries to allocate at least size bytes of shared memory */ +static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size) { block_t* prv; /* block prior to working block */ block_t* cur; /* working block in list */ @@ -158,14 +156,14 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size, siz return SIZE_MAX; } - if (cur->size >= realsize && cur->size < (realsize + min_block_size)) { + if (cur->size >= realsize && cur->size < (realsize + smaheader->min_block_size)) { /* cur is big enough for realsize, but too small to split - unlink it */ prv = BLOCKAT(cur->fprev); prv->fnext = cur->fnext; BLOCKAT(cur->fnext)->fprev = OFFSET(prv); NEXT_SBLOCK(cur)->prev_size = 0; /* block is alloc'd */ } else { - /* nextfit is too big; split it into two smaller blocks */ + /* cur is too big; split it into two smaller blocks */ block_t* nxt; /* the new block (chopped part of cur) */ size_t oldsize; /* size of cur before split */ @@ -251,9 +249,7 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset) /* }}} */ /* {{{ APC SMA API */ -PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, int32_t num, size_t size, size_t min_alloc_size, char *mask) { - int32_t i; - +PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, size_t size, size_t min_alloc_size, char *mask) { if (sma->initialized) { return; } @@ -261,138 +257,79 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex sma->initialized = 1; sma->expunge = expunge; sma->data = data; - sma->min_block_size = min_alloc_size > 0 ? ALIGNWORD(min_alloc_size + ALIGNWORD(sizeof(block_t))) : MINBLOCKSIZE; - -#ifdef APC_MMAP - /* - * I don't think multiple anonymous mmaps makes any sense - * so force sma_numseg to 1 in this case - */ - if(!mask || - (mask && !strlen(mask)) || - (mask && !strcmp(mask, "/dev/zero"))) { - sma->num = 1; - } else { - sma->num = num > 0 ? num : DEFAULT_NUMSEG; - } -#else - sma->num = num > 0 ? num : DEFAULT_NUMSEG; -#endif - - sma->size = ALIGNWORD(size > 0 ? size : DEFAULT_SEGSIZE); - - sma->segs = (apc_segment_t*) pemalloc(sma->num * sizeof(apc_segment_t), 1); - - for (i = 0; i < sma->num; i++) { - sma_header_t *smaheader; - block_t *first, *empty, *last; + sma->size = ALIGNWORD(size > 0 ? size : SMA_DEFAULT_SEGSIZE); #ifdef APC_MMAP - sma->segs[i].shmaddr = apc_mmap(mask, sma->size); - if(sma->num != 1) - memcpy(&mask[strlen(mask)-6], "XXXXXX", 6); + sma->shmaddr = apc_mmap(mask, sma->size); #else - sma->segs[i].shmaddr = apc_shm_attach(sma->size); + sma->shmaddr = apc_shm_attach(sma->size); #endif - sma->segs[i].size = sma->size; - - smaheader = (sma_header_t *) sma->segs[i].shmaddr; - SMA_CREATE_LOCK(&smaheader->sma_lock); - smaheader->segsize = sma->size; - smaheader->avail = sma->size - ALIGNWORD(sizeof(sma_header_t)) - ALIGNWORD(sizeof(block_t)) - ALIGNWORD(sizeof(block_t)); - - first = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); - first->size = 0; - first->fnext = ALIGNWORD(sizeof(sma_header_t)) + ALIGNWORD(sizeof(block_t)); - first->fprev = 0; - first->prev_size = 0; - SET_CANARY(first); - - empty = BLOCKAT(first->fnext); - empty->size = smaheader->avail; - empty->fnext = OFFSET(empty) + empty->size; - empty->fprev = ALIGNWORD(sizeof(sma_header_t)); - empty->prev_size = 0; - SET_CANARY(empty); - - last = BLOCKAT(empty->fnext); - last->size = 0; - last->fnext = 0; - last->fprev = OFFSET(empty); - last->prev_size = empty->size; - SET_CANARY(last); - } + sma_header_t *smaheader = sma->shmaddr; + SMA_CREATE_LOCK(&smaheader->sma_lock); + smaheader->min_block_size = min_alloc_size > 0 ? ALIGNWORD(min_alloc_size + ALIGNWORD(sizeof(block_t))) : MINBLOCKSIZE; + smaheader->avail = sma->size - ALIGNWORD(sizeof(sma_header_t)) - ALIGNWORD(sizeof(block_t)) - ALIGNWORD(sizeof(block_t)); + + block_t *first = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + first->size = 0; + first->fnext = ALIGNWORD(sizeof(sma_header_t)) + ALIGNWORD(sizeof(block_t)); + first->fprev = 0; + first->prev_size = 0; + SET_CANARY(first); + + block_t *empty = BLOCKAT(first->fnext); + empty->size = smaheader->avail; + empty->fnext = OFFSET(empty) + empty->size; + empty->fprev = ALIGNWORD(sizeof(sma_header_t)); + empty->prev_size = 0; + SET_CANARY(empty); + + block_t *last = BLOCKAT(empty->fnext); + last->size = 0; + last->fnext = 0; + last->fprev = OFFSET(empty); + last->prev_size = empty->size; + SET_CANARY(last); } PHP_APCU_API void apc_sma_detach(apc_sma_t* sma) { /* Important: This function should not clean up anything that's in shared memory, * only detach our process-local use of it. In particular locks cannot be destroyed * here. */ - int32_t i; assert(sma->initialized); sma->initialized = 0; - for (i = 0; i < sma->num; i++) { #ifdef APC_MMAP - apc_unmap(sma->segs[i].shmaddr, sma->segs[i].size); + apc_unmap(sma->shmaddr, sma->size); #else - apc_shm_detach(sma->segs[i].shmaddr); + apc_shm_detach(sma->shmaddr); #endif - } - - free(sma->segs); } PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n) { size_t off; - int32_t i; zend_bool nuked = 0; - int32_t last = sma->last; restart: assert(sma->initialized); - if (!SMA_LOCK(sma, last)) { + if (!SMA_LOCK(sma)) { return NULL; } - off = sma_allocate(SMA_HDR(sma, last), n, sma->min_block_size); + off = sma_allocate(SMA_HDR(sma), n); + + SMA_UNLOCK(sma); if (off != SIZE_MAX) { - void* p = (void *)(SMA_ADDR(sma, last) + off); - SMA_UNLOCK(sma, last); + void *p = (void *)(SMA_ADDR(sma) + off); #ifdef VALGRIND_MALLOCLIKE_BLOCK VALGRIND_MALLOCLIKE_BLOCK(p, n, 0, 0); #endif return p; } - SMA_UNLOCK(sma, last); - - for (i = 0; i < sma->num; i++) { - if (i == last) { - continue; - } - - if (!SMA_LOCK(sma, i)) { - return NULL; - } - - off = sma_allocate(SMA_HDR(sma, i), n, sma->min_block_size); - if (off != SIZE_MAX) { - void* p = (void *)(SMA_ADDR(sma, i) + off); - sma->last = i; - SMA_UNLOCK(sma, i); -#ifdef VALGRIND_MALLOCLIKE_BLOCK - VALGRIND_MALLOCLIKE_BLOCK(p, n, 0, 0); -#endif - return p; - } - SMA_UNLOCK(sma, i); - } - /* Expunge cache in hope of freeing up memory, but only once */ if (!nuked) { sma->expunge(*sma->data, n); @@ -404,7 +341,6 @@ PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n) { } PHP_APCU_API void apc_sma_free(apc_sma_t* sma, void* p) { - int32_t i; size_t offset; if (p == NULL) { @@ -413,129 +349,101 @@ PHP_APCU_API void apc_sma_free(apc_sma_t* sma, void* p) { assert(sma->initialized); - for (i = 0; i < sma->num; i++) { - offset = (size_t)((char *)p - SMA_ADDR(sma, i)); - if (p >= (void*)SMA_ADDR(sma, i) && offset < sma->size) { - if (!SMA_LOCK(sma, i)) { - return; - } + offset = (size_t)((char *)p - SMA_ADDR(sma)); + if (p < (void *)SMA_ADDR(sma) || offset >= sma->size) { + apc_error("apc_sma_free: could not locate address %p", p); + return; + } - sma_deallocate(SMA_HDR(sma, i), offset); - SMA_UNLOCK(sma, i); -#ifdef VALGRIND_FREELIKE_BLOCK - VALGRIND_FREELIKE_BLOCK(p, 0); -#endif - return; - } + if (!SMA_LOCK(sma)) { + return; } - apc_error("apc_sma_free: could not locate address %p", p); + sma_deallocate(SMA_HDR(sma), offset); + SMA_UNLOCK(sma); +#ifdef VALGRIND_FREELIKE_BLOCK + VALGRIND_FREELIKE_BLOCK(p, 0); +#endif } PHP_APCU_API apc_sma_info_t *apc_sma_info(apc_sma_t* sma, zend_bool limited) { - apc_sma_info_t *info; - apc_sma_link_t **link; - int32_t i; - block_t *prv; if (!sma->initialized) { return NULL; } - info = emalloc(sizeof(apc_sma_info_t)); - info->num_seg = sma->num; + apc_sma_info_t *info = emalloc(sizeof(apc_sma_info_t)); info->seg_size = sma->size - (ALIGNWORD(sizeof(sma_header_t)) + ALIGNWORD(sizeof(block_t)) + ALIGNWORD(sizeof(block_t))); - - info->list = emalloc(info->num_seg * sizeof(apc_sma_link_t *)); - for (i = 0; i < sma->num; i++) { - info->list[i] = NULL; - } + info->list = NULL; if (limited) { return info; } - /* For each segment */ - for (i = 0; i < sma->num; i++) { - SMA_LOCK(sma, i); - sma_header_t *smaheader = (sma_header_t *)SMA_ADDR(sma, i); - prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + SMA_LOCK(sma); + sma_header_t *smaheader = SMA_HDR(sma); + block_t *prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + apc_sma_link_t **link = &info->list; - link = &info->list[i]; + /* For each free block */ + while (BLOCKAT(prv->fnext)->fnext != 0) { + block_t *cur = BLOCKAT(prv->fnext); - /* For each block in this segment */ - while (BLOCKAT(prv->fnext)->fnext != 0) { - block_t *cur = BLOCKAT(prv->fnext); - - CHECK_CANARY(cur); + CHECK_CANARY(cur); - *link = emalloc(sizeof(apc_sma_link_t)); - (*link)->size = cur->size; - (*link)->offset = prv->fnext; - (*link)->next = NULL; - link = &(*link)->next; + *link = emalloc(sizeof(apc_sma_link_t)); + (*link)->size = cur->size; + (*link)->offset = prv->fnext; + (*link)->next = NULL; + link = &(*link)->next; - prv = cur; - } - SMA_UNLOCK(sma, i); + prv = cur; } + SMA_UNLOCK(sma); return info; } PHP_APCU_API void apc_sma_free_info(apc_sma_t *sma, apc_sma_info_t *info) { - int i; - - for (i = 0; i < info->num_seg; i++) { - apc_sma_link_t *p = info->list[i]; - while (p) { - apc_sma_link_t *q = p; - p = p->next; - efree(q); - } + apc_sma_link_t *p = info->list; + + while (p) { + apc_sma_link_t *q = p; + p = p->next; + efree(q); } - efree(info->list); + efree(info); } PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma) { - size_t avail_mem = 0; - int32_t i; - - for (i = 0; i < sma->num; i++) { - avail_mem += SMA_HDR(sma, i)->avail; - } - return avail_mem; + return SMA_HDR(sma)->avail; } PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { - int32_t i; size_t realsize = ALIGNWORD(size + ALIGNWORD(sizeof(block_t))); + sma_header_t *smaheader = SMA_HDR(sma); - for (i = 0; i < sma->num; i++) { - sma_header_t *smaheader = SMA_HDR(sma, i); - - /* If total size of available memory is too small, we can skip the contiguous-block check */ - if (smaheader->avail < realsize) { - continue; - } + /* If total size of available memory is too small, we can skip the contiguous-block check */ + if (smaheader->avail < realsize) { + return 0; + } - SMA_LOCK(sma, i); - block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + SMA_LOCK(sma); + block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); - /* Look for a contiguous block of memory */ - while (cur->fnext) { - cur = BLOCKAT(cur->fnext); + /* Look for a contiguous block of memory */ + while (cur->fnext) { + cur = BLOCKAT(cur->fnext); - if (cur->size >= realsize) { - SMA_UNLOCK(sma, i); - return 1; - } + if (cur->size >= realsize) { + SMA_UNLOCK(sma); + return 1; } - - SMA_UNLOCK(sma, i); } + SMA_UNLOCK(sma); + return 0; } diff --git a/apc_sma.h b/apc_sma.h index 7efd2382..472a8c72 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -35,17 +35,11 @@ #include "apc.h" -/* {{{ struct definition: apc_segment_t */ -typedef struct _apc_segment_t { - size_t size; /* size of this segment */ - void* shmaddr; /* address of shared memory */ -} apc_segment_t; /* }}} */ - /* {{{ struct definition: apc_sma_link_t */ typedef struct apc_sma_link_t apc_sma_link_t; struct apc_sma_link_t { - zend_long size; /* size of this free block */ - zend_long offset; /* offset in segment of this block */ + zend_long size; /* size of this free block */ + zend_long offset; /* offset in segment of this block */ apc_sma_link_t* next; /* link to next free block */ }; /* }}} */ @@ -53,9 +47,8 @@ struct apc_sma_link_t { /* {{{ struct definition: apc_sma_info_t */ typedef struct apc_sma_info_t apc_sma_info_t; struct apc_sma_info_t { - int num_seg; /* number of segments */ - size_t seg_size; /* segment size */ - apc_sma_link_t** list; /* one list per segment of links */ + size_t seg_size; /* segment size */ + apc_sma_link_t* list; /* list of free blocks */ }; /* }}} */ @@ -70,23 +63,18 @@ typedef struct _apc_sma_t { void** data; /* expunge data */ /* info */ - int32_t num; /* number of segments */ size_t size; /* segment size */ - int32_t last; /* last segment */ - size_t min_block_size; /* expected minimum size of allocated blocks */ - - /* segments */ - apc_segment_t *segs; /* segments */ + void *shmaddr; /* address of shm segment */ } apc_sma_t; /* }}} */ /* -* apc_sma_api_init will initialize a shared memory allocator with num segments of the given size +* apc_sma_init will initialize a shared memory allocator with the given size of shared memory * * should be called once per allocator per process */ PHP_APCU_API void apc_sma_init( apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, - int32_t num, size_t size, size_t min_alloc_size, char *mask); + size_t size, size_t min_alloc_size, char *mask); /* * apc_sma_detach will detach from shared memory and cleanup local allocations. diff --git a/php_apc.c b/php_apc.c index 0ddc71df..1ce35827 100644 --- a/php_apc.c +++ b/php_apc.c @@ -91,21 +91,6 @@ static void php_apc_init_globals(zend_apcu_globals* apcu_globals) /* {{{ PHP_INI */ -static PHP_INI_MH(OnUpdateShmSegments) /* {{{ */ -{ - zend_long shm_segments = ZEND_STRTOL(new_value->val, NULL, 10); -#ifdef APC_MMAP - if (shm_segments != 1) { - php_error_docref(NULL, E_WARNING, "apc.shm_segments setting ignored in MMAP mode"); - } - APCG(shm_segments) = 1; -#else - APCG(shm_segments) = shm_segments; -#endif - return SUCCESS; -} -/* }}} */ - static PHP_INI_MH(OnUpdateShmSize) /* {{{ */ { #if PHP_VERSION_ID >= 80200 @@ -133,7 +118,6 @@ static PHP_INI_MH(OnUpdateShmSize) /* {{{ */ PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("apc.enabled", "1", PHP_INI_SYSTEM, OnUpdateBool, enabled, zend_apcu_globals, apcu_globals) -STD_PHP_INI_ENTRY("apc.shm_segments", "1", PHP_INI_SYSTEM, OnUpdateShmSegments, shm_segments, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.shm_size", "32M", PHP_INI_SYSTEM, OnUpdateShmSize, shm_size, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.entries_hint", "4096", PHP_INI_SYSTEM, OnUpdateLong, entries_hint, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.gc_ttl", "3600", PHP_INI_SYSTEM, OnUpdateLong, gc_ttl, zend_apcu_globals, apcu_globals) @@ -247,7 +231,7 @@ static PHP_MINIT_FUNCTION(apcu) /* initialize shared memory allocator */ apc_sma_init( &apc_sma, (void **) &apc_user_cache, (apc_sma_expunge_f) apc_cache_default_expunge, - APCG(shm_segments), APCG(shm_size), APC_ENTRY_MIN_ALLOC_SIZE, mmap_file_mask); + APCG(shm_size), APC_ENTRY_MIN_ALLOC_SIZE, mmap_file_mask); REGISTER_LONG_CONSTANT(APC_SERIALIZER_CONSTANT, (zend_long)&_apc_register_serializer, CONST_PERSISTENT | CONST_CS); @@ -377,9 +361,6 @@ PHP_FUNCTION(apcu_key_info) /* {{{ proto array apcu_sma_info([bool limited]) */ PHP_FUNCTION(apcu_sma_info) { - apc_sma_info_t* info; - zval block_lists; - int i; zend_bool limited = 0; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -387,15 +368,15 @@ PHP_FUNCTION(apcu_sma_info) Z_PARAM_BOOL(limited) ZEND_PARSE_PARAMETERS_END(); - info = apc_sma_info(&apc_sma, limited); + apc_sma_info_t *info = apc_sma_info(&apc_sma, limited); if (!info) { php_error_docref(NULL, E_WARNING, "No APC SMA info available. Perhaps APC is disabled via apc.enabled?"); RETURN_FALSE; } - array_init(return_value); - add_assoc_long(return_value, "num_seg", info->num_seg); + array_init(return_value); + add_assoc_long(return_value, "num_seg", 1); add_assoc_double(return_value, "seg_size", (double)info->seg_size); add_assoc_double(return_value, "avail_mem", (double)apc_sma_get_avail_mem(&apc_sma)); @@ -404,24 +385,25 @@ PHP_FUNCTION(apcu_sma_info) return; } - array_init(&block_lists); - - for (i = 0; i < info->num_seg; i++) { - apc_sma_link_t* p; - zval list; - - array_init(&list); - for (p = info->list[i]; p != NULL; p = p->next) { - zval link; + /* generate list of free blocks */ + apc_sma_link_t *p; + zval list; + array_init(&list); + for (p = info->list; p != NULL; p = p->next) { + zval link; + + array_init(&link); + add_assoc_long(&link, "size", p->size); + add_assoc_long(&link, "offset", p->offset); + add_next_index_zval(&list, &link); + } - array_init(&link); + /* Since support for multiple shm segments has been dropped, the "block_lists" array + * only exists to ensure compatibility with existing PHP scripts. */ + zval block_lists; + array_init(&block_lists); + add_next_index_zval(&block_lists, &list); - add_assoc_long(&link, "size", p->size); - add_assoc_long(&link, "offset", p->offset); - add_next_index_zval(&list, &link); - } - add_next_index_zval(&block_lists, &list); - } add_assoc_zval(return_value, "block_lists", &block_lists); apc_sma_free_info(&apc_sma, info); } diff --git a/tests/apcu_sma_info.phpt b/tests/apcu_sma_info.phpt index bc246641..996bf726 100644 --- a/tests/apcu_sma_info.phpt +++ b/tests/apcu_sma_info.phpt @@ -3,7 +3,6 @@ Basic apcu_sma_info() test --INI-- apc.enabled=1 apc.enable_cli=1 -apc.shm_segments=1 --FILE-- Date: Tue, 29 Apr 2025 16:49:10 +0200 Subject: [PATCH 058/101] Make the key of a cache entry relocatable The key is now implemented as the last part of apc_cache_entry_t, which is a step toward making entries relocatable in the future. Furthermore, this reduces the size of an entry by the size of a pointer and removes one memcpy() of apc_cache_entry_t during insertion. --- TECHNOTES.txt | 2 +- apc_cache.c | 65 ++++++++++++++++++++++---------------------------- apc_cache.h | 5 ++-- apc_iterator.c | 8 +++---- apc_persist.c | 49 +++++++++++++++++++++++-------------- php_apc.c | 2 +- 6 files changed, 69 insertions(+), 62 deletions(-) diff --git a/TECHNOTES.txt b/TECHNOTES.txt index 079f3e7c..c9627168 100644 --- a/TECHNOTES.txt +++ b/TECHNOTES.txt @@ -258,7 +258,6 @@ form of a quick-start guide to start hacking on APCu. /* {{{ struct definition: apc_cache_entry_t */ typedef struct apc_cache_entry_t apc_cache_entry_t; struct apc_cache_entry_t { - zend_string *key; /* entry key */ zval val; /* the zval copied at store time */ apc_cache_entry_t *next; /* next entry in linked list */ zend_long ttl; /* the ttl on this specific entry */ @@ -269,6 +268,7 @@ form of a quick-start guide to start hacking on APCu. time_t dtime; /* time entry was removed from cache */ time_t atime; /* time entry was last accessed */ zend_long mem_size; /* memory used */ + zend_string key; /* entry key (MUST BE THE LAST FIELD OF THE STRUCT!) */ }; /* }}} */ diff --git a/apc_cache.c b/apc_cache.c index 05571619..7cdbdcaa 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -57,7 +57,7 @@ /* Defined in apc_persist.c */ apc_cache_entry_t *apc_persist( - apc_sma_t *sma, apc_serializer_t *serializer, const apc_cache_entry_t *orig_entry); + apc_sma_t *sma, apc_serializer_t *serializer, zend_string *key, const zval *val); zend_bool apc_unpersist(zval *dst, const zval *value, apc_serializer_t *serializer); /* {{{ make_prime */ @@ -135,9 +135,9 @@ static inline void apc_cache_hash_slot( } /* }}} */ static inline zend_bool apc_entry_key_equals(const apc_cache_entry_t *entry, zend_string *key, zend_ulong hash) { - return ZSTR_H(entry->key) == hash - && ZSTR_LEN(entry->key) == ZSTR_LEN(key) - && memcmp(ZSTR_VAL(entry->key), ZSTR_VAL(key), ZSTR_LEN(key)) == 0; + return ZSTR_H(&entry->key) == hash + && ZSTR_LEN(&entry->key) == ZSTR_LEN(key) + && memcmp(ZSTR_VAL(&entry->key), ZSTR_VAL(key), ZSTR_LEN(key)) == 0; } /* An entry is hard expired if the creation time if older than the per-entry TTL. @@ -213,7 +213,7 @@ static void apc_cache_wlocked_gc(apc_cache_t* cache) if (dead->ref_count > 0) { apc_debug( "GC cache entry '%s' was on gc-list for %lld seconds", - ZSTR_VAL(dead->key), (long long) gc_sec + ZSTR_VAL(&dead->key), (long long) gc_sec ); } @@ -334,7 +334,7 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser static inline zend_bool apc_cache_wlocked_insert( apc_cache_t *cache, apc_cache_entry_t *new_entry, zend_bool exclusive) { - zend_string *key = new_entry->key; + zend_string *key = &new_entry->key; time_t t = new_entry->ctime; /* process deleted list */ @@ -391,27 +391,38 @@ static inline zend_bool apc_cache_wlocked_insert( return 1; } -static void apc_cache_init_entry( - apc_cache_entry_t *entry, zend_string *key, const zval* val, const int32_t ttl, time_t t); +static void apc_cache_set_entry_values(apc_cache_entry_t *entry, const int32_t ttl, const time_t t) +{ + entry->ttl = ttl; + entry->next = NULL; + entry->ref_count = 0; + entry->mem_size = 0; + entry->nhits = 0; + entry->ctime = t; + entry->mtime = t; + entry->atime = t; + entry->dtime = 0; +} /* TODO This function may lead to a deadlock on expunge */ static inline zend_bool apc_cache_store_internal( apc_cache_t *cache, zend_string *key, const zval *val, const int32_t ttl, const zend_bool exclusive) { - apc_cache_entry_t tmp_entry, *entry; time_t t = apc_time(); if (apc_cache_defense(cache, key, t)) { return 0; } - /* initialize the entry for insertion */ - apc_cache_init_entry(&tmp_entry, key, val, ttl, t); - entry = apc_persist(cache->sma, cache->serializer, &tmp_entry); + /* create entry in the shared memory */ + apc_cache_entry_t *entry = apc_persist(cache->sma, cache->serializer, key, val); if (!entry) { return 0; } + /* init remaining values of the entry */ + apc_cache_set_entry_values(entry, ttl, t); + /* execute an insertion */ if (!apc_cache_wlocked_insert(cache, entry, exclusive)) { free_entry(cache, entry); @@ -497,7 +508,6 @@ static inline apc_cache_entry_t *apc_cache_rlocked_find_incref( PHP_APCU_API zend_bool apc_cache_store( apc_cache_t* cache, zend_string *key, const zval *val, const int32_t ttl, const zend_bool exclusive) { - apc_cache_entry_t tmp_entry, *entry; time_t t = apc_time(); zend_bool ret = 0; @@ -510,13 +520,15 @@ PHP_APCU_API zend_bool apc_cache_store( return 0; } - /* initialize the entry for insertion */ - apc_cache_init_entry(&tmp_entry, key, val, ttl, t); - entry = apc_persist(cache->sma, cache->serializer, &tmp_entry); + /* create entry in the shared memory */ + apc_cache_entry_t *entry = apc_persist(cache->sma, cache->serializer, key, val); if (!entry) { return 0; } + /* init remaining values of the entry */ + apc_cache_set_entry_values(entry, ttl, t); + /* execute an insertion */ if (!apc_cache_wlock(cache)) { free_entry(cache, entry); @@ -978,25 +990,6 @@ PHP_APCU_API zend_bool apc_cache_entry_fetch_zval( } /* }}} */ -/* {{{ apc_cache_make_entry */ -static void apc_cache_init_entry( - apc_cache_entry_t *entry, zend_string *key, const zval *val, const int32_t ttl, time_t t) -{ - entry->ttl = ttl; - entry->key = key; - ZVAL_COPY_VALUE(&entry->val, val); - - entry->next = NULL; - entry->ref_count = 0; - entry->mem_size = 0; - entry->nhits = 0; - entry->ctime = t; - entry->mtime = t; - entry->atime = t; - entry->dtime = 0; -} -/* }}} */ - static inline void array_add_long(zval *array, zend_string *key, zend_long lval) { zval zv; ZVAL_LONG(&zv, lval); @@ -1015,7 +1008,7 @@ static zval apc_cache_link_info(apc_cache_t *cache, apc_cache_entry_t *p) zval link, zv; array_init(&link); - ZVAL_STR(&zv, zend_string_dup(p->key, 0)); + ZVAL_STR(&zv, zend_string_dup(&p->key, 0)); zend_hash_add_new(Z_ARRVAL(link), apc_str_info, &zv); array_add_long(&link, apc_str_ttl, p->ttl); diff --git a/apc_cache.h b/apc_cache.h index 39983410..40df2770 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -49,7 +49,6 @@ struct apc_cache_slam_key_t { /* {{{ struct definition: apc_cache_entry_t */ typedef struct apc_cache_entry_t apc_cache_entry_t; struct apc_cache_entry_t { - zend_string *key; /* entry key */ zval val; /* the zval copied at store time */ apc_cache_entry_t *next; /* next entry in linked list */ zend_long ttl; /* the ttl on this specific entry */ @@ -60,6 +59,7 @@ struct apc_cache_entry_t { time_t dtime; /* time entry was removed from cache */ time_t atime; /* time entry was last accessed */ zend_long mem_size; /* memory used */ + zend_string key; /* entry key (MUST BE THE LAST FIELD OF THE STRUCT!) */ }; /* }}} */ @@ -297,7 +297,8 @@ static inline void apc_cache_runlock(apc_cache_t *cache) { } } -#define APC_ENTRY_MIN_ALLOC_SIZE (ZEND_MM_ALIGNED_SIZE(sizeof(apc_cache_entry_t)) + ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(1))) +/* APC_ENTRY_SIZE takes into account the trailing key-string + terminating 0-byte */ +#define APC_ENTRY_SIZE(key_len) (ZEND_MM_ALIGNED_SIZE(XtOffsetOf(apc_cache_entry_t, key.val) + key_len + 1)) #endif diff --git a/apc_iterator.c b/apc_iterator.c index 07bd0e4e..3d892ad7 100644 --- a/apc_iterator.c +++ b/apc_iterator.c @@ -55,7 +55,7 @@ static apc_iterator_item_t* apc_iterator_item_ctor( array_init(&item->value); ht = Z_ARRVAL(item->value); - item->key = zend_string_dup(entry->key, 0); + item->key = zend_string_dup(&entry->key, 0); if (APC_ITER_TYPE & iterator->format) { ZVAL_STR_COPY(&zv, apc_str_user); @@ -178,18 +178,18 @@ static int apc_iterator_search_match(apc_iterator_t *iterator, apc_cache_entry_t #if PHP_VERSION_ID >= 70300 rval = pcre2_match( php_pcre_pce_re(iterator->pce), - (PCRE2_SPTR) ZSTR_VAL(entry->key), ZSTR_LEN(entry->key), + (PCRE2_SPTR) ZSTR_VAL(&entry->key), ZSTR_LEN(&entry->key), 0, 0, iterator->re_match_data, php_pcre_mctx()) >= 0; #else rval = pcre_exec( iterator->pce->re, iterator->pce->extra, - ZSTR_VAL(entry->key), ZSTR_LEN(entry->key), + ZSTR_VAL(&entry->key), ZSTR_LEN(&entry->key), 0, 0, NULL, 0) >= 0; #endif } if (iterator->search_hash) { - rval = zend_hash_exists(iterator->search_hash, entry->key); + rval = zend_hash_exists(iterator->search_hash, &entry->key); } return rval; diff --git a/apc_persist.c b/apc_persist.c index 1d288fce..ce284f3a 100644 --- a/apc_persist.c +++ b/apc_persist.c @@ -86,7 +86,7 @@ static inline void apc_persist_copy_zval(apc_persist_context_t *ctxt, zval *zv) apc_persist_copy_zval_impl(ctxt, zv); } -void apc_persist_init_context(apc_persist_context_t *ctxt, apc_serializer_t *serializer) { +static void apc_persist_init_context(apc_persist_context_t *ctxt, apc_serializer_t *serializer) { ctxt->serializer = serializer; ctxt->size = 0; ctxt->memoization_needed = 0; @@ -97,7 +97,7 @@ void apc_persist_init_context(apc_persist_context_t *ctxt, apc_serializer_t *ser ctxt->alloc_cur = NULL; } -void apc_persist_destroy_context(apc_persist_context_t *ctxt) { +static void apc_persist_destroy_context(apc_persist_context_t *ctxt) { if (ctxt->memoization_needed) { zend_hash_destroy(&ctxt->already_counted); zend_hash_destroy(&ctxt->already_allocated); @@ -235,10 +235,10 @@ static zend_bool apc_persist_calc_zval(apc_persist_context_t *ctxt, const zval * } } -static zend_bool apc_persist_calc(apc_persist_context_t *ctxt, const apc_cache_entry_t *entry) { - ADD_SIZE(sizeof(apc_cache_entry_t)); - ADD_SIZE_STR(ZSTR_LEN(entry->key)); - return apc_persist_calc_zval(ctxt, &entry->val); +static zend_bool apc_persist_calc(apc_persist_context_t *ctxt, const zend_string *key, const zval *zv) { + ADD_SIZE(APC_ENTRY_SIZE(ZSTR_LEN(key))); + + return apc_persist_calc_zval(ctxt, zv); } static inline void *apc_persist_get_already_allocated(apc_persist_context_t *ctxt, void *ptr) { @@ -435,27 +435,40 @@ static void apc_persist_copy_zval_impl(apc_persist_context_t *ctxt, zval *zv) { } } -static apc_cache_entry_t *apc_persist_copy( - apc_persist_context_t *ctxt, const apc_cache_entry_t *orig_entry) { - apc_cache_entry_t *entry = COPY(orig_entry, sizeof(apc_cache_entry_t)); - entry->key = apc_persist_copy_zstr_no_add(ctxt, entry->key); +static apc_cache_entry_t *apc_persist_create_entry( + apc_persist_context_t *ctxt, zend_string *key, const zval *zv) +{ + /* Get memory for the entry (incl. key) */ + apc_cache_entry_t *entry = ALLOC(APC_ENTRY_SIZE(ZSTR_LEN(key))); + + /* Deep copy of the key */ + GC_SET_REFCOUNT(&entry->key, 1); + GC_SET_PERSISTENT_TYPE(&entry->key, IS_STRING); + ZSTR_LEN(&entry->key) = ZSTR_LEN(key); + memcpy(ZSTR_VAL(&entry->key), ZSTR_VAL(key), ZSTR_LEN(key)); + ZSTR_VAL(&entry->key)[ZSTR_LEN(key)] = '\0'; + ZSTR_H(&entry->key) = zend_string_hash_val(key); + + /* Deep copy of the value */ + ZVAL_COPY_VALUE(&entry->val, zv); apc_persist_copy_zval(ctxt, &entry->val); + return entry; } apc_cache_entry_t *apc_persist( - apc_sma_t *sma, apc_serializer_t *serializer, const apc_cache_entry_t *orig_entry) { + apc_sma_t *sma, apc_serializer_t *serializer, zend_string *key, const zval *val) { apc_persist_context_t ctxt; apc_cache_entry_t *entry; apc_persist_init_context(&ctxt, serializer); /* The top-level value should never be a reference */ - ZEND_ASSERT(Z_TYPE(orig_entry->val) != IS_REFERENCE); + ZEND_ASSERT(Z_TYPE_P(val) != IS_REFERENCE); /* If we're serializing an array using the default serializer, we will have * to keep track of potentially repeated refcounted structures. */ - if (!serializer && Z_TYPE(orig_entry->val) == IS_ARRAY) { + if (!serializer && Z_TYPE_P(val) == IS_ARRAY) { ctxt.memoization_needed = 1; zend_hash_init(&ctxt.already_counted, 0, NULL, NULL, 0); zend_hash_init(&ctxt.already_allocated, 0, NULL, NULL, 0); @@ -463,12 +476,12 @@ apc_cache_entry_t *apc_persist( /* Objects are always serialized, and arrays when a serializer is set. * Other cases are detected during apc_persist_calc(). */ - if (Z_TYPE(orig_entry->val) == IS_OBJECT - || (serializer && Z_TYPE(orig_entry->val) == IS_ARRAY)) { + if (Z_TYPE_P(val) == IS_OBJECT + || (serializer && Z_TYPE_P(val) == IS_ARRAY)) { ctxt.use_serialization = 1; } - if (!apc_persist_calc(&ctxt, orig_entry)) { + if (!apc_persist_calc(&ctxt, key, val)) { if (!ctxt.use_serialization) { apc_persist_destroy_context(&ctxt); return NULL; @@ -478,7 +491,7 @@ apc_cache_entry_t *apc_persist( apc_persist_destroy_context(&ctxt); apc_persist_init_context(&ctxt, serializer); ctxt.use_serialization = 1; - if (!apc_persist_calc(&ctxt, orig_entry)) { + if (!apc_persist_calc(&ctxt, key, val)) { apc_persist_destroy_context(&ctxt); return NULL; } @@ -490,7 +503,7 @@ apc_cache_entry_t *apc_persist( return NULL; } - entry = apc_persist_copy(&ctxt, orig_entry); + entry = apc_persist_create_entry(&ctxt, key, val); ZEND_ASSERT(ctxt.alloc_cur == ctxt.alloc + ctxt.size); entry->mem_size = ctxt.size; diff --git a/php_apc.c b/php_apc.c index 1ce35827..90c40b72 100644 --- a/php_apc.c +++ b/php_apc.c @@ -231,7 +231,7 @@ static PHP_MINIT_FUNCTION(apcu) /* initialize shared memory allocator */ apc_sma_init( &apc_sma, (void **) &apc_user_cache, (apc_sma_expunge_f) apc_cache_default_expunge, - APCG(shm_size), APC_ENTRY_MIN_ALLOC_SIZE, mmap_file_mask); + APCG(shm_size), APC_ENTRY_SIZE(0), mmap_file_mask); REGISTER_LONG_CONSTANT(APC_SERIALIZER_CONSTANT, (zend_long)&_apc_register_serializer, CONST_PERSISTENT | CONST_CS); From 19c4d568daaae2d7b742b3034625ba4f328b0378 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Thu, 1 May 2025 20:05:32 +0200 Subject: [PATCH 059/101] Fix mem_size of cache entries This fixes a bug introduced in 453a849b / #554. The size was erroneously reset to 0 by apc_cache_set_entry_values() after it has been correctly set by apc_persist(). --- apc_cache.c | 1 - 1 file changed, 1 deletion(-) diff --git a/apc_cache.c b/apc_cache.c index 7cdbdcaa..076e6c51 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -396,7 +396,6 @@ static void apc_cache_set_entry_values(apc_cache_entry_t *entry, const int32_t t entry->ttl = ttl; entry->next = NULL; entry->ref_count = 0; - entry->mem_size = 0; entry->nhits = 0; entry->ctime = t; entry->mtime = t; From 85276fa7a198d1ad81efc8da645f69f599ac8cf5 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sun, 4 May 2025 02:08:03 +0200 Subject: [PATCH 060/101] Make cache entries relocatable This makes the persistence representation of cache entries in shm position-independent by converting all pointers in an entry's zval value to offsets relative to the entry's starting address. During persistence, all (process local) pointers are converted and stored as offsets in shm. During unpersistence, the (process local) shm starting address of the entry is added to each offset before data access. This is necessary to make the whole cache relocatable or to develop defragmentation. --- apc_cache.c | 4 ++-- apc_persist.c | 63 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 076e6c51..a3af4593 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -58,7 +58,7 @@ /* Defined in apc_persist.c */ apc_cache_entry_t *apc_persist( apc_sma_t *sma, apc_serializer_t *serializer, zend_string *key, const zval *val); -zend_bool apc_unpersist(zval *dst, const zval *value, apc_serializer_t *serializer); +zend_bool apc_unpersist(zval *dst, const apc_cache_entry_t *entry, apc_serializer_t *serializer); /* {{{ make_prime */ static int const primes[] = { @@ -985,7 +985,7 @@ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key) PHP_APCU_API zend_bool apc_cache_entry_fetch_zval( apc_cache_t *cache, apc_cache_entry_t *entry, zval *dst) { - return apc_unpersist(dst, &entry->val, cache->serializer); + return apc_unpersist(dst, entry, cache->serializer); } /* }}} */ diff --git a/apc_persist.c b/apc_persist.c index ce284f3a..8f74e347 100644 --- a/apc_persist.c +++ b/apc_persist.c @@ -53,7 +53,7 @@ typedef struct _apc_persist_context_t { /* Serialized object/array string, in case there can only be one */ unsigned char *serialized_str; size_t serialized_str_len; - /* Whole SMA allocation */ + /* Address (process local) of entry in shm / Whole SMA allocation */ char *alloc; /* Current position in allocation */ char *alloc_cur; @@ -69,9 +69,21 @@ typedef struct _apc_persist_context_t { #define ALLOC(sz) apc_persist_alloc(ctxt, sz) #define COPY(val, sz) apc_persist_alloc_copy(ctxt, val, sz) +/* TO_OFF() converts a (process local) pointer from an entry (in shm) to an + * offset relative to the beginning of this entry. It expects the presence + * of a variable ctxt that points to an apc_persist_context_t. */ +#define TO_OFF(ptr) (apc_persist_compute_offset(ptr, ctxt)) + static zend_bool apc_persist_calc_zval(apc_persist_context_t *ctxt, const zval *zv); static void apc_persist_copy_zval_impl(apc_persist_context_t *ctxt, zval *zv); +static inline void *apc_persist_compute_offset(void *ptr, apc_persist_context_t *ctxt) { + /* The pointer must point to the shm area of the entry to be persisted */ + assert(((uintptr_t)ptr >= (uintptr_t)ctxt->alloc) && ((uintptr_t)ptr < ((uintptr_t)ctxt->alloc + (uintptr_t)ctxt->size))); + + return ((void *)((uintptr_t)ptr - (uintptr_t)ctxt->alloc)); +} + /* Used to reduce hash collisions when using pointers in hash tables. (#175) */ static inline zend_ulong apc_shr3(zend_ulong index) { return (index >> 3) | (index << (SIZEOF_ZEND_LONG * 8 - 3)); @@ -84,6 +96,7 @@ static inline void apc_persist_copy_zval(apc_persist_context_t *ctxt, zval *zv) } apc_persist_copy_zval_impl(ctxt, zv); + Z_PTR_P(zv) = TO_OFF(Z_PTR_P(zv)); } static void apc_persist_init_context(apc_persist_context_t *ctxt, apc_serializer_t *serializer) { @@ -368,6 +381,8 @@ static zend_array *apc_persist_copy_ht(apc_persist_context_t *ctxt, const HashTa apc_persist_copy_zval(ctxt, val); } + + ht->arPacked = TO_OFF(ht->arPacked); } else #endif { @@ -382,6 +397,7 @@ static zend_array *apc_persist_copy_ht(apc_persist_context_t *ctxt, const HashTa if (p->key) { p->key = apc_persist_copy_zstr_no_add(ctxt, p->key); + p->key = TO_OFF(p->key); ht->u.flags &= ~HASH_FLAG_STATIC_KEYS; } else if ((zend_long) p->h >= (zend_long) ht->nNextFreeElement) { ht->nNextFreeElement = p->h + 1; @@ -389,6 +405,8 @@ static zend_array *apc_persist_copy_ht(apc_persist_context_t *ctxt, const HashTa apc_persist_copy_zval(ctxt, &p->val); } + + ht->arData = TO_OFF(ht->arData); } return ht; @@ -521,21 +539,36 @@ typedef struct _apc_unpersist_context_t { zend_bool memoization_needed; /* HashTable storing already copied refcounteds. */ HashTable already_copied; + /* Address (process local) of entry in shm / Whole SMA allocation */ + char *alloc; } apc_unpersist_context_t; +/* TO_PTR() does the opposite of TO_OFF() and converts an offset stored in an entry (in shm) + * into a (process local) pointer, which can then be used to access an element of that entry. + * It expects the presence of a variable ctxt that points to an apc_unpersist_context_t. */ +#define TO_PTR(off) (apc_unpersist_compute_pointer(off, ctxt)) + static void apc_unpersist_zval_impl(apc_unpersist_context_t *ctxt, zval *zv); +static inline void *apc_unpersist_compute_pointer(void *off, apc_unpersist_context_t *ctxt) { + /* The offset must be smaller than the size of the entry (in shm) to be unpersisted */ + assert((uintptr_t)off < (uintptr_t)((apc_cache_entry_t *)ctxt->alloc)->mem_size); + + return (void *)((uintptr_t)ctxt->alloc + (uintptr_t)off); +} + static inline void apc_unpersist_zval(apc_unpersist_context_t *ctxt, zval *zv) { /* No data apart from the zval itself */ if (Z_TYPE_P(zv) < IS_STRING) { return; } + Z_PTR_P(zv) = TO_PTR(Z_PTR_P(zv)); apc_unpersist_zval_impl(ctxt, zv); } static zend_bool apc_unpersist_serialized( - zval *dst, zend_string *str, apc_serializer_t *serializer) { + apc_unpersist_context_t *ctxt, zval *dst, zend_string *str, apc_serializer_t *serializer) { apc_unserialize_t unserialize = APC_UNSERIALIZER_NAME(php); void *config = NULL; @@ -544,6 +577,7 @@ static zend_bool apc_unpersist_serialized( config = serializer->config; } + str = TO_PTR(str); if (unserialize(dst, (unsigned char *) ZSTR_VAL(str), ZSTR_LEN(str), config)) { return 1; } @@ -617,11 +651,11 @@ static zend_array *apc_unpersist_ht( #endif HT_SET_DATA_ADDR(ht, emalloc(apc_compute_ht_data_size(ht))); - memcpy(HT_GET_DATA_ADDR(ht), HT_GET_DATA_ADDR(orig_ht), HT_HASH_SIZE(ht->nTableMask)); + memcpy(HT_GET_DATA_ADDR(ht), TO_PTR(HT_GET_DATA_ADDR(orig_ht)), HT_HASH_SIZE(ht->nTableMask)); #if PHP_VERSION_ID >= 80200 if (HT_IS_PACKED(ht)) { - zval *p = ht->arPacked, *q = orig_ht->arPacked, *p_end = p + ht->nNumUsed; + zval *p = ht->arPacked, *q = TO_PTR(orig_ht->arPacked), *p_end = p + ht->nNumUsed; for (; p < p_end; p++, q++) { *p = *q; apc_unpersist_zval(ctxt, p); @@ -629,14 +663,14 @@ static zend_array *apc_unpersist_ht( } else #endif if (ht->u.flags & HASH_FLAG_STATIC_KEYS) { - Bucket *p = ht->arData, *q = orig_ht->arData, *p_end = p + ht->nNumUsed; + Bucket *p = ht->arData, *q = TO_PTR(orig_ht->arData), *p_end = p + ht->nNumUsed; for (; p < p_end; p++, q++) { /* No need to check for UNDEF, as unpersist_zval can be safely called on UNDEF */ *p = *q; apc_unpersist_zval(ctxt, &p->val); } } else { - Bucket *p = ht->arData, *q = orig_ht->arData, *p_end = p + ht->nNumUsed; + Bucket *p = ht->arData, *q = TO_PTR(orig_ht->arData), *p_end = p + ht->nNumUsed; for (; p < p_end; p++, q++) { if (Z_TYPE(q->val) == IS_UNDEF) { ZVAL_UNDEF(&p->val); @@ -646,7 +680,7 @@ static zend_array *apc_unpersist_ht( p->val = q->val; p->h = q->h; if (q->key) { - p->key = zend_string_dup(q->key, 0); + p->key = zend_string_dup(TO_PTR(q->key), 0); } else { p->key = NULL; } @@ -687,21 +721,24 @@ static void apc_unpersist_zval_impl(apc_unpersist_context_t *ctxt, zval *zv) { } } -zend_bool apc_unpersist(zval *dst, const zval *value, apc_serializer_t *serializer) { +zend_bool apc_unpersist(zval *dst, const apc_cache_entry_t *entry, apc_serializer_t *serializer) { apc_unpersist_context_t ctxt; - if (Z_TYPE_P(value) == IS_PTR) { - return apc_unpersist_serialized(dst, Z_PTR_P(value), serializer); + /* Needed to convert offsets back to pointers */ + ctxt.alloc = (char *)entry; + + if (Z_TYPE(entry->val) == IS_PTR) { + return apc_unpersist_serialized(&ctxt, dst, Z_PTR(entry->val), serializer); } ctxt.memoization_needed = 0; - ZEND_ASSERT(Z_TYPE_P(value) != IS_REFERENCE); - if (Z_TYPE_P(value) == IS_ARRAY) { + ZEND_ASSERT(Z_TYPE(entry->val) != IS_REFERENCE); + if (Z_TYPE(entry->val) == IS_ARRAY) { ctxt.memoization_needed = 1; zend_hash_init(&ctxt.already_copied, 0, NULL, NULL, 0); } - ZVAL_COPY_VALUE(dst, value); + ZVAL_COPY_VALUE(dst, &entry->val); apc_unpersist_zval(&ctxt, dst); if (ctxt.memoization_needed) { From bd7f3f599ea30fb16b772fab8e5918f29666740e Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Wed, 7 May 2025 22:14:20 +0200 Subject: [PATCH 061/101] Compute the number of hash slots instead of using a fix default value The new default behavior is to use 512 slots per MB of shared memory, which keeps the hash table size < 0.4% of shm, which seems negligible. This should improve the default behavior, as it improves apcu's performance with many entries, and the hash table size now scales with the size of shared memory. Previously, a fixed value of 4096 at 32 MB shm was used as default, which corresponds to 128 slots per MB. --- apc_cache.c | 4 ++-- php_apc.c | 2 +- tests/iterator_006.phpt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index a3af4593..2fe38f9c 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -288,8 +288,8 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser zend_long cache_size; size_t nslots; - /* calculate number of slots */ - nslots = make_prime(size_hint > 0 ? size_hint : 2000); + /* calculate number of slots. Default: 512 slots per MB of shared memory */ + nslots = make_prime(size_hint > 0 ? (size_t)size_hint : sma->size / 2048); /* allocate pointer by normal means */ cache = pemalloc(sizeof(apc_cache_t), 1); diff --git a/php_apc.c b/php_apc.c index 90c40b72..3aff354a 100644 --- a/php_apc.c +++ b/php_apc.c @@ -119,7 +119,7 @@ static PHP_INI_MH(OnUpdateShmSize) /* {{{ */ PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("apc.enabled", "1", PHP_INI_SYSTEM, OnUpdateBool, enabled, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.shm_size", "32M", PHP_INI_SYSTEM, OnUpdateShmSize, shm_size, zend_apcu_globals, apcu_globals) -STD_PHP_INI_ENTRY("apc.entries_hint", "4096", PHP_INI_SYSTEM, OnUpdateLong, entries_hint, zend_apcu_globals, apcu_globals) +STD_PHP_INI_ENTRY("apc.entries_hint", "0", PHP_INI_SYSTEM, OnUpdateLong, entries_hint, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.gc_ttl", "3600", PHP_INI_SYSTEM, OnUpdateLong, gc_ttl, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.ttl", "0", PHP_INI_SYSTEM, OnUpdateLong, ttl, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.smart", "0", PHP_INI_SYSTEM, OnUpdateLong, smart, zend_apcu_globals, apcu_globals) diff --git a/tests/iterator_006.phpt b/tests/iterator_006.phpt index d98b0383..b86831e9 100644 --- a/tests/iterator_006.phpt +++ b/tests/iterator_006.phpt @@ -5,7 +5,7 @@ APC: APCIterator formats --INI-- apc.enabled=1 apc.enable_cli=1 -apc.user_entries_hint=4096 +apc.entries_hint=4096 --FILE-- Date: Tue, 6 May 2025 20:57:18 +0200 Subject: [PATCH 062/101] Make cache relocatable This makes the cache position-independent by converting all pointers to entries into offsets relative to the starting address of the cache header in the shared memory segment. This is a further step to enable the use of APCU with processes that access the shared memory segment via different starting addresses. --- TECHNOTES.txt | 24 +++--- apc_cache.c | 210 +++++++++++++++++++++++++------------------------ apc_cache.h | 12 ++- apc_iterator.c | 48 ++++++----- 4 files changed, 156 insertions(+), 138 deletions(-) diff --git a/TECHNOTES.txt b/TECHNOTES.txt index c9627168..e7cf642b 100644 --- a/TECHNOTES.txt +++ b/TECHNOTES.txt @@ -150,7 +150,7 @@ form of a quick-start guide to start hacking on APCu. /* {{{ struct definition: apc_cache_t */ typedef struct _apc_cache_t { apc_cache_header_t* header; /* cache header (stored in SHM) */ - apc_cache_entry_t** slots; /* array of cache slots (stored in SHM) */ + uintptr_t* slots; /* array of cache slots (stored in SHM) */ apc_sma_t* sma; /* shared memory allocator */ apc_serializer_t* serializer; /* serializer */ size_t nslots; /* number of slots in cache */ @@ -168,16 +168,16 @@ form of a quick-start guide to start hacking on APCu. /* {{{ struct definition: apc_cache_header_t Any values that must be shared among processes should go in here. */ typedef struct _apc_cache_header_t { - apc_lock_t lock; /* header lock */ - zend_long nhits; /* hit count */ - zend_long nmisses; /* miss count */ - zend_long ninserts; /* insert count */ - zend_long nexpunges; /* expunge count */ - zend_long nentries; /* entry count */ - zend_long mem_size; /* used */ - time_t stime; /* start time */ - apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */ - apc_cache_entry_t *gc; /* gc list */ + apc_lock_t lock; /* header lock */ + zend_long nhits; /* hit count */ + zend_long nmisses; /* miss count */ + zend_long ninserts; /* insert count */ + zend_long nexpunges; /* expunge count */ + zend_long nentries; /* entry count */ + zend_long mem_size; /* used */ + time_t stime; /* start time */ + apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */ + uintptr_t gc; /* offset in shm to the first entry of gc list */ } apc_cache_header_t; /* }}} */ Since this is at the start of the shared memory segment, these values are accessible @@ -259,7 +259,7 @@ form of a quick-start guide to start hacking on APCu. typedef struct apc_cache_entry_t apc_cache_entry_t; struct apc_cache_entry_t { zval val; /* the zval copied at store time */ - apc_cache_entry_t *next; /* next entry in linked list */ + uintptr_t next; /* offset in shm of next entry in linked list */ zend_long ttl; /* the ttl on this specific entry */ zend_long ref_count; /* the reference count of this entry */ zend_long nhits; /* number of hits to this entry */ diff --git a/apc_cache.c b/apc_cache.c index 2fe38f9c..48bb863d 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -161,12 +161,12 @@ static zend_bool apc_cache_entry_expired( } /* {{{ apc_cache_wlocked_remove_entry */ -static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, apc_cache_entry_t **entry) +static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, uintptr_t *entry_offset) { - apc_cache_entry_t *dead = *entry; + apc_cache_entry_t *dead = ENTRYAT(*entry_offset); - /* think here is safer */ - *entry = (*entry)->next; + /* unlink entry from list */ + *entry_offset = dead->next; /* adjust header info */ if (cache->header->mem_size) @@ -175,14 +175,14 @@ static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, apc_cache_entry_t if (cache->header->nentries) cache->header->nentries--; - /* remove if there are no references */ + /* free entry if there are no references */ if (dead->ref_count <= 0) { free_entry(cache, dead); } else { /* add to gc if there are still refs */ - dead->next = cache->header->gc; dead->dtime = time(0); - cache->header->gc = dead; + dead->next = cache->header->gc; + cache->header->gc = ENTRYOF(dead); } } /* }}} */ @@ -199,33 +199,29 @@ static void apc_cache_wlocked_gc(apc_cache_t* cache) return; } - { - apc_cache_entry_t **entry = &cache->header->gc; - time_t now = time(0); - - while (*entry != NULL) { - time_t gc_sec = cache->gc_ttl ? (now - (*entry)->dtime) : 0; - - if (!(*entry)->ref_count || gc_sec > (time_t)cache->gc_ttl) { - apc_cache_entry_t *dead = *entry; + time_t now = time(0); - /* good ol' whining */ - if (dead->ref_count > 0) { - apc_debug( - "GC cache entry '%s' was on gc-list for %lld seconds", - ZSTR_VAL(&dead->key), (long long) gc_sec - ); - } + uintptr_t *entry_offset = &cache->header->gc; + while (*entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(*entry_offset); + time_t gc_sec = cache->gc_ttl ? (now - entry->dtime) : 0; - /* set next entry */ - *entry = (*entry)->next; + if (entry->ref_count > 0 && gc_sec <= (time_t)cache->gc_ttl) { + entry_offset = &entry->next; + continue; + } - /* free entry */ - free_entry(cache, dead); - } else { - entry = &(*entry)->next; - } + /* good ol' whining */ + if (entry->ref_count > 0) { + apc_debug( + "GC cache entry '%s' was on gc-list for %lld seconds", + ZSTR_VAL(&entry->key), (long long) gc_sec + ); } + + /* set next and free current entry */ + *entry_offset = entry->next; + free_entry(cache, entry); } } /* }}} */ @@ -295,7 +291,7 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser cache = pemalloc(sizeof(apc_cache_t), 1); /* calculate cache size for shm allocation */ - cache_size = sizeof(apc_cache_header_t) + nslots * sizeof(apc_cache_entry_t *); + cache_size = sizeof(apc_cache_header_t) + nslots * sizeof(uintptr_t); /* allocate shm */ cache->header = apc_sma_malloc(sma, cache_size); @@ -313,11 +309,11 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser cache->header->nmisses = 0; cache->header->nentries = 0; cache->header->nexpunges = 0; - cache->header->gc = NULL; + cache->header->gc = 0; cache->header->stime = time(NULL); /* set cache options */ - cache->slots = (apc_cache_entry_t **) (((char*) cache->header) + sizeof(apc_cache_header_t)); + cache->slots = (uintptr_t *)((uintptr_t)cache->header + sizeof(apc_cache_header_t)); cache->sma = sma; cache->serializer = serializer; cache->nslots = nslots; @@ -336,65 +332,62 @@ static inline zend_bool apc_cache_wlocked_insert( apc_cache_t *cache, apc_cache_entry_t *new_entry, zend_bool exclusive) { zend_string *key = &new_entry->key; time_t t = new_entry->ctime; + zend_ulong h; + size_t s; /* process deleted list */ apc_cache_wlocked_gc(cache); - /* make the insertion */ - { - apc_cache_entry_t **entry; - zend_ulong h; - size_t s; - - /* calculate hash and entry */ - apc_cache_hash_slot(cache, key, &h, &s); - - entry = &cache->slots[s]; - while (*entry) { - /* check for a match by hash and string */ - if (apc_entry_key_equals(*entry, key, h)) { - /* - * At this point we have found the user cache entry. If we are doing - * an exclusive insert (apc_add) we are going to bail right away if - * the user entry already exists and is hard expired. - */ - if (exclusive && !apc_cache_entry_hard_expired(*entry, t)) { - return 0; - } + /* calculate hash and entry */ + apc_cache_hash_slot(cache, key, &h, &s); - apc_cache_wlocked_remove_entry(cache, entry); - break; - } + uintptr_t *entry_offset = &cache->slots[s]; + while (*entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(*entry_offset); + /* check for a match by hash and string */ + if (apc_entry_key_equals(entry, key, h)) { /* - * This is a bit nasty. The idea here is to do runtime cleanup of the linked list of - * entry entries so we don't always have to skip past a bunch of stale entries. + * At this point we have found the user cache entry. If we are doing + * an exclusive insert (apc_add) we are going to bail right away if + * the user entry already exists and is not hard expired. */ - if (apc_cache_entry_expired(cache, *entry, t)) { - apc_cache_wlocked_remove_entry(cache, entry); - continue; + if (exclusive && !apc_cache_entry_hard_expired(entry, t)) { + return 0; } - /* set next entry */ - entry = &(*entry)->next; + apc_cache_wlocked_remove_entry(cache, entry_offset); + break; } - /* link in new entry */ - new_entry->next = *entry; - *entry = new_entry; + /* + * This is a bit nasty. The idea here is to do runtime cleanup of the linked list of + * entries, so we don't always have to skip past a bunch of stale entries. + */ + if (apc_cache_entry_expired(cache, entry, t)) { + apc_cache_wlocked_remove_entry(cache, entry_offset); + continue; + } - cache->header->mem_size += new_entry->mem_size; - cache->header->nentries++; - cache->header->ninserts++; + /* set next entry */ + entry_offset = &entry->next; } + /* link in new entry */ + new_entry->next = *entry_offset; + *entry_offset = ENTRYOF(new_entry); + + cache->header->mem_size += new_entry->mem_size; + cache->header->nentries++; + cache->header->ninserts++; + return 1; } static void apc_cache_set_entry_values(apc_cache_entry_t *entry, const int32_t ttl, const time_t t) { entry->ttl = ttl; - entry->next = NULL; + entry->next = 0; entry->ref_count = 0; entry->nhits = 0; entry->ctime = t; @@ -434,15 +427,16 @@ static inline zend_bool apc_cache_store_internal( /* Find entry, without updating stat counters or access time */ static inline apc_cache_entry_t *apc_cache_rlocked_find_nostat( apc_cache_t *cache, zend_string *key, time_t t) { - apc_cache_entry_t *entry; zend_ulong h; size_t s; /* calculate hash and slot */ apc_cache_hash_slot(cache, key, &h, &s); - entry = cache->slots[s]; - while (entry) { + uintptr_t entry_offset = cache->slots[s]; + while (entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(entry_offset); + /* check for a matching key by has and identifier */ if (apc_entry_key_equals(entry, key, h)) { /* Check to make sure this entry isn't expired by a hard TTL */ @@ -453,7 +447,7 @@ static inline apc_cache_entry_t *apc_cache_rlocked_find_nostat( return entry; } - entry = entry->next; + entry_offset = entry->next; } return NULL; @@ -462,15 +456,17 @@ static inline apc_cache_entry_t *apc_cache_rlocked_find_nostat( /* Find entry, updating stat counters and access time */ static inline apc_cache_entry_t *apc_cache_rlocked_find( apc_cache_t *cache, zend_string *key, time_t t) { - apc_cache_entry_t *entry; + zend_ulong h; size_t s; /* calculate hash and slot */ apc_cache_hash_slot(cache, key, &h, &s); - entry = cache->slots[s]; - while (entry) { + uintptr_t entry_offset = cache->slots[s]; + while (entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(entry_offset); + /* check for a matching key by has and identifier */ if (apc_entry_key_equals(entry, key, h)) { /* Check to make sure this entry isn't expired by a hard TTL */ @@ -485,7 +481,7 @@ static inline apc_cache_entry_t *apc_cache_rlocked_find( return entry; } - entry = entry->next; + entry_offset = entry->next; } ATOMIC_INC_RLOCKED(cache->header->nmisses); @@ -698,9 +694,9 @@ static void apc_cache_wlocked_real_expunge(apc_cache_t* cache) { /* expunge */ for (i = 0; i < cache->nslots; i++) { - apc_cache_entry_t **entry = &cache->slots[i]; - while (*entry) { - apc_cache_wlocked_remove_entry(cache, entry); + uintptr_t *entry_offset = &cache->slots[i]; + while (*entry_offset) { + apc_cache_wlocked_remove_entry(cache, entry_offset); } } @@ -767,15 +763,17 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) /* look for junk */ for (i = 0; i < cache->nslots; i++) { - apc_cache_entry_t **entry = &cache->slots[i]; - while (*entry) { - if (apc_cache_entry_expired(cache, *entry, t)) { - apc_cache_wlocked_remove_entry(cache, entry); + uintptr_t *entry_offset = &cache->slots[i]; + while (*entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(*entry_offset); + + if (apc_cache_entry_expired(cache, entry, t)) { + apc_cache_wlocked_remove_entry(cache, entry_offset); continue; } /* grab next entry */ - entry = &(*entry)->next; + entry_offset = &entry->next; } } @@ -945,7 +943,6 @@ PHP_APCU_API zend_bool apc_cache_atomic_update_long( /* {{{ apc_cache_delete */ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key) { - apc_cache_entry_t **entry; zend_ulong h; size_t s; @@ -961,19 +958,20 @@ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key) } /* find head */ - entry = &cache->slots[s]; + uintptr_t *entry_offset = &cache->slots[s]; + while (*entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(*entry_offset); - while (*entry) { /* check for a match by hash and identifier */ - if (apc_entry_key_equals(*entry, key, h)) { + if (apc_entry_key_equals(entry, key, h)) { /* executing removal */ - apc_cache_wlocked_remove_entry(cache, entry); + apc_cache_wlocked_remove_entry(cache, entry_offset); apc_cache_wunlock(cache); return 1; } - entry = &(*entry)->next; + entry_offset = &entry->next; } apc_cache_wunlock(cache); @@ -1029,7 +1027,7 @@ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool zval list; zval gc; zval slots; - apc_cache_entry_t *p; + uintptr_t entry_offset; zend_ulong j; ZVAL_NULL(info); @@ -1067,12 +1065,15 @@ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool array_init(&slots); for (i = 0; i < cache->nslots; i++) { - p = cache->slots[i]; j = 0; - for (; p != NULL; p = p->next) { - zval link = apc_cache_link_info(cache, p); + entry_offset = cache->slots[i]; + while (entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(entry_offset); + zval link = apc_cache_link_info(cache, entry); + add_next_index_zval(&list, &link); j++; + entry_offset = entry->next; } if (j != 0) { add_index_long(&slots, (zend_ulong)i, j); @@ -1082,9 +1083,13 @@ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool /* For each slot pending deletion */ array_init(&gc); - for (p = cache->header->gc; p != NULL; p = p->next) { - zval link = apc_cache_link_info(cache, p); + entry_offset = cache->header->gc; + while (entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(entry_offset); + zval link = apc_cache_link_info(cache, entry); + add_next_index_zval(&gc, &link); + entry_offset = entry->next; } add_assoc_zval(info, "cache_list", &list); @@ -1120,9 +1125,10 @@ PHP_APCU_API void apc_cache_stat(apc_cache_t *cache, zend_string *key, zval *sta php_apc_try { /* find head */ - apc_cache_entry_t *entry = cache->slots[s]; + uintptr_t entry_offset = cache->slots[s]; + while (entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(entry_offset); - while (entry) { /* check for a matching key by has and identifier */ if (apc_entry_key_equals(entry, key, h)) { array_init(stat); @@ -1137,7 +1143,7 @@ PHP_APCU_API void apc_cache_stat(apc_cache_t *cache, zend_string *key, zval *sta } /* next */ - entry = entry->next; + entry_offset = entry->next; } } php_apc_finally { apc_cache_runlock(cache); diff --git a/apc_cache.h b/apc_cache.h index 40df2770..e800854f 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -50,7 +50,7 @@ struct apc_cache_slam_key_t { typedef struct apc_cache_entry_t apc_cache_entry_t; struct apc_cache_entry_t { zval val; /* the zval copied at store time */ - apc_cache_entry_t *next; /* next entry in linked list */ + uintptr_t next; /* offset in shm of next entry in linked list */ zend_long ttl; /* the ttl on this specific entry */ zend_long ref_count; /* the reference count of this entry */ zend_long nhits; /* number of hits to this entry */ @@ -75,13 +75,13 @@ typedef struct _apc_cache_header_t { zend_long mem_size; /* used */ time_t stime; /* start time */ apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */ - apc_cache_entry_t *gc; /* gc list */ + uintptr_t gc; /* offset in shm to the first entry of gc list */ } apc_cache_header_t; /* }}} */ /* {{{ struct definition: apc_cache_t */ typedef struct _apc_cache_t { apc_cache_header_t* header; /* cache header (stored in SHM) */ - apc_cache_entry_t** slots; /* array of cache slots (stored in SHM) */ + uintptr_t* slots; /* array of cache slots (stored in SHM) */ apc_sma_t* sma; /* shared memory allocator */ apc_serializer_t* serializer; /* serializer */ size_t nslots; /* number of slots in cache */ @@ -300,6 +300,12 @@ static inline void apc_cache_runlock(apc_cache_t *cache) { /* APC_ENTRY_SIZE takes into account the trailing key-string + terminating 0-byte */ #define APC_ENTRY_SIZE(key_len) (ZEND_MM_ALIGNED_SIZE(XtOffsetOf(apc_cache_entry_t, key.val) + key_len + 1)) +/* ENTRYAT and ENTRYOF are used to convert between offsets and pointers to cache entries. + * Both expect the presence of cache->header that points to the cache header in the + * shared memory segment. */ +#define ENTRYAT(offset) ((apc_cache_entry_t *)((uintptr_t)cache->header + (uintptr_t)offset)) +#define ENTRYOF(entry) (((uintptr_t)entry) - (uintptr_t)cache->header) + #endif /* diff --git a/apc_iterator.c b/apc_iterator.c index 3d892ad7..9181085c 100644 --- a/apc_iterator.c +++ b/apc_iterator.c @@ -211,6 +211,7 @@ static int apc_iterator_check_expiry(apc_cache_t* cache, apc_cache_entry_t *entr /* {{{ apc_iterator_fetch_active */ static size_t apc_iterator_fetch_active(apc_iterator_t *iterator) { + apc_cache_t *cache = apc_user_cache; size_t count = 0; apc_iterator_item_t *item; time_t t = apc_time(); @@ -219,15 +220,16 @@ static size_t apc_iterator_fetch_active(apc_iterator_t *iterator) { apc_iterator_item_dtor(apc_stack_pop(iterator->stack)); } - if (!apc_cache_rlock(apc_user_cache)) { + if (!apc_cache_rlock(cache)) { return count; } php_apc_try { - while (count <= iterator->chunk_size && iterator->slot_idx < apc_user_cache->nslots) { - apc_cache_entry_t *entry = apc_user_cache->slots[iterator->slot_idx]; - while (entry) { - if (apc_iterator_check_expiry(apc_user_cache, entry, t)) { + while (count <= iterator->chunk_size && iterator->slot_idx < cache->nslots) { + uintptr_t entry_offset = cache->slots[iterator->slot_idx]; + while (entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(entry_offset); + if (apc_iterator_check_expiry(cache, entry, t)) { if (apc_iterator_search_match(iterator, entry)) { count++; item = apc_iterator_item_ctor(iterator, entry); @@ -236,13 +238,13 @@ static size_t apc_iterator_fetch_active(apc_iterator_t *iterator) { } } } - entry = entry->next; + entry_offset = entry->next; } iterator->slot_idx++; } } php_apc_finally { iterator->stack_idx = 0; - apc_cache_runlock(apc_user_cache); + apc_cache_runlock(cache); } php_apc_end_try(); return count; @@ -251,21 +253,23 @@ static size_t apc_iterator_fetch_active(apc_iterator_t *iterator) { /* {{{ apc_iterator_fetch_deleted */ static size_t apc_iterator_fetch_deleted(apc_iterator_t *iterator) { + apc_cache_t *cache = apc_user_cache; size_t count = 0; apc_iterator_item_t *item; - if (!apc_cache_rlock(apc_user_cache)) { + if (!apc_cache_rlock(cache)) { return count; } php_apc_try { - apc_cache_entry_t *entry = apc_user_cache->header->gc; - while (entry && count <= iterator->slot_idx) { + uintptr_t entry_offset = cache->header->gc; + while (entry_offset && count <= iterator->slot_idx) { count++; - entry = entry->next; + entry_offset = ENTRYAT(entry_offset)->next; } count = 0; - while (entry && count < iterator->chunk_size) { + while (entry_offset && count < iterator->chunk_size) { + apc_cache_entry_t *entry = ENTRYAT(entry_offset); if (apc_iterator_search_match(iterator, entry)) { count++; item = apc_iterator_item_ctor(iterator, entry); @@ -273,12 +277,12 @@ static size_t apc_iterator_fetch_deleted(apc_iterator_t *iterator) { apc_stack_push(iterator->stack, item); } } - entry = entry->next; + entry_offset = entry->next; } } php_apc_finally { iterator->slot_idx += count; iterator->stack_idx = 0; - apc_cache_runlock(apc_user_cache); + apc_cache_runlock(cache); } php_apc_end_try(); return count; @@ -287,31 +291,33 @@ static size_t apc_iterator_fetch_deleted(apc_iterator_t *iterator) { /* {{{ apc_iterator_totals */ static void apc_iterator_totals(apc_iterator_t *iterator) { + apc_cache_t *cache = apc_user_cache; time_t t = apc_time(); - if (!apc_cache_rlock(apc_user_cache)) { + if (!apc_cache_rlock(cache)) { return; } php_apc_try { size_t i; - for (i=0; i < apc_user_cache->nslots; i++) { - apc_cache_entry_t *entry = apc_user_cache->slots[i]; - while (entry) { - if (apc_iterator_check_expiry(apc_user_cache, entry, t)) { + for (i=0; i < cache->nslots; i++) { + uintptr_t entry_offset = cache->slots[i]; + while (entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(entry_offset); + if (apc_iterator_check_expiry(cache, entry, t)) { if (apc_iterator_search_match(iterator, entry)) { iterator->size += entry->mem_size; iterator->hits += entry->nhits; iterator->count++; } } - entry = entry->next; + entry_offset = entry->next; } } } php_apc_finally { iterator->totals_flag = 1; - apc_cache_runlock(apc_user_cache); + apc_cache_runlock(cache); } php_apc_end_try(); } /* }}} */ From 7bd94a794deae0a988014516566cc5fc7e1e62b9 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Mon, 26 May 2025 20:59:39 +0200 Subject: [PATCH 063/101] Small SMA code cleanups / improvements --- apc_sma.c | 54 ++++++++++++++++++++++-------------------------------- apc_sma.h | 5 ----- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/apc_sma.c b/apc_sma.c index 3559c545..d487a22d 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -80,7 +80,7 @@ struct block_t { /* macros for getting the next or previous sequential block */ #define NEXT_SBLOCK(block) ((block_t*)((char*)block + block->size)) -#define PREV_SBLOCK(block) (block->prev_size ? ((block_t*)((char*)block - block->prev_size)) : NULL) +#define PREV_SBLOCK(block) ((block_t*)((char*)block - block->prev_size)) /* Canary macros for setting, checking and resetting memory canaries */ #ifdef APC_SMA_CANARIES @@ -99,13 +99,18 @@ struct block_t { #define BEST_FIT_LIMIT 3 static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) { - block_t *cur, *prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); block_t *found = NULL; uint32_t i; - CHECK_CANARY(prv); + CHECK_CANARY(cur); + + /* First, ensure that at least realsize free bytes are available, even if they are not contiguous. */ + if (smaheader->avail < realsize) { + return NULL; + } - while (prv->fnext) { - cur = BLOCKAT(prv->fnext); + while (cur->fnext) { + cur = BLOCKAT(cur->fnext); CHECK_CANARY(cur); /* Found a suitable block */ @@ -113,22 +118,17 @@ static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) { found = cur; break; } - - prv = cur; } if (found) { /* Try to find a smaller block that also fits */ - prv = cur; - for (i = 0; i < BEST_FIT_LIMIT && prv->fnext; i++) { - cur = BLOCKAT(prv->fnext); + for (i = 0; i < BEST_FIT_LIMIT && cur->fnext; i++) { + cur = BLOCKAT(cur->fnext); CHECK_CANARY(cur); if (cur->size >= realsize && cur->size < found->size) { found = cur; } - - prv = cur; } } @@ -141,14 +141,8 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size) block_t* prv; /* block prior to working block */ block_t* cur; /* working block in list */ size_t realsize; /* actual size of block needed, including block header */ - size_t block_header_size = ALIGNWORD(sizeof(block_t)); - - realsize = ALIGNWORD(size + block_header_size); - /* First, ensure that the segment contains at least realsize free bytes, even if they are not contiguous. */ - if (smaheader->avail < realsize) { - return SIZE_MAX; - } + realsize = ALIGNWORD(size + ALIGNWORD(sizeof(block_t))); cur = find_block(smaheader, realsize); if (!cur) { @@ -189,7 +183,7 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size) SET_CANARY(cur); - return OFFSET(cur) + block_header_size; + return OFFSET(cur) + ALIGNWORD(sizeof(block_t)); } /* }}} */ @@ -217,7 +211,7 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset) BLOCKAT(prv->fnext)->fprev = prv->fprev; BLOCKAT(prv->fprev)->fnext = prv->fnext; /* cur and prv share an edge, combine them */ - prv->size +=cur->size; + prv->size += cur->size; RESET_CANARY(cur); cur = prv; @@ -382,22 +376,23 @@ PHP_APCU_API apc_sma_info_t *apc_sma_info(apc_sma_t* sma, zend_bool limited) { SMA_LOCK(sma); sma_header_t *smaheader = SMA_HDR(sma); - block_t *prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); apc_sma_link_t **link = &info->list; - /* For each free block */ - while (BLOCKAT(prv->fnext)->fnext != 0) { - block_t *cur = BLOCKAT(prv->fnext); + /* Skip 1st (0-sized) block */ + cur = BLOCKAT(cur->fnext); + /* For each free block */ + while (cur->fnext != 0) { CHECK_CANARY(cur); *link = emalloc(sizeof(apc_sma_link_t)); (*link)->size = cur->size; - (*link)->offset = prv->fnext; + (*link)->offset = OFFSET(cur); (*link)->next = NULL; link = &(*link)->next; - prv = cur; + cur = BLOCKAT(cur->fnext); } SMA_UNLOCK(sma); @@ -447,11 +442,6 @@ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { return 0; } -PHP_APCU_API void apc_sma_check_integrity(apc_sma_t* sma) -{ - /* dummy */ -} - /* }}} */ /* diff --git a/apc_sma.h b/apc_sma.h index 472a8c72..7c15775f 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -111,11 +111,6 @@ PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma); */ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size); -/* -* apc_sma_api_check_integrity will check the integrity of sma -*/ -PHP_APCU_API void apc_sma_check_integrity(apc_sma_t* sma); /* }}} */ - /* {{{ ALIGNWORD: pad up x, aligned to the system's word boundary */ #define ALIGNWORD(x) ZEND_MM_ALIGNED_SIZE(x) /* }}} */ From 289addf45b8d6b435ffb2ece90ab51e3f77161cb Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Wed, 14 May 2025 20:52:41 +0200 Subject: [PATCH 064/101] Refactor linked lists of the cache layer to doubly linked lists Cache entries now contain a reference to the previous entry or the head pointer of the linked list. This is to prepare for defragmentation, as it allows entries to be moved in memory if the address containing the reference to the current entry is not available in the context. --- TECHNOTES.txt | 5 +++-- apc_cache.c | 56 ++++++++++++++++++++++++++++++++++----------------- apc_cache.h | 5 +++-- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/TECHNOTES.txt b/TECHNOTES.txt index e7cf642b..bd6a19dd 100644 --- a/TECHNOTES.txt +++ b/TECHNOTES.txt @@ -258,8 +258,8 @@ form of a quick-start guide to start hacking on APCu. /* {{{ struct definition: apc_cache_entry_t */ typedef struct apc_cache_entry_t apc_cache_entry_t; struct apc_cache_entry_t { - zval val; /* the zval copied at store time */ - uintptr_t next; /* offset in shm of next entry in linked list */ + uintptr_t next; /* offset to next entry (MUST BE THE 1st FIELD OF THE STRUCT!) */ + uintptr_t prev; /* offset to previous entry / head-pointer of the linked list */ zend_long ttl; /* the ttl on this specific entry */ zend_long ref_count; /* the reference count of this entry */ zend_long nhits; /* number of hits to this entry */ @@ -268,6 +268,7 @@ form of a quick-start guide to start hacking on APCu. time_t dtime; /* time entry was removed from cache */ time_t atime; /* time entry was last accessed */ zend_long mem_size; /* memory used */ + zval val; /* the zval copied at store time */ zend_string key; /* entry key (MUST BE THE LAST FIELD OF THE STRUCT!) */ }; /* }}} */ diff --git a/apc_cache.c b/apc_cache.c index 48bb863d..2627130c 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -160,29 +160,47 @@ static zend_bool apc_cache_entry_expired( || apc_cache_entry_soft_expired(cache, entry, t); } +/* Inserts an entry into a linked list. The argument entry_offset must point either + * to entry->next of an existing entry or to the head pointer of a linked list. */ +static void apc_cache_wlocked_link_entry(apc_cache_t *cache, uintptr_t *entry_offset, apc_cache_entry_t *entry) { + entry->next = *entry_offset; + entry->prev = ENTRYOF(entry_offset); + *entry_offset = ENTRYOF(entry); + if (entry->next) { + ENTRYAT(entry->next)->prev = *entry_offset; + } +} + +/* Removes an entry from a linked list. */ +static void apc_cache_wlocked_unlink_entry(apc_cache_t *cache, apc_cache_entry_t *entry) { + /* Since “next” is the 1st field of apc_cache_entry_t, the head pointer of the list + * can be changed like a previous entry via ENTRYAT(entry->prev)->next. */ + ENTRYAT(entry->prev)->next = entry->next; + if (entry->next) { + ENTRYAT(entry->next)->prev = entry->prev; + } +} + /* {{{ apc_cache_wlocked_remove_entry */ -static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, uintptr_t *entry_offset) +static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, apc_cache_entry_t *entry) { - apc_cache_entry_t *dead = ENTRYAT(*entry_offset); - - /* unlink entry from list */ - *entry_offset = dead->next; + /* unlink entry from list */ + apc_cache_wlocked_unlink_entry(cache, entry); /* adjust header info */ if (cache->header->mem_size) - cache->header->mem_size -= dead->mem_size; + cache->header->mem_size -= entry->mem_size; if (cache->header->nentries) cache->header->nentries--; /* free entry if there are no references */ - if (dead->ref_count <= 0) { - free_entry(cache, dead); + if (entry->ref_count <= 0) { + free_entry(cache, entry); } else { /* add to gc if there are still refs */ - dead->dtime = time(0); - dead->next = cache->header->gc; - cache->header->gc = ENTRYOF(dead); + entry->dtime = time(0); + apc_cache_wlocked_link_entry(cache, &cache->header->gc, entry); } } /* }}} */ @@ -220,7 +238,7 @@ static void apc_cache_wlocked_gc(apc_cache_t* cache) } /* set next and free current entry */ - *entry_offset = entry->next; + apc_cache_wlocked_unlink_entry(cache, entry); free_entry(cache, entry); } } @@ -356,7 +374,7 @@ static inline zend_bool apc_cache_wlocked_insert( return 0; } - apc_cache_wlocked_remove_entry(cache, entry_offset); + apc_cache_wlocked_remove_entry(cache, entry); break; } @@ -365,7 +383,7 @@ static inline zend_bool apc_cache_wlocked_insert( * entries, so we don't always have to skip past a bunch of stale entries. */ if (apc_cache_entry_expired(cache, entry, t)) { - apc_cache_wlocked_remove_entry(cache, entry_offset); + apc_cache_wlocked_remove_entry(cache, entry); continue; } @@ -374,8 +392,7 @@ static inline zend_bool apc_cache_wlocked_insert( } /* link in new entry */ - new_entry->next = *entry_offset; - *entry_offset = ENTRYOF(new_entry); + apc_cache_wlocked_link_entry(cache, entry_offset, new_entry); cache->header->mem_size += new_entry->mem_size; cache->header->nentries++; @@ -388,6 +405,7 @@ static void apc_cache_set_entry_values(apc_cache_entry_t *entry, const int32_t t { entry->ttl = ttl; entry->next = 0; + entry->prev = 0; entry->ref_count = 0; entry->nhits = 0; entry->ctime = t; @@ -696,7 +714,7 @@ static void apc_cache_wlocked_real_expunge(apc_cache_t* cache) { for (i = 0; i < cache->nslots; i++) { uintptr_t *entry_offset = &cache->slots[i]; while (*entry_offset) { - apc_cache_wlocked_remove_entry(cache, entry_offset); + apc_cache_wlocked_remove_entry(cache, ENTRYAT(*entry_offset)); } } @@ -768,7 +786,7 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) apc_cache_entry_t *entry = ENTRYAT(*entry_offset); if (apc_cache_entry_expired(cache, entry, t)) { - apc_cache_wlocked_remove_entry(cache, entry_offset); + apc_cache_wlocked_remove_entry(cache, entry); continue; } @@ -965,7 +983,7 @@ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key) /* check for a match by hash and identifier */ if (apc_entry_key_equals(entry, key, h)) { /* executing removal */ - apc_cache_wlocked_remove_entry(cache, entry_offset); + apc_cache_wlocked_remove_entry(cache, entry); apc_cache_wunlock(cache); return 1; diff --git a/apc_cache.h b/apc_cache.h index e800854f..9fcf9ef9 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -49,8 +49,8 @@ struct apc_cache_slam_key_t { /* {{{ struct definition: apc_cache_entry_t */ typedef struct apc_cache_entry_t apc_cache_entry_t; struct apc_cache_entry_t { - zval val; /* the zval copied at store time */ - uintptr_t next; /* offset in shm of next entry in linked list */ + uintptr_t next; /* offset to next entry (MUST BE THE 1st FIELD OF THE STRUCT!) */ + uintptr_t prev; /* offset to previous entry / head-pointer of the linked list */ zend_long ttl; /* the ttl on this specific entry */ zend_long ref_count; /* the reference count of this entry */ zend_long nhits; /* number of hits to this entry */ @@ -59,6 +59,7 @@ struct apc_cache_entry_t { time_t dtime; /* time entry was removed from cache */ time_t atime; /* time entry was last accessed */ zend_long mem_size; /* memory used */ + zval val; /* the zval copied at store time */ zend_string key; /* entry key (MUST BE THE LAST FIELD OF THE STRUCT!) */ }; /* }}} */ From d0a1fa9b138b6e0a5a11fca4bda48561253dda92 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Fri, 23 May 2025 22:34:51 +0200 Subject: [PATCH 065/101] Add defragmentation to apcu This adds defragmentation logic to apcu, which is performed during the default_expunge operation. It works by shifting all allocated blocks to the left (low addresses), allowing all free blocks to be coalesced to one larger free block on the right side. --- apc_cache.c | 47 +++++++++++++++++++----- apc_sma.c | 62 ++++++++++++++++++++++++++++++- apc_sma.h | 22 ++++++++++- package.xml | 1 + tests/apc_defrag.phpt | 85 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 tests/apc_defrag.phpt diff --git a/apc_cache.c b/apc_cache.c index 2627130c..55765b0b 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -160,6 +160,24 @@ static zend_bool apc_cache_entry_expired( || apc_cache_entry_soft_expired(cache, entry, t); } +/* apc_cache_wlocked_move_entry() is called during defragmentation, before an entry is moved to a new position. */ +static zend_bool apc_cache_wlocked_move_entry(apc_cache_t *cache, apc_cache_entry_t *old, apc_cache_entry_t *new) { + /* Check if the entry can be moved. */ + if (old->ref_count > 0) { + return 0; + } + + /* Change all references to this entry to the new position. + * Since “next” is the 1st field of apc_cache_entry_t, the head pointer of the list + * can be changed like a previous entry via ENTRYAT(old->prev)->next. */ + ENTRYAT(old->prev)->next = ENTRYOF(new); + if (old->next) { + ENTRYAT(old->next)->prev = ENTRYOF(new); + } + + return 1; +} + /* Inserts an entry into a linked list. The argument entry_offset must point either * to entry->next of an existing entry or to the head pointer of a linked list. */ static void apc_cache_wlocked_link_entry(apc_cache_t *cache, uintptr_t *entry_offset, apc_cache_entry_t *entry) { @@ -772,9 +790,6 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) return; } - /* gc */ - apc_cache_wlocked_gc(cache); - /* smart > 1 increases the probability of a full cache wipe, * so expunge() is called less often when memory is low. */ size = (cache->smart > 0L) ? (size_t) (cache->smart * size) : size; @@ -795,15 +810,29 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) } } - /* if the cache now has space, then reset last key */ - if (apc_sma_get_avail_size(cache->sma, size)) { - /* wipe lastkey */ - memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t)); - } else { - /* with not enough space left in cache, we are forced to expunge */ + /* gc */ + apc_cache_wlocked_gc(cache); + + /* if all free blocks together do not provide enough memory, we immediately perform a real expunge */ + if (!apc_sma_check_avail(cache->sma, size)) { + apc_cache_wlocked_real_expunge(cache); + apc_cache_wunlock(cache); + return; + } + + /* run defragmentation to coalesce free blocks */ + apc_sma_defrag(cache->sma, cache, (apc_sma_move_f)apc_cache_wlocked_move_entry); + + /* if size bytes can't be allocated as a contiguous block after defragmentation, we do a real expunge */ + if (!apc_sma_check_avail_contiguous(cache->sma, size)) { apc_cache_wlocked_real_expunge(cache); + apc_cache_wunlock(cache); + return; } + /* wipe lastkey */ + memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t)); + apc_cache_wunlock(cache); } /* }}} */ diff --git a/apc_sma.c b/apc_sma.c index d487a22d..8b00adc0 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -415,7 +415,11 @@ PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma) { return SMA_HDR(sma)->avail; } -PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { +PHP_APCU_API zend_bool apc_sma_check_avail(apc_sma_t *sma, size_t size) { + return SMA_HDR(sma)->avail >= ALIGNWORD(size + ALIGNWORD(sizeof(block_t))); +} + +PHP_APCU_API zend_bool apc_sma_check_avail_contiguous(apc_sma_t *sma, size_t size) { size_t realsize = ALIGNWORD(size + ALIGNWORD(sizeof(block_t))); sma_header_t *smaheader = SMA_HDR(sma); @@ -442,6 +446,62 @@ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) { return 0; } +PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move) { + sma_header_t *smaheader = SMA_HDR(sma); + block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)) + ALIGNWORD(sizeof(block_t))); + block_t *first = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + + if (!SMA_LOCK(sma)) { + return; + } + + /* empty the free list */ + first->fnext = sma->size - ALIGNWORD(sizeof(block_t)); + BLOCKAT(first->fnext)->fprev = OFFSET(first); + + /* loop through all blocks */ + while (cur->size != 0) { + /* continue until cur points to a free block */ + if (!cur->fnext) { + cur = NEXT_SBLOCK(cur); + continue; + } + + /* if cur is free, nxt must be an allocated block, since we never have two consecutive free blocks */ + block_t *nxt = NEXT_SBLOCK(cur); + + /* if nxt is the last block, or if nxt can't be moved, cur can't be combined with other free blocks */ + if (nxt->size == 0 || !move(data, (char *)nxt + ALIGNWORD(sizeof(block_t)), (char *)cur + ALIGNWORD(sizeof(block_t)))) { + /* insert cur into the free list */ + cur->fnext = first->fnext; + cur->fprev = OFFSET(first); + first->fnext = OFFSET(cur); + BLOCKAT(cur->fnext)->fprev = first->fnext; + cur->prev_size = 0; + nxt->prev_size = cur->size; + + cur = NEXT_SBLOCK(nxt); + continue; + } + + /* swap cur and nxt by moving nxt (incl. header) and initializing a new block header for cur behind it */ + size_t free_size = cur->size; + memmove(cur, nxt, nxt->size); + cur->prev_size = 0; + cur = NEXT_SBLOCK(cur); + cur->size = free_size; + cur->fnext = 1; /* mark cur as free */ + + /* if the next block is also free, combine cur and nxt to one larger free block */ + nxt = NEXT_SBLOCK(cur); + if (nxt->fnext) { + cur->size += nxt->size; + } + } + + SMA_UNLOCK(sma); +} + /* }}} */ /* diff --git a/apc_sma.h b/apc_sma.h index 7c15775f..a444c9d5 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -107,9 +107,27 @@ PHP_APCU_API void apc_sma_free_info(apc_sma_t* sma, apc_sma_info_t* info); PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma); /* -* apc_sma_api_get_avail_size will return true if at least size contiguous bytes are available to the sma +* apc_sma_check_avail returns true if at least size bytes are available across all free blocks */ -PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size); +PHP_APCU_API zend_bool apc_sma_check_avail(apc_sma_t *sma, size_t size); + +/* +* apc_sma_check_avail_contiguous returns true if at least size contiguous bytes can be allocated from the sma +*/ +PHP_APCU_API zend_bool apc_sma_check_avail_contiguous(apc_sma_t *sma, size_t size); + +/* +* apc_sma_defrag defragments the shared memory by shifting all allocated blocks to the left, +* allowing all free blocks to be coalesced to one larger free block on the right side. +* +* The move() callback is called for each allocated block before it is moved. Therefore, move() can be used +* to prepare for the move or to prevent the block from being moved by returning 0. The argument "data" is +* passed as the first argument from apc_sma_defrag() to move(), while the old and the new address of the +* allocation is passed as the 2nd and 3rd argument. The callback must not write to the new memory area +* because the area is not yet allocated during the callback. +*/ +typedef zend_bool (*apc_sma_move_f)(void *data, void *pointer_old, void *pointer_new); +PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move); /* {{{ ALIGNWORD: pad up x, aligned to the system's word boundary */ #define ALIGNWORD(x) ZEND_MM_ALIGNED_SIZE(x) diff --git a/package.xml b/package.xml index f9c90514..157585cc 100644 --- a/package.xml +++ b/package.xml @@ -75,6 +75,7 @@ + diff --git a/tests/apc_defrag.phpt b/tests/apc_defrag.phpt new file mode 100644 index 00000000..38a44dea --- /dev/null +++ b/tests/apc_defrag.phpt @@ -0,0 +1,85 @@ +--TEST-- +Test defragmentation +--SKIPIF-- + +--INI-- +apc.enabled=1 +apc.enable_cli=1 +apc.use_request_time=1 +apc.shm_size=1M +--FILE-- += $min_entry_size) { + $i++; + apcu_store(sprintf("ttl3_%010d", $i), $i, 3); + + if (apcu_sma_info(true)['avail_mem'] >= $min_entry_size) { + apcu_store(sprintf("ttl1_%010d", $i), $i, 1); + } + } + + return $i; +} + +// store the first entry with ttl=1, which causes all entries +// behind this entry to be moved during defragmentation +apcu_store("ttl1_int", 123, 1); + +// store entries of different datatypes with ttl=3, which must be present after expiration + defragmentation +apcu_store("ttl3_int", 123456789, 3); +apcu_store("ttl3_string", "abc", 3); +apcu_store("ttl3_array", [1, 2, "a", "b"], 3); +apcu_store("ttl3_object", (object) ["prop1" => "val1", "prop2" => 2], 3); + +// safe available memory for later comparison +$avail_before_filled = apcu_sma_info(true)['avail_mem']; + +// fill cache with alternating ttl=1 + ttl=3 entries +fill_cache(); + +// ensure that cache is full +var_dump(apcu_sma_info(true)['avail_mem']); + +// expire all ttl1_* entries +apcu_inc_request_time(2); + +// this insertion should trigger an default_expunge which removes all ttl1_ entries and performs a defragmentation +var_dump(apcu_store("large_entry", str_repeat('x', 1000), 1)); + +// delete large_entry to be able to check the available memory in the next step +var_dump(apcu_delete("large_entry")); + +// the defragmentation should have freed more than 50% of the filled memory, because "ttl1_int" was also freed +var_dump(apcu_sma_info(true)['avail_mem'] > $avail_before_filled / 2); + +// after the default expunge, all ttl1_ entries should not be present anymore +var_dump(apcu_fetch("ttl1_int") === false); + +// all ttl3_ entries must be present (and correct after defragmentation) +var_dump(apcu_fetch("ttl3_int") === 123456789); +var_dump(apcu_fetch("ttl3_string") === "abc"); +var_dump(apcu_fetch("ttl3_array") === [1, 2, "a", "b"]); +var_dump(apcu_fetch("ttl3_object") == (object) ["prop1" => "val1", "prop2" => 2]); + +?> +--EXPECT-- +float(0) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) From 19eb5cf5cd4e03d318e94906eb7c53b4ef25e5a5 Mon Sep 17 00:00:00 2001 From: KitanoTom <81726287+KitanoTom@users.noreply.github.com> Date: Wed, 4 Jun 2025 04:46:56 +0900 Subject: [PATCH 066/101] Add support for hugetlb pages on Linux (#559) Enable huge pages on Linux by calling mmap() with the MAP_HUGETLB flag. Using huge pages can improve performance when working with large amounts of shared memory. This PR adds the apc.mmap_hugepage_size setting, which accepts the huge page size. --- .github/workflows/config.yml | 2 ++ apc_globals.h | 3 ++- apc_mmap.c | 42 +++++++++++++++++++++++++++++--- apc_mmap.h | 2 +- apc_sma.c | 4 +-- apc_sma.h | 2 +- package.xml | 6 +++++ php_apc.c | 40 ++++++++++++++++++++++++++---- tests/apc_mmap_hugepage_001.phpt | 18 ++++++++++++++ tests/apc_mmap_hugepage_002.phpt | 28 +++++++++++++++++++++ tests/apc_mmap_hugepage_003.phpt | 28 +++++++++++++++++++++ tests/apc_mmap_hugepage_004.phpt | 28 +++++++++++++++++++++ tests/apc_mmap_hugepage_005.phpt | 28 +++++++++++++++++++++ tests/apc_mmap_hugepage_006.phpt | 28 +++++++++++++++++++++ 14 files changed, 246 insertions(+), 13 deletions(-) create mode 100644 tests/apc_mmap_hugepage_001.phpt create mode 100644 tests/apc_mmap_hugepage_002.phpt create mode 100644 tests/apc_mmap_hugepage_003.phpt create mode 100644 tests/apc_mmap_hugepage_004.phpt create mode 100644 tests/apc_mmap_hugepage_005.phpt create mode 100644 tests/apc_mmap_hugepage_006.phpt diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 065d8018..c667a977 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -7,6 +7,8 @@ jobs: version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] runs-on: ubuntu-latest steps: + - name: Set the hugepages parameter + run: sudo sh -c "echo 1 > /proc/sys/vm/nr_hugepages" - name: Checkout apcu uses: actions/checkout@v4 - name: Setup PHP diff --git a/apc_globals.h b/apc_globals.h index 0eb1da1f..27100bd4 100644 --- a/apc_globals.h +++ b/apc_globals.h @@ -44,7 +44,8 @@ ZEND_BEGIN_MODULE_GLOBALS(apcu) zend_long smart; /* smart value */ #ifdef APC_MMAP - char *mmap_file_mask; /* mktemp-style file-mask to pass to mmap */ + char *mmap_file_mask; /* mktemp-style file-mask to pass to mmap */ + zend_long mmap_hugepage_size; /* hugepage size flag to pass to mmap (0: none)*/ #endif /* module variables */ diff --git a/apc_mmap.c b/apc_mmap.c index ad739818..225de401 100644 --- a/apc_mmap.c +++ b/apc_mmap.c @@ -51,7 +51,36 @@ # define MAP_ANON MAP_ANONYMOUS #endif -void *apc_mmap(char *file_mask, size_t size) +static int apc_mmap_hugepage_flags(size_t size, zend_long hugepage_size) +{ + if (!hugepage_size) return 0; // not use hugepages + +#if defined(MAP_HUGETLB) && defined(MAP_HUGE_MASK) && defined(MAP_HUGE_SHIFT) + if (size % hugepage_size) { + zend_error_noreturn(E_CORE_ERROR, "apc.shm_size must be a multiple of apc.mmap_hugepage_size"); + } + + zend_long page_size = hugepage_size; + int log2_page_size = -1; + + // calculate log2 of hugepage size + while (page_size) { + page_size >>= 1; + log2_page_size++; + } + + if (!log2_page_size || (log2_page_size & MAP_HUGE_MASK) != log2_page_size) { + // maybe hugepage size is too large or small + zend_error_noreturn(E_CORE_ERROR, "Invalid hugepage size: %ld", hugepage_size); + } + + return MAP_HUGETLB | ((unsigned int)log2_page_size << MAP_HUGE_SHIFT); +#else + zend_error_noreturn(E_CORE_ERROR, "This system does not support hugepages"); +#endif +} + +void *apc_mmap(char *file_mask, size_t size, zend_long hugepage_size) { void *shmaddr; int fd = -1; @@ -84,15 +113,22 @@ void *apc_mmap(char *file_mask, size_t size) unlink(file_mask); } + flags |= apc_mmap_hugepage_flags(size, hugepage_size); shmaddr = (void *)mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0); if ((long)shmaddr == -1) { - zend_error_noreturn(E_CORE_ERROR, "apc_mmap: Failed to mmap %zu bytes. Is your apc.shm_size too large?", size); + if (hugepage_size) { + zend_error_noreturn(E_CORE_ERROR, "apc_mmap: Failed to mmap %zu bytes with hugepage size %ld. apc.shm_size may be too large, apc.mmap_hugepage_size may be invalid, or the system lacks sufficient reserved hugepages.", size, hugepage_size); + } else { + zend_error_noreturn(E_CORE_ERROR, "apc_mmap: Failed to mmap %zu bytes. apc.shm_size may be too large.", size); + } } #ifdef MADV_HUGEPAGE /* enable transparent huge pages to reduce TLB misses (Linux only) */ - madvise(shmaddr, size, MADV_HUGEPAGE); + if (!hugepage_size) { + madvise(shmaddr, size, MADV_HUGEPAGE); + } #endif if (fd != -1) close(fd); diff --git a/apc_mmap.h b/apc_mmap.h index 142a6e55..ab5aaec7 100644 --- a/apc_mmap.h +++ b/apc_mmap.h @@ -35,7 +35,7 @@ /* Wrapper functions for shared memory mapped files */ #ifdef APC_MMAP -void *apc_mmap(char *file_mask, size_t size); +void *apc_mmap(char *file_mask, size_t size, zend_long hugepage_size); void apc_unmap(void *shmaddr, size_t size); #endif diff --git a/apc_sma.c b/apc_sma.c index 8b00adc0..128ade53 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -243,7 +243,7 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset) /* }}} */ /* {{{ APC SMA API */ -PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, size_t size, size_t min_alloc_size, char *mask) { +PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, size_t size, size_t min_alloc_size, char *mask, zend_long hugepage_size) { if (sma->initialized) { return; } @@ -254,7 +254,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex sma->size = ALIGNWORD(size > 0 ? size : SMA_DEFAULT_SEGSIZE); #ifdef APC_MMAP - sma->shmaddr = apc_mmap(mask, sma->size); + sma->shmaddr = apc_mmap(mask, sma->size, hugepage_size); #else sma->shmaddr = apc_shm_attach(sma->size); #endif diff --git a/apc_sma.h b/apc_sma.h index a444c9d5..c3204fbf 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -74,7 +74,7 @@ typedef struct _apc_sma_t { */ PHP_APCU_API void apc_sma_init( apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, - size_t size, size_t min_alloc_size, char *mask); + size_t size, size_t min_alloc_size, char *mask, zend_long hugepage_size); /* * apc_sma_detach will detach from shared memory and cleanup local allocations. diff --git a/package.xml b/package.xml index 157585cc..27a9cfd9 100644 --- a/package.xml +++ b/package.xml @@ -90,6 +90,12 @@ + + + + + + diff --git a/php_apc.c b/php_apc.c index 3aff354a..a00a04e1 100644 --- a/php_apc.c +++ b/php_apc.c @@ -116,6 +116,33 @@ static PHP_INI_MH(OnUpdateShmSize) /* {{{ */ } /* }}} */ +#if defined(APC_MMAP) +static PHP_INI_MH(OnUpdateMmapHugepageSize) /* {{{ */ +{ + zend_long s; + +#if PHP_VERSION_ID >= 80200 + s = zend_ini_parse_quantity_warn(new_value, entry->name); +#else + s = zend_atol(new_value->val, new_value->len); +#endif + + if (s < 0) { + php_error_docref(NULL, E_CORE_ERROR, "apc.mmap_hugepage_size must be a positive integer"); + return FAILURE; + } + + if (s & (s - 1)) { + php_error_docref(NULL, E_CORE_ERROR, "apc.mmap_hugepage_size must be a power of 2"); + return FAILURE; + } + + APCG(mmap_hugepage_size) = s; + return SUCCESS; +} +/* }}} */ +#endif + PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("apc.enabled", "1", PHP_INI_SYSTEM, OnUpdateBool, enabled, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.shm_size", "32M", PHP_INI_SYSTEM, OnUpdateShmSize, shm_size, zend_apcu_globals, apcu_globals) @@ -124,7 +151,8 @@ STD_PHP_INI_ENTRY("apc.gc_ttl", "3600", PHP_INI_SYSTEM, OnUpdateLong, STD_PHP_INI_ENTRY("apc.ttl", "0", PHP_INI_SYSTEM, OnUpdateLong, ttl, zend_apcu_globals, apcu_globals) STD_PHP_INI_ENTRY("apc.smart", "0", PHP_INI_SYSTEM, OnUpdateLong, smart, zend_apcu_globals, apcu_globals) #ifdef APC_MMAP -STD_PHP_INI_ENTRY("apc.mmap_file_mask", NULL, PHP_INI_SYSTEM, OnUpdateString, mmap_file_mask, zend_apcu_globals, apcu_globals) +STD_PHP_INI_ENTRY("apc.mmap_file_mask", NULL, PHP_INI_SYSTEM, OnUpdateString, mmap_file_mask, zend_apcu_globals, apcu_globals) +STD_PHP_INI_ENTRY("apc.mmap_hugepage_size", "0", PHP_INI_SYSTEM, OnUpdateMmapHugepageSize, mmap_hugepage_size, zend_apcu_globals, apcu_globals) #endif STD_PHP_INI_BOOLEAN("apc.enable_cli", "0", PHP_INI_SYSTEM, OnUpdateBool, enable_cli, zend_apcu_globals, apcu_globals) STD_PHP_INI_BOOLEAN("apc.slam_defense", "0", PHP_INI_SYSTEM, OnUpdateBool, slam_defense, zend_apcu_globals, apcu_globals) @@ -219,10 +247,12 @@ static PHP_MINIT_FUNCTION(apcu) if (APCG(enabled)) { if (!APCG(initialized)) { -#ifdef APC_MMAP - char *mmap_file_mask = APCG(mmap_file_mask); -#else char *mmap_file_mask = NULL; + zend_long mmap_hugepage_size = 0; + +#ifdef APC_MMAP + mmap_file_mask = APCG(mmap_file_mask); + mmap_hugepage_size = APCG(mmap_hugepage_size); #endif /* ensure this runs only once */ @@ -231,7 +261,7 @@ static PHP_MINIT_FUNCTION(apcu) /* initialize shared memory allocator */ apc_sma_init( &apc_sma, (void **) &apc_user_cache, (apc_sma_expunge_f) apc_cache_default_expunge, - APCG(shm_size), APC_ENTRY_SIZE(0), mmap_file_mask); + APCG(shm_size), APC_ENTRY_SIZE(0), mmap_file_mask, mmap_hugepage_size); REGISTER_LONG_CONSTANT(APC_SERIALIZER_CONSTANT, (zend_long)&_apc_register_serializer, CONST_PERSISTENT | CONST_CS); diff --git a/tests/apc_mmap_hugepage_001.phpt b/tests/apc_mmap_hugepage_001.phpt new file mode 100644 index 00000000..54e2b4ad --- /dev/null +++ b/tests/apc_mmap_hugepage_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +Disable hugepage +--SKIPIF-- + +--INI-- +apc.enabled=1 +apc.enable_cli=1 +apc.mmap_hugepage_size=0 +apc.shm_size=2M +--FILE-- +===DONE=== +--EXPECT-- +===DONE=== diff --git a/tests/apc_mmap_hugepage_002.phpt b/tests/apc_mmap_hugepage_002.phpt new file mode 100644 index 00000000..106e6abf --- /dev/null +++ b/tests/apc_mmap_hugepage_002.phpt @@ -0,0 +1,28 @@ +--TEST-- +Enable hugepage +--SKIPIF-- + +--INI-- +apc.enabled=1 +apc.enable_cli=1 +apc.mmap_hugepage_size=2M +apc.shm_size=2M +--FILE-- +===DONE=== +--EXPECT-- +===DONE=== diff --git a/tests/apc_mmap_hugepage_003.phpt b/tests/apc_mmap_hugepage_003.phpt new file mode 100644 index 00000000..0fdfd9bf --- /dev/null +++ b/tests/apc_mmap_hugepage_003.phpt @@ -0,0 +1,28 @@ +--TEST-- +Error if apc.mmap_hugepage_size is negative +--SKIPIF-- + +--INI-- +apc.enabled=1 +apc.enable_cli=1 +apc.mmap_hugepage_size=-1 +apc.shm_size=2M +--FILE-- +Irrelevant +--EXPECTF-- +%A: apc.mmap_hugepage_size must be a positive integer in Unknown on line 0 diff --git a/tests/apc_mmap_hugepage_004.phpt b/tests/apc_mmap_hugepage_004.phpt new file mode 100644 index 00000000..8505c4f7 --- /dev/null +++ b/tests/apc_mmap_hugepage_004.phpt @@ -0,0 +1,28 @@ +--TEST-- +Error if apc.mmap_hugepage_size is not a power of 2 +--SKIPIF-- + +--INI-- +apc.enabled=1 +apc.enable_cli=1 +apc.mmap_hugepage_size=1000 +apc.shm_size=2M +--FILE-- +Irrelevant +--EXPECTF-- +%A: apc.mmap_hugepage_size must be a power of 2 in Unknown on line 0 diff --git a/tests/apc_mmap_hugepage_005.phpt b/tests/apc_mmap_hugepage_005.phpt new file mode 100644 index 00000000..d97294e7 --- /dev/null +++ b/tests/apc_mmap_hugepage_005.phpt @@ -0,0 +1,28 @@ +--TEST-- +Error if apc.shm_size is not a multiple of apc.mmap_hugepage_size +--SKIPIF-- + +--INI-- +apc.enabled=1 +apc.enable_cli=1 +apc.mmap_hugepage_size=2M +apc.shm_size=3M +--FILE-- +Irrelevant +--EXPECTF-- +%A: apc.shm_size must be a multiple of apc.mmap_hugepage_size in Unknown on line 0 diff --git a/tests/apc_mmap_hugepage_006.phpt b/tests/apc_mmap_hugepage_006.phpt new file mode 100644 index 00000000..a5dc1105 --- /dev/null +++ b/tests/apc_mmap_hugepage_006.phpt @@ -0,0 +1,28 @@ +--TEST-- +Error if apc.mmap_hugepage_size is too small +--SKIPIF-- + +--INI-- +apc.enabled=1 +apc.enable_cli=1 +apc.mmap_hugepage_size=1 +apc.shm_size=2M +--FILE-- +Irrelevant +--EXPECTF-- +%A: Invalid hugepage size: %d in Unknown on line 0 From 4292854263085de533792d32bebd8cb925536760 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 3 Jun 2025 21:54:08 +0200 Subject: [PATCH 067/101] Disable Windows CI for PHP 7.x (#565) This requires windows-2019, which is being phased out: actions/runner-images#12045 --- .github/workflows/config.yml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index c667a977..1c905f8b 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -33,22 +33,10 @@ jobs: shell: cmd strategy: matrix: - version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] + version: ["8.0", "8.1", "8.2", "8.3", "8.4"] arch: [x64] ts: [nts, ts] - os: [windows-2019, windows-2022] - exclude: - - { os: windows-2019, version: "8.4" } - - { os: windows-2019, version: "8.3" } - - { os: windows-2019, version: "8.2" } - - { os: windows-2019, version: "8.1" } - - { os: windows-2019, version: "8.0" } - - { os: windows-2022, version: "7.4" } - - { os: windows-2022, version: "7.3" } - - { os: windows-2022, version: "7.2" } - - { os: windows-2022, version: "7.1" } - - { os: windows-2022, version: "7.0" } - runs-on: ${{matrix.os}} + runs-on: windows-2022 steps: - name: Checkout apcu uses: actions/checkout@v4 From 01e5c4dec29dbb5295f800ea7da7297549a193b2 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sat, 7 Jun 2025 00:00:52 +0200 Subject: [PATCH 068/101] Fix Linux CI for PHP 7.x to not silently abort tests (#567) All PHP 7.x Linux pipelines silently aborted the tests because the PHP executable could not be found. This was caused by the definition of "PHP_EXECUTABLE = NONE" in the Makefile, as "php-config --php-binary" returned "NONE" during ./configure. Since it seems time-consuming to fix the root cause, this is a temporary workaround until PHP 7.x is removed from the CI pipeline. --- .github/workflows/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 1c905f8b..23145290 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -19,6 +19,8 @@ jobs: run: phpize - name: configure run: ./configure --enable-apcu-debug + - name: Fix missing PHP_EXECUTABLE in Makefile for PHP 7.x + run: sed -i -e 's/^PHP_EXECUTABLE = NONE$/PHP_EXECUTABLE = \/usr\/bin\/php/' Makefile - name: make run: make - name: test From ebd7a02d236d8c3020b142b0455624b26ce0639a Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sun, 8 Jun 2025 17:26:51 +0200 Subject: [PATCH 069/101] Update atime and statistics when accessing entries with apcu_exists() (#564) The access time and access statistics are now updated when using apcu_exists(). The documentation does not indicate that this function behaves differently than apcu_fetch() in this regard. This should also help to use values other than 0 for apc.ttl, since entries frequently checked with apcu_exists() no longer soft-expire. --- apc_cache.c | 2 +- tests/apc_020.phpt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 55765b0b..2ef4cbe2 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -880,7 +880,7 @@ PHP_APCU_API zend_bool apc_cache_exists(apc_cache_t* cache, zend_string *key, ti return 0; } - entry = apc_cache_rlocked_find_nostat(cache, key, t); + entry = apc_cache_rlocked_find(cache, key, t); apc_cache_runlock(cache); return entry != NULL; diff --git a/tests/apc_020.phpt b/tests/apc_020.phpt index 7c74ee3d..375863fd 100644 --- a/tests/apc_020.phpt +++ b/tests/apc_020.phpt @@ -14,10 +14,9 @@ apc.shm_size=1M --FILE-- Date: Sun, 8 Jun 2025 17:44:31 +0200 Subject: [PATCH 070/101] Update changelog --- package.xml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 27a9cfd9..5c4b58f7 100644 --- a/package.xml +++ b/package.xml @@ -39,7 +39,27 @@ PHP License -- TBD + - If the cache is full, try to clean up expired entries based on their per-entry hard TTL even + if the soft apc.ttl is 0. Previously the entire cache was discarded. + - If a new entry cannot be inserted due to fragmentation, the cache will be defragmented, + combining many small free blocks into one big free block by moving around cache entries. + This avoids the need to discard the entire cache in more cases. + - The access time (which is used by the soft apc.ttl) is now also updated when using + apcu_exists(). + - apc.entries_hint now defaults to 512 entries per 1MB of shared memory. Previously the + default was 4096, independent of shm_size. This could lead to a large number of hash + collisions if shm_size was increased without also increasing entries_hint. + - Added apc.mmap_hugepage_size to use huge pages of a certain size for the apcu shared memory + segment. This requires support for huge pages to be enabled in the kernel. Note that even if + this option is not set, shaed memory is still configured to use transparent huge pages. + - The apc.shm_segments ini option has been removed. Multiple SHM segments are no longer + supported. (They were already not supported when using mmap, which is the default mode of + operation) + + Internal changes: + - Fixed -Wclobbered compiler warnings. + - All cache data structures are now relocatable, i.e. independent of the base address of the + cache. This enables defragmentation support. From 51b745da08694dcbf196fa2511d0f07b48acab4b Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Mon, 9 Jun 2025 16:40:57 +0200 Subject: [PATCH 071/101] Update changelog (#569) --- package.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 5c4b58f7..ba3392dd 100644 --- a/package.xml +++ b/package.xml @@ -51,15 +51,22 @@ collisions if shm_size was increased without also increasing entries_hint. - Added apc.mmap_hugepage_size to use huge pages of a certain size for the apcu shared memory segment. This requires support for huge pages to be enabled in the kernel. Note that even if - this option is not set, shaed memory is still configured to use transparent huge pages. + this option is not set, shared memory is still configured to use transparent huge pages. - The apc.shm_segments ini option has been removed. Multiple SHM segments are no longer supported. (They were already not supported when using mmap, which is the default mode of operation) + - The apc.smart configuration setting should now work more reliably. Values > 1 can be used + to increase the chance of discarding the entire cache when the amount of memory freed by + removing expired entries was too small. This could be useful if performance degrades due to + executing the logic to remove expired entries (+ defragmentation) too frequently during + periods of high memory usage. + - Fixed several issues that caused inserting new entries to fail unexpectedly. Internal changes: - Fixed -Wclobbered compiler warnings. - All cache data structures are now relocatable, i.e. independent of the base address of the cache. This enables defragmentation support. + - Hash slots now use doubly linked lists. This is necessary for defragmentation. From 66150a135bed6f1630d15a01e19dc7851504cfc3 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sat, 14 Jun 2025 20:56:15 +0200 Subject: [PATCH 072/101] Add cache cleanup and defragmentation counts to apcu_cache_info() (#570) The cache cleanup and defragmentation counts are now available in the array returned by apcu_cache_info(). --- apc.php | 2 ++ apc_cache.c | 14 +++++++++++++- apc_cache.h | 4 +++- package.xml | 4 ++++ tests/apc_defrag.phpt | 32 ++++++++++++++++++++++++-------- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/apc.php b/apc.php index dddb4544..afe7b8e5 100644 --- a/apc.php +++ b/apc.php @@ -804,6 +804,8 @@ function block_sort($array1, $array2) Hit Rate$hit_rate_user cache requests/second Miss Rate$miss_rate_user cache requests/second Insert Rate$insert_rate_user cache requests/second + Cache cleanup count{$cache['cleanups']} + Cache defragmentation count{$cache['defragmentations']} Cache full count{$cache['expunges']} diff --git a/apc_cache.c b/apc_cache.c index 2ef4cbe2..521ea020 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -344,6 +344,8 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser cache->header->nhits = 0; cache->header->nmisses = 0; cache->header->nentries = 0; + cache->header->ncleanups = 0; + cache->header->ndefragmentations = 0; cache->header->nexpunges = 0; cache->header->gc = 0; cache->header->stime = time(NULL); @@ -765,6 +767,8 @@ PHP_APCU_API void apc_cache_clear(apc_cache_t* cache) /* set info */ cache->header->stime = apc_time(); + cache->header->ncleanups = 0; + cache->header->ndefragmentations = 0; cache->header->nexpunges = 0; apc_cache_wunlock(cache); @@ -794,6 +798,9 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) * so expunge() is called less often when memory is low. */ size = (cache->smart > 0L) ? (size_t) (cache->smart * size) : size; + /* increment cache cleanup statistics (removal of expired entries) */ + cache->header->ncleanups++; + /* look for junk */ for (i = 0; i < cache->nslots; i++) { uintptr_t *entry_offset = &cache->slots[i]; @@ -820,6 +827,9 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) return; } + /* increment defragmentation statistics */ + cache->header->ndefragmentations++; + /* run defragmentation to coalesce free blocks */ apc_sma_defrag(cache->sma, cache, (apc_sma_move_f)apc_cache_wlocked_move_entry); @@ -1094,7 +1104,9 @@ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool add_assoc_double(info, "num_misses", (double) cache->header->nmisses); add_assoc_double(info, "num_inserts", (double) cache->header->ninserts); add_assoc_long(info, "num_entries", cache->header->nentries); - add_assoc_double(info, "expunges", (double) cache->header->nexpunges); + add_assoc_long(info, "cleanups", cache->header->ncleanups); + add_assoc_long(info, "defragmentations", cache->header->ndefragmentations); + add_assoc_long(info, "expunges", cache->header->nexpunges); add_assoc_long(info, "start_time", cache->header->stime); array_add_double(info, apc_str_mem_size, (double) cache->header->mem_size); diff --git a/apc_cache.h b/apc_cache.h index 9fcf9ef9..a9c60803 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -71,7 +71,9 @@ typedef struct _apc_cache_header_t { zend_long nhits; /* hit count */ zend_long nmisses; /* miss count */ zend_long ninserts; /* insert count */ - zend_long nexpunges; /* expunge count */ + zend_long ncleanups; /* default expunge count */ + zend_long ndefragmentations; /* defragmentation count */ + zend_long nexpunges; /* real expunge count */ zend_long nentries; /* entry count */ zend_long mem_size; /* used */ time_t stime; /* start time */ diff --git a/package.xml b/package.xml index ba3392dd..77e8ff15 100644 --- a/package.xml +++ b/package.xml @@ -60,6 +60,10 @@ removing expired entries was too small. This could be useful if performance degrades due to executing the logic to remove expired entries (+ defragmentation) too frequently during periods of high memory usage. + - The number of cache cleanups performed (removal of expired entries) is now available + in the array returned by apcu_cache_info() (via array key "cleanups"). + - The number of defragmentations performed is now available in the array returned by + apcu_cache_info() (via array key "defragmentations"). - Fixed several issues that caused inserting new entries to fail unexpectedly. Internal changes: diff --git a/tests/apc_defrag.phpt b/tests/apc_defrag.phpt index 38a44dea..162b6821 100644 --- a/tests/apc_defrag.phpt +++ b/tests/apc_defrag.phpt @@ -16,15 +16,15 @@ apc.shm_size=1M // fill_cache() fills the cache with small entries with alternating ttl=1 and ttl=3 function fill_cache(): int { $i = 0; - $min_entry_size = apcu_sma_info(true)['avail_mem']; + $entry_size = apcu_sma_info(true)['avail_mem']; apcu_store(sprintf("ttl1_%010d", $i), $i, 1); - $min_entry_size -= apcu_sma_info(true)['avail_mem']; + $entry_size -= apcu_sma_info(true)['avail_mem']; - while (apcu_sma_info(true)['avail_mem'] >= $min_entry_size) { + while (apcu_sma_info(true)['avail_mem'] >= $entry_size) { $i++; apcu_store(sprintf("ttl3_%010d", $i), $i, 3); - if (apcu_sma_info(true)['avail_mem'] >= $min_entry_size) { + if (apcu_sma_info(true)['avail_mem'] >= $entry_size) { apcu_store(sprintf("ttl1_%010d", $i), $i, 1); } } @@ -48,9 +48,6 @@ $avail_before_filled = apcu_sma_info(true)['avail_mem']; // fill cache with alternating ttl=1 + ttl=3 entries fill_cache(); -// ensure that cache is full -var_dump(apcu_sma_info(true)['avail_mem']); - // expire all ttl1_* entries apcu_inc_request_time(2); @@ -72,9 +69,28 @@ var_dump(apcu_fetch("ttl3_string") === "abc"); var_dump(apcu_fetch("ttl3_array") === [1, 2, "a", "b"]); var_dump(apcu_fetch("ttl3_object") == (object) ["prop1" => "val1", "prop2" => 2]); +// check that cache cleanup and defragmentation have been performed, but no real expunge +var_dump(apcu_cache_info(true)["cleanups"] === 1); +var_dump(apcu_cache_info(true)["defragmentations"] === 1); +var_dump(apcu_cache_info(true)["expunges"] === 0); + +// this insertion should trigger an default_expunge which performs a real expunge (but no defragmentation) +var_dump(apcu_store("huge_entry", str_repeat('x', 700000), 1)); + +// check that cache cleanup and real expunge have been performed, but no defragmentation +var_dump(apcu_cache_info(true)["cleanups"] === 2); +var_dump(apcu_cache_info(true)["defragmentations"] === 1); +var_dump(apcu_cache_info(true)["expunges"] === 1); + ?> --EXPECT-- -float(0) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) bool(true) bool(true) bool(true) From 322dceab88483aaee12f135d244171520dc45f91 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sat, 12 Jul 2025 10:07:33 +0200 Subject: [PATCH 073/101] Update TECHNOTES (#574) A large portion of the TECHNOTES no longer reflected the current state of apcu. The TECHNOTES have therefore been revised to make it easier to get started. Since the copies of the struct definitions are not required to provide a general overview, they have been removed to avoid duplicate maintenance. --- TECHNOTES.txt | 424 +++++++++++++++++++------------------------------- 1 file changed, 162 insertions(+), 262 deletions(-) diff --git a/TECHNOTES.txt b/TECHNOTES.txt index bd6a19dd..9a4a4191 100644 --- a/TECHNOTES.txt +++ b/TECHNOTES.txt @@ -3,21 +3,20 @@ APCu Quick-Start Braindump This is a rapidly written braindump of how APCu currently works in the form of a quick-start guide to start hacking on APCu. -1. Install and use APC a bit so you know what it does from the end-user's - perspective. - user-space functions are all explained here: https://www.php.net/apcu +1. Install and use APCu a bit so you know what it does from the end-user's + perspective. PHP functions are all explained here: https://www.php.net/apcu -2. Grab the current APC code from https://github.com/krakjoe/apcu - - apcu/php_apc.c has most of the code for the user-visible stuff. It is - also a regular PHP extension in the sense that there are MINIT, MINFO, - MSHUTDOWN, RSHUTDOWN, etc. functions. +2. Grab the current APCu code from https://github.com/krakjoe/apcu + + Most of the user-visible stuff is implemented in apcu/php_apc.c. + It is also a regular PHP extension in the sense that there are MINIT, MINFO, + MSHUTDOWN, RSHUTDOWN, etc. functions. 3. Build it. cd apcu phpize - ./configure --enable-apcu + ./configure --enable-apcu --enable-apcu-debug make make test cp modules/apcu.so /usr/local/lib/php @@ -25,304 +24,205 @@ form of a quick-start guide to start hacking on APCu. 4. Debugging Hints - apachectl stop - gdb /usr/bin/httpd - break ?? - run -X + apachectl stop + gdb /usr/bin/httpd + break ?? + run -X Grab the .gdbinit from the PHP source tree and have a look at the macros. 5. The basics of APCu - APCu has three main component parts: - 1) shared memory allocator - 2) pooling - 3) user land cache + The caching functionality of APCu is provided by a modified version of the APC source code. + Many tweaks have been applied. There's probably some of my blood in it, if you look real close... (krakjoe) -5.1) APCu SMA - - It is a pretty standard memory allocator, now supporting third party extensions. + APCu has the following main component parts: + 1) shared memory allocator / SMA (apc_sma.c) + 2) user land cache (apc_cache.c) + 3) persistence representation (apc_persist.c) - apc_sma_malloc and apc_sma_free behave to the caller just like malloc and free, - they are generated from macros in apc_sma.h +5.1 SMA - Note: apc_sma.h is formatted and designed such that the SMA APCu - uses can be used by third parties in their own extensions without - interfering with, or consuming the resources of APCu itself + The SMA (shared memory allocator) is a pretty standard memory allocator. It provides + apc_sma_malloc() and apc_sma_free() which behave just like malloc() and free(). - apc_sma is a structure of type apc_sma_t, it is statically allocated at runtime, - appropriate handlers are generated and set, and the structure made ready for initialization. + The SMA is designed such that the SMA can be used by third parties in their own extensions + without interfering with or consuming the resources of APCu itself. - MINIT then initializes apc_sma with apc_sma_api_init(). - APCu SMA then takes care of mmaping the shared memory. - ( which you can obtain in any compilation unit with apc_sma_api_extern(apc_sma) ) + MINIT calls apc_sma_init(), which takes care of mapping and initializing the shared memory segment. + It initializes the smaheader (sma_header_t) at the beginning of the shared memory. The smaheader + serves as a place to store, among other things, statistical information and the lock for the SMA. - At this point, we have a completely useless 32MB chunk of memory at our disposal, before - it can be used, a sma_header_t is initialized at the beginning of the region of - mmapp'ed memory. - - The smaheader serves as a place to store, among other things, statistical information and a lock. - - Immediately after the smaheader come three blocks. The first and last block are 0-sized - and simplify the handling of the linked list. The block between the 0-sized blocks - contains the remaining size of the shared memory which is available for allocation. + Immediately after the smaheader it initializes three blocks. The first and last block are 0-sized + and simplify the handling of the linked list of free blocks and sequential traversal of blocks. + The block between the 0-sized blocks contains the remaining amount of shared memory available + for allocation. - At this point, the shared memory looks like this: + At this point, the shared memory looks like this: - +--------+--------+-----------------------------------+ - | header | 0-size | free-block | 0-size | - +--------+--------+-----------------------------------+ + +-----------+--------+-----------------------------------+ + | smaheader | 0-size | free-block | 0-size | + +-----------+--------+-----------------------------------+ - The blocks are just a simple offset-based doubly linked list (so no pointers): + These three blocks (type block_t) form the initial doubly linked list of free blocks. + Since the whole SMA is implemented relocatable (independent of the starting address + of the shared memory segment), this list is offset-based (so no pointers). - typedef struct block_t block_t; - struct block_t { - size_t size; /* size of this block */ - size_t prev_size; /* size of sequentially previous block, 0 if prev is allocated */ - size_t fnext; /* offset in segment of next free block */ - size_t fprev; /* offset in segment of prev free block */ -#ifdef APC_SMA_CANARIES - size_t canary; /* canary to check for memory overwrites */ -#endif - }; + The macros BLOCKAT and OFFSET are used to simplify the handling of the offset-based blocks: - The BLOCKAT macro turns an offset into an actual address for you: + - The BLOCKAT macro turns an offset into an actual process-local address/pointer: #define BLOCKAT(offset) ((block_t*)((char *)smaheader + offset)) - where smaheader = sma->shaddrs[0] - - And the OFFSET macro goes the other way: + - The OFFSET macro goes the other way: #define OFFSET(block) ((int)(((char*)block) - (char*)smaheader)) - Allocating a block (`sma_allocate`) walks through the doubly linked list of blocks until it - finds one that is >= to the requested size. The first call to allocate will hit the second block. - We then chop up that block so it looks like this: + Both macros assume the presence of the variable "smaheader" that points to the beginning + of the shared memory segment. + + To allocate a block via apc_sma_malloc(), we walk through the doubly linked list of blocks until we + find one that is >= to the requested size (see find_block()). The first call to find_block() will hit + the second block. To get a block of the requested size, we then chop up that block so it looks like this: - +--------+--------+-------+------------------+--------+ - | header | 0-size | block | free-block | 0-size | - +--------+--------+-------+------------------+--------+ + +-----------+--------+-------+------------------+--------+ + | smaheader | 0-size | block | free-block | 0-size | + +-----------+--------+-------+------------------+--------+ Then we unlink that block from the doubly linked list so it won't show up - as an available block on the next allocate. So we actually have: + as an available block on the next allocation. So we actually have: - +--------+--------+ +------------------+--------+ - | header | 0-size |<----->| free-block | 0-size | - +--------+--------+ +------------------+--------+ + +-----------+--------+ +------------------+--------+ + | smaheader | 0-size |<----->| free-block | 0-size | + +-----------+--------+ +------------------+--------+ And smaheader->avail along with block->size of the remaining large free block are updated accordingly. The arrow there represents the link which now points to a block with an offset further along in the segment. - When the block is freed the steps are basically just reversed. + When the block is freed, the steps are basically just reversed. The block is put back and then the deallocate code looks at the block before and after to see - if the block immediately before and after are free and if so the blocks are combined. So you never - have 2 free blocks next to each other, apart from the 0-sized dummy blocks. + if the blocks immediately before or after are free, and if so, the blocks are combined. + So we never have 2 free blocks next to each other, apart from the 0-sized dummy blocks. This mostly prevents fragmentation. Block start pointers are aligned to the system's word boundary (usually 8 bytes) with the `ALIGNWORD` macro. -5.2) APCu Cache - - The caching functionality of APCu is provided by a modified version of the APC source code - - Some simple tweaks have been applied: - Locking is written to use the best kind of locking available, and emulate it where it is not to simplify logic. - Extension of the SMA to support multiple instances, such that additional caches using APCu do not - increase contention of the main APCu cache. - The possibility to control more finely what happens when resources become low for APCu. - An exposed, coherent, and documented API and example included in the distribution. - - There's probably some of my blood in it, if you look real close ... - - The remainder of the document goes on to explain in some detail the cache itself, functionally unchanged by APCu - -6. Next up is apc_cache.c which implements the cache logic. - - Having initialized a suitable allocator, MINIT must call apc_cache_create, using the allocator provided - APCu will create a cache. The parameters to apc_cache_create for APCu are defined by various INI settings. - API users can provide the same options from anywhere ( their globals for example ). - - The cache is stored in/described by this struct allocated locally: - - /* {{{ struct definition: apc_cache_t */ - typedef struct _apc_cache_t { - apc_cache_header_t* header; /* cache header (stored in SHM) */ - uintptr_t* slots; /* array of cache slots (stored in SHM) */ - apc_sma_t* sma; /* shared memory allocator */ - apc_serializer_t* serializer; /* serializer */ - size_t nslots; /* number of slots in cache */ - zend_long gc_ttl; /* maximum time on GC list for a entry */ - zend_long ttl; /* if slot is needed and entry's access time is older than this ttl, remove it */ - zend_long smart; /* smart parameter for gc */ - zend_bool defend; /* defense parameter for runtime */ - } apc_cache_t; /* }}} */ - - Whenever you see functions that take a 'cache' argument, this is what they - take. - - At the beginning of the cache we have a header. The header looks like this: - - /* {{{ struct definition: apc_cache_header_t - Any values that must be shared among processes should go in here. */ - typedef struct _apc_cache_header_t { - apc_lock_t lock; /* header lock */ - zend_long nhits; /* hit count */ - zend_long nmisses; /* miss count */ - zend_long ninserts; /* insert count */ - zend_long nexpunges; /* expunge count */ - zend_long nentries; /* entry count */ - zend_long mem_size; /* used */ - time_t stime; /* start time */ - apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */ - uintptr_t gc; /* offset in shm to the first entry of gc list */ - } apc_cache_header_t; /* }}} */ - - Since this is at the start of the shared memory segment, these values are accessible - across all processes / threads and hence access to them has to be locked. - - After the header we have an array of slots. The number of slots is user-defined - through the apc.entries_hint ini hint. Each slot is described by: - - /* {{{ struct definition: apc_cache_slot_t */ - typedef struct apc_cache_slot_t apc_cache_slot_t; - struct apc_cache_slot_t { - apc_cache_key_t key; /* slot key */ - apc_cache_entry_t* value; /* slot value */ - apc_cache_slot_t* next; /* next slot in linked list */ - zend_ulong nhits; /* number of hits to this slot */ - time_t ctime; /* time slot was initialized */ - time_t dtime; /* time slot was removed from cache */ - time_t atime; /* time slot was last accessed */ - }; - /* }}} */ - - The apc_cache_slot_t *next there is a linked list to other slots that happened to hash to the - same array position. - - apc_cache_store_internal() shows what happens on a new cache insert. +5.2 Cache - /* calculate hash and entry */ - apc_cache_hash_slot(cache, key, &h, &s); + Next up is apc_cache.c which implements the cache logic. + + Having initialized the shared memory allocator (SMA), MINIT calls apc_cache_create() to initialize + the cache. The parameters to apc_cache_create() for APCu are mostly defined by various INI settings. + + The function apc_cache_create() allocates and returns a struct of the type apc_cache_t, which describes + the created cache. Whenever you see functions that take a 'cache' argument, this is what they take. + + In addition, apc_cache_create() allocates and initializes shared memory for the cache header + (apc_cache_header_t) and the hash slots of the hash table. Since the header and the hash slots are + in the shared memory, these values are accessible across all processes / threads and hence access + to it has to be locked. For this purpose, the header contains a read / write lock that allows access + to either multiple parallel readers or only one writer. + + The number of hash slots is computed / user-defined through the apc.entries_hint ini value. + Therefore, the size of the hash slot array in shared memory can vary. + + Each hash slot consists of a doubly linked list of entries. Because the cache layer is implemented + relocatable (independent of the shared memory's starting address), these doubly linked lists + use offsets relative to the starting address of the cache header instead of pointers. For this reason, + the array of hash slots is just an array of uintptr_t values, each containing an offset to the + first entry of the doubly linked list which belongs to this slot (a value of 0 means the slot is empty). + + The macros ENTRYAT and ENTRYOF are used to convert between offsets and pointers to cache entries. + Both expect the presence of cache->header that points to the cache header in the shared memory segment: - entry = &cache->slots[s]; + #define ENTRYAT(offset) ((apc_cache_entry_t *)((uintptr_t)cache->header + (uintptr_t)offset)) + #define ENTRYOF(entry) (((uintptr_t)entry) - (uintptr_t)cache->header) - cache->slots is our array of slots in the segment. + In Addition, the functions apc_cache_wlocked_link_entry() and apc_cache_wlocked_unlink_entry() + help inserting or removing entries into or from doubly linked lists. Using them, you don't have to + worry about offsets when adding or removing entries from lists. - So, on an insert we find the array position in the slots array by hashing the key provided. - If there are currently no other slots there, we just stick the created `apc_cache_entry_t` into the array. + apc_cache_wlocked_insert() shows what happens when a cache entry is inserted: - while (*entry) { - /* process expired entries and check for entry with matching key */ + /* calculate hash and entry */ + apc_cache_hash_slot(cache, key, &h, &s); + + uintptr_t *entry_offset = &cache->slots[s]; + + while (*entry_offset) { + apc_cache_entry_t *entry = ENTRYAT(*entry_offset); + /* process expired entries and check for entry with matching key */ } + /* link in new entry */ - new_entry->next = *entry; - *entry = new_entry; - - If there are other slots already at this position we walk the link list to get to - the end. - - While walking the linked list we also check to see if the cache has a TTL defined. - If while walking the linked list we see a slot that has expired, we remove it - since we are right there looking at it. This is the only place we remove stale - entries unless the shared memory segment fills up and we force a full expunge via - apc_cache_expunge(). apc_cache_expunge() walks all slots attempting deletion, how - deletion occurs depends on runtime parameters, see INSTALL for runtime parameter - configuration details. - - apc_cache_rlocked_find() simply hashes and returns the entry if it is there. If it is there - but older than the mtime in the entry we are looking for, we return indicating we didn't find it. - - API users are advised to use apc_cache_fetch over find for simplicity, this ensures - correct operation, fetch sets up the call to find and takes care of copying and releasing - the entry from the cache to a zval* provided. - - Next we need to understand what an actual cache entry looks like. Have a look at - apc_cache.h for the structs. Here is the definition of apc_cache_key_t: - - /* {{{ struct definition: apc_cache_key_t */ - typedef struct _apc_cache_key_t { - const char *str; /* pointer to constant string key */ - zend_uint len; /* length of data at str */ - zend_ulong h; /* pre-computed hash of key */ - time_t mtime; /* the mtime of this cached entry */ - apc_cache_owner_t owner; /* the context that created this key */ - } apc_cache_key_t; /* }}} */ - - To create a apc_cache_key_t structure, call apc_cache_make_key(), see apc_cache.h - - Ok, on to the actual cache entry, here is the definition of apc_cache_entry_t: - - /* {{{ struct definition: apc_cache_entry_t */ - typedef struct apc_cache_entry_t apc_cache_entry_t; - struct apc_cache_entry_t { - uintptr_t next; /* offset to next entry (MUST BE THE 1st FIELD OF THE STRUCT!) */ - uintptr_t prev; /* offset to previous entry / head-pointer of the linked list */ - zend_long ttl; /* the ttl on this specific entry */ - zend_long ref_count; /* the reference count of this entry */ - zend_long nhits; /* number of hits to this entry */ - time_t ctime; /* time entry was initialized */ - time_t mtime; /* the mtime of this cached entry */ - time_t dtime; /* time entry was removed from cache */ - time_t atime; /* time entry was last accessed */ - zend_long mem_size; /* memory used */ - zval val; /* the zval copied at store time */ - zend_string key; /* entry key (MUST BE THE LAST FIELD OF THE STRUCT!) */ - }; - /* }}} */ - - To create an apc_cache_entry_t, call apc_cache_make_entry(), see apc_cache.h - - Any of the structures taken by apc_cache_* functions have their equivalent apc_cache_make_* - - If an insertion of an entry should fail, it falls to the caller of insert to free - the pooled resources used to create the entry. - -7. Serializers - - The way data is serialized and unserialized can be found in apc_persist.c. - Saving to shared memory (persist) is done using `apc_persist_context_t`. - Both the entry key and the entry's zval get persisted into shared memory in a continuous block - for the single entry. - - typedef struct _apc_persist_context_t { - /* Serializer to use */ - apc_serializer_t *serializer; - /* Computed size of the needed SMA allocation */ - size_t size; - /* Whether or not we may have to memoize refcounted addresses */ - zend_bool memoization_needed; - /* Whether to serialize the top-level value */ - zend_bool use_serialization; - /* Serialized object/array string, in case there can only be one */ - unsigned char *serialized_str; - size_t serialized_str_len; - /* Whole SMA allocation */ - char *alloc; - /* Current position in allocation */ - char *alloc_cur; - /* HashTable storing refcounteds for which the size has already been counted. */ - HashTable already_counted; - /* HashTable storing already allocated refcounteds. Pointers to refcounteds are stored. */ - HashTable already_allocated; - } apc_persist_context_t; + apc_cache_wlocked_link_entry(cache, entry_offset, new_entry); + + During insertion, we get the array position in the hash slot array (cache->slots) by hashing + the key. If there are currently no other entries in the slot, we just link the new entry + (apc_cache_entry_t) into the slot, which results in a doubly linked list with one entry. + + If there are other entries in the slot, we walk to the end of the linked list. As we traverse + the linked list, we also remove expired entries since we are right there looking at them and + we already have a write-lock. This is the only place where we remove stale entries unless the + shared memory segment is full and we try to cleanup the cache via apc_cache_default_expunge(). + + apc_cache_rlocked_find() simply tries to return an entry by searching for the key (hash lookup + + traversing the linked list). If an entry with the same key exists but its TTL has expired, + we return that it was not found. + + Developers are encouraged to use apc_cache_fetch() instead of find for simplicity. This ensures + correct operation, because fetch sets up the read lock, the call to find, and takes care of + copying and releasing the entry from the cache to a provided zval*. + + The function apc_cache_default_expunge() removes all expired entries and defragments the shared + memory to get as much free contiguous memory as possible. If the removal of expired entries and + defragmentation doesn't free enough contiguous memory, a full cache wipe is performed + via apc_cache_wlocked_real_expunge(). + +5.3 Persistence + + Before an entry can be inserted into a linked list, it must be created (persisted) in the + shared memory segment. This is done with apc_persist() in apc_persist.c. The following steps + are required to persist an entry: + - The amount of memory needed to store the entry is calculated by apc_persist_calc(). + - The shared memory to persist the entry is allocated by apc_sma_malloc(). + - The entry is persisted in the allocated shared memory by apc_persist_create_entry(). + + The persistence representation of an entry is implemented independent of its position + in the shared memory segment, which enables us to move around entries during defragmentation + and to make the entire cache relocatable. This is achieved by converting all entry's pointers + to offsets relative to the entry's starting address during persistence. + + The function apc_unpersist() creates a process-local copy of the persisted entry's value (zval), + which can be passed to the php runtime after an entry has been found. During apc_unpersist(), + all offsets relative to the entry's starting address must be converted back to process-local + addresses / pointers before data access. It is important to note that this conversion must + occur on the stack or in process-local memory and that the shared memory representation of the + entry must not be modified, as this would break the persistence representation. + + In some cases, the value (zval) of an entry is persisted by using a serializer. Whether a + serializer is used depends primarily on the data type of the value (e.g., arrays or objects). + For simple types as null/bool/int/float/string, serializers are unnecessary and not used. The ini setting `apc.serializer` can be used to customize the `apc_serializer_t *serializer`. - This affects which serializer is used for PHP objects or arrays. - (for a top level null/bool/int/float/string, serializers are unnecessary and not used) - - - `apc.serializer=php` (default) will use serialize() and unserialize() for serializing arrays/ - This has lower memory usage than `apc.serializer=default` for most use cases - - `apc.serializer=default` is used for arrays that don't contain objects, and will store the array structure in shared memory - in a form that allows deduplicating values as well as faster unserialization of small arrays, at the cost of generally having higher memory usage. - - For arrays that contain objects, it falls back to php's native serialize()/unserialize() - - APCu can be configured to use third party serializers if they are compiled with support for apcu. - For example, `apc.serializer=igbinary` (https://github.com/igbinary/igbinary) can be used for generally faster unserialization and lower memory usage than apc.serializer=php - (requires that igbinary be configured and compiled after APCu is installed) + This affects which serializer is used (Default: apc.serializer=php). -If you made it to the end of this, you should have a pretty good idea of where things are in -the code. There is much more reading to do in headers ... good luck ... + APCu can be configured to use third-party serializers if they are compiled with support for apcu. + For example, `apc.serializer=igbinary` (https://github.com/igbinary/igbinary) can be used for + generally faster unserialization and lower memory usage than apc.serializer=php + (requires that igbinary is configured and compiled after APCu is installed) + +6. Relocatable design + + All layers of APCu are implemented in a relocatable manner. The goal of the relocatable design + is to allow independent processes to attach to the same shared memory segment in the future, even + if they use different starting addresses for the shared memory segment. This is achieved by using + offset-based addressing instead of pointers in all layers of APCu. Therefore, you will not find + any pointers in the entire shared memory segment. So, do not store pointers (absolute addresses) + in the shared memory segment, as doing so will likely break the relocatable design! + +If you made it to the end of this, you should have a pretty good idea of where things are in +the code. There is much more reading to do in headers ... good luck ... From 3bab43a0604ee045ff9bfb94b188396a7611ca1e Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sat, 12 Jul 2025 10:13:22 +0200 Subject: [PATCH 074/101] Fix relocatable representation of zend_empty_array (#575) When using the zend_empty_array optimization, the process-local address of zend_empty_array was stored in shared memory during persistence, which is inconsistent with the relocatable representation. To indicate the use of zend_empty_array during unpersist, the entry's starting address is now used during persist instead. --- apc_persist.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apc_persist.c b/apc_persist.c index 8f74e347..9f7f14e2 100644 --- a/apc_persist.c +++ b/apc_persist.c @@ -333,7 +333,8 @@ static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_I static zend_array *apc_persist_copy_ht(apc_persist_context_t *ctxt, const HashTable *orig_ht) { #if PHP_VERSION_ID >= 70300 if (orig_ht->nNumOfElements == 0) { - return (HashTable *)&zend_empty_array; + /* To indicate using zend_empty_array during unpersist, we point to the entry's starting address. */ + return (HashTable *)ctxt->alloc; } #endif HashTable *ht = COPY(orig_ht, sizeof(HashTable)); @@ -708,7 +709,8 @@ static void apc_unpersist_zval_impl(apc_unpersist_context_t *ctxt, zval *zv) { return; case IS_ARRAY: #if PHP_VERSION_ID >= 70300 - if (Z_ARR_P(zv)->nNumOfElements == 0) { + if (Z_ARR_P(zv) == (zend_array *)ctxt->alloc) { + /* If the zval points to the entry's starting address, we use the zend_empty_array optimization. */ ZVAL_EMPTY_ARRAY(zv); /* #323 */ return; } From 8e7ed9a499ded5d9ddaf8cb3aefc598e54a35489 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sat, 26 Jul 2025 17:27:51 +0200 Subject: [PATCH 075/101] Add test that a repeated unpersist doesn't break representation (#577) The purpose of this test is to ensure that the unpersist-code does not accidentally change the persistence representation in SHM (e.g., by the offset -> pointer conversion), causing a second unpersist to fail. --- package.xml | 1 + tests/apc_repeated_unpersist.phpt | 73 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/apc_repeated_unpersist.phpt diff --git a/package.xml b/package.xml index 77e8ff15..eb0dadb5 100644 --- a/package.xml +++ b/package.xml @@ -115,6 +115,7 @@ + diff --git a/tests/apc_repeated_unpersist.phpt b/tests/apc_repeated_unpersist.phpt new file mode 100644 index 00000000..da54ed82 --- /dev/null +++ b/tests/apc_repeated_unpersist.phpt @@ -0,0 +1,73 @@ +--TEST-- +APCU: Test that repeated unpersist does not break the persistence representation in SHM +--SKIPIF-- + +--INI-- +apc.enabled=1 +apc.enable_cli=1 +apc.serializer=default +apc.shm_size=1M +--FILE-- + "val1", "prop2" => 2]); +apcu_store("array_empty", []); +apcu_store("array_simple", $tmp); +apcu_store("array_nested", [$tmp]); +apcu_store("array_referenced", [&$tmp, &$tmp]); + +echo "1st unpersist:\n"; +var_dump(apcu_fetch("int") === 123); +var_dump(apcu_fetch("string") === "abc"); +var_dump(apcu_fetch("object") == (object) ["prop1" => "val1", "prop2" => 2]); +var_dump(apcu_fetch("array_empty") === []); +var_dump(apcu_fetch("array_simple") === $tmp); +var_dump(apcu_fetch("array_nested") === [$tmp]); +var_dump(apcu_fetch("array_referenced") === [&$tmp, &$tmp]); + +echo "2nd unpersist (check, if the 1st unpersist didn't break the representation in SHM):\n"; +var_dump(apcu_fetch("int") === 123); +var_dump(apcu_fetch("string") === "abc"); +var_dump(apcu_fetch("object") == (object) ["prop1" => "val1", "prop2" => 2]); +var_dump(apcu_fetch("array_empty") === []); +var_dump(apcu_fetch("array_simple") === $tmp); +var_dump(apcu_fetch("array_nested") === [$tmp]); +var_dump(apcu_fetch("array_referenced") === [&$tmp, &$tmp]); + +echo "Check if the reference has been preserved:\n"; +$tmp = apcu_fetch("array_referenced"); +$tmp[0][0] = 2; +$tmp[0][1] = "b"; +var_dump($tmp[1][0] === 2); +var_dump($tmp[1][1] === "b"); + +?> +--EXPECT-- +1st unpersist: +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +2nd unpersist (check, if the 1st unpersist didn't break the representation in SHM): +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +Check if the reference has been preserved: +bool(true) +bool(true) From 16b021c98e8bd1093621cae06313af54ab56afd2 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sun, 27 Jul 2025 20:33:50 +0200 Subject: [PATCH 076/101] Add reference and empty array to the defrag test (#576) A reference and an empty array have been added to the tested types. This allows the defragmentation test to verify whether references or the special case zend_empty_array work correctly after defragmentation. --- tests/apc_defrag.phpt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/apc_defrag.phpt b/tests/apc_defrag.phpt index 162b6821..533772b5 100644 --- a/tests/apc_defrag.phpt +++ b/tests/apc_defrag.phpt @@ -37,9 +37,10 @@ function fill_cache(): int { apcu_store("ttl1_int", 123, 1); // store entries of different datatypes with ttl=3, which must be present after expiration + defragmentation +$reference = "b"; apcu_store("ttl3_int", 123456789, 3); apcu_store("ttl3_string", "abc", 3); -apcu_store("ttl3_array", [1, 2, "a", "b"], 3); +apcu_store("ttl3_array", [1, "a", &$reference, []], 3); apcu_store("ttl3_object", (object) ["prop1" => "val1", "prop2" => 2], 3); // safe available memory for later comparison @@ -66,7 +67,7 @@ var_dump(apcu_fetch("ttl1_int") === false); // all ttl3_ entries must be present (and correct after defragmentation) var_dump(apcu_fetch("ttl3_int") === 123456789); var_dump(apcu_fetch("ttl3_string") === "abc"); -var_dump(apcu_fetch("ttl3_array") === [1, 2, "a", "b"]); +var_dump(apcu_fetch("ttl3_array") === [1, "a", &$reference, []]); var_dump(apcu_fetch("ttl3_object") == (object) ["prop1" => "val1", "prop2" => 2]); // check that cache cleanup and defragmentation have been performed, but no real expunge From 616c72d58cfc2a7ffce7512f9ae91e49d2d06ab9 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 28 Jul 2025 20:49:01 +0200 Subject: [PATCH 077/101] Release apcu 5.1.25 --- package.xml | 4 ++-- php_apc.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.xml b/package.xml index eb0dadb5..e511c65d 100644 --- a/package.xml +++ b/package.xml @@ -28,9 +28,9 @@ nikic@php.net yes - 2024-09-21 + 2025-07-28 - 5.1.25-dev + 5.1.25 5.1.18 diff --git a/php_apc.h b/php_apc.h index 3ca816fb..30622016 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.25-dev" +#define PHP_APCU_VERSION "5.1.25" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From e63ac232f0c7b2b8c57e09cdf242e876808e0618 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 28 Jul 2025 20:52:41 +0200 Subject: [PATCH 078/101] Back to dev --- package.xml | 80 +++++++++++++++++++++++++++++++---------------------- php_apc.h | 2 +- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/package.xml b/package.xml index e511c65d..7231224c 100644 --- a/package.xml +++ b/package.xml @@ -30,7 +30,7 @@ 2025-07-28 - 5.1.25 + 5.1.26-dev 5.1.18 @@ -39,38 +39,6 @@ PHP License - - If the cache is full, try to clean up expired entries based on their per-entry hard TTL even - if the soft apc.ttl is 0. Previously the entire cache was discarded. - - If a new entry cannot be inserted due to fragmentation, the cache will be defragmented, - combining many small free blocks into one big free block by moving around cache entries. - This avoids the need to discard the entire cache in more cases. - - The access time (which is used by the soft apc.ttl) is now also updated when using - apcu_exists(). - - apc.entries_hint now defaults to 512 entries per 1MB of shared memory. Previously the - default was 4096, independent of shm_size. This could lead to a large number of hash - collisions if shm_size was increased without also increasing entries_hint. - - Added apc.mmap_hugepage_size to use huge pages of a certain size for the apcu shared memory - segment. This requires support for huge pages to be enabled in the kernel. Note that even if - this option is not set, shared memory is still configured to use transparent huge pages. - - The apc.shm_segments ini option has been removed. Multiple SHM segments are no longer - supported. (They were already not supported when using mmap, which is the default mode of - operation) - - The apc.smart configuration setting should now work more reliably. Values > 1 can be used - to increase the chance of discarding the entire cache when the amount of memory freed by - removing expired entries was too small. This could be useful if performance degrades due to - executing the logic to remove expired entries (+ defragmentation) too frequently during - periods of high memory usage. - - The number of cache cleanups performed (removal of expired entries) is now available - in the array returned by apcu_cache_info() (via array key "cleanups"). - - The number of defragmentations performed is now available in the array returned by - apcu_cache_info() (via array key "defragmentations"). - - Fixed several issues that caused inserting new entries to fail unexpectedly. - - Internal changes: - - Fixed -Wclobbered compiler warnings. - - All cache data structures are now relocatable, i.e. independent of the base address of the - cache. This enables defragmentation support. - - Hash slots now use doubly linked lists. This is necessary for defragmentation. @@ -224,6 +192,52 @@ + + 2025-07-28 + + 5.1.25 + 5.1.18 + + + stable + stable + + PHP License + + - If the cache is full, try to clean up expired entries based on their per-entry hard TTL even + if the soft apc.ttl is 0. Previously the entire cache was discarded. + - If a new entry cannot be inserted due to fragmentation, the cache will be defragmented, + combining many small free blocks into one big free block by moving around cache entries. + This avoids the need to discard the entire cache in more cases. + - The access time (which is used by the soft apc.ttl) is now also updated when using + apcu_exists(). + - apc.entries_hint now defaults to 512 entries per 1MB of shared memory. Previously the + default was 4096, independent of shm_size. This could lead to a large number of hash + collisions if shm_size was increased without also increasing entries_hint. + - Added apc.mmap_hugepage_size to use huge pages of a certain size for the apcu shared memory + segment. This requires support for huge pages to be enabled in the kernel. Note that even if + this option is not set, shared memory is still configured to use transparent huge pages. + - The apc.shm_segments ini option has been removed. Multiple SHM segments are no longer + supported. (They were already not supported when using mmap, which is the default mode of + operation) + - The apc.smart configuration setting should now work more reliably. Values > 1 can be used + to increase the chance of discarding the entire cache when the amount of memory freed by + removing expired entries was too small. This could be useful if performance degrades due to + executing the logic to remove expired entries (+ defragmentation) too frequently during + periods of high memory usage. + - The number of cache cleanups performed (removal of expired entries) is now available + in the array returned by apcu_cache_info() (via array key "cleanups"). + - The number of defragmentations performed is now available in the array returned by + apcu_cache_info() (via array key "defragmentations"). + - Fixed several issues that caused inserting new entries to fail unexpectedly. + + Internal changes: + - Fixed -Wclobbered compiler warnings. + - All cache data structures are now relocatable, i.e. independent of the base address of the + cache. This enables defragmentation support. + - Hash slots now use doubly linked lists. This is necessary for defragmentation. + + 2024-09-21 diff --git a/php_apc.h b/php_apc.h index 30622016..6a26e99a 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.25" +#define PHP_APCU_VERSION "5.1.26-dev" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From 1ca8ca981c374a85921984271be8d837393aa746 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Tue, 29 Jul 2025 22:04:03 +0200 Subject: [PATCH 079/101] Use different fatal error in test for PHP 8.5 Since PHP 8.5, a missing trait is no longer a fatal error. Therefore, we must use a different fatal error. --- tests/apc_entry_003.phpt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/apc_entry_003.phpt b/tests/apc_entry_003.phpt index e04bf4bd..d074f9ae 100644 --- a/tests/apc_entry_003.phpt +++ b/tests/apc_entry_003.phpt @@ -9,7 +9,8 @@ apc.enable_cli=1 --EXPECTF-- From 459e208cd11b29d3397b2586164ee02288236524 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Tue, 29 Jul 2025 22:05:16 +0200 Subject: [PATCH 080/101] Add PHP 8.5 to Linux CI matrix --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 23145290..7cbb83d9 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -4,7 +4,7 @@ jobs: ubuntu: strategy: matrix: - version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] + version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"] runs-on: ubuntu-latest steps: - name: Set the hugepages parameter From 6b23303ae02322af3ee136532814e2e5fecfe7ae Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Mon, 4 Aug 2025 21:12:05 +0200 Subject: [PATCH 081/101] Fix stacking of default expunge operations Under high load, multiple parallel insert operations could trigger multiple default expunge operations at the same time. To prevent this, pending default expunge operations are now aborted if another default expunge operation has run in the meantime. --- apc_cache.c | 30 ++++++++++++++++++++---------- apc_cache.h | 2 +- apc_sma.c | 4 ++-- apc_sma.h | 2 +- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 521ea020..981cb63d 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -776,31 +776,37 @@ PHP_APCU_API void apc_cache_clear(apc_cache_t* cache) /* }}} */ /* {{{ apc_cache_default_expunge */ -PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) +PHP_APCU_API zend_bool apc_cache_default_expunge(apc_cache_t* cache, size_t size) { time_t t; size_t i; if (!cache) { - return; + return 1; } + /* get the number of cleanups before acquiring the lock */ + zend_long ncleanups = cache->header->ncleanups; + /* apc_time() depends on globals, don't read it if there's no cache. This may happen if SHM * is too small and the initial cache creation during MINIT triggers an expunge. */ t = apc_time(); /* get the lock for header */ if (!apc_cache_wlock(cache)) { - return; + return 1; + } + + /* skip processing if another default expunge operation was performed while waiting for the write lock */ + if (ncleanups < cache->header->ncleanups) { + apc_cache_wunlock(cache); + return 0; } /* smart > 1 increases the probability of a full cache wipe, * so expunge() is called less often when memory is low. */ size = (cache->smart > 0L) ? (size_t) (cache->smart * size) : size; - /* increment cache cleanup statistics (removal of expired entries) */ - cache->header->ncleanups++; - /* look for junk */ for (i = 0; i < cache->nslots; i++) { uintptr_t *entry_offset = &cache->slots[i]; @@ -823,8 +829,7 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) /* if all free blocks together do not provide enough memory, we immediately perform a real expunge */ if (!apc_sma_check_avail(cache->sma, size)) { apc_cache_wlocked_real_expunge(cache); - apc_cache_wunlock(cache); - return; + goto end_lbl; } /* increment defragmentation statistics */ @@ -836,14 +841,19 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size) /* if size bytes can't be allocated as a contiguous block after defragmentation, we do a real expunge */ if (!apc_sma_check_avail_contiguous(cache->sma, size)) { apc_cache_wlocked_real_expunge(cache); - apc_cache_wunlock(cache); - return; + goto end_lbl; } /* wipe lastkey */ memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t)); +end_lbl: + /* Increment cache cleanup statistics (removal of expired entries). + * This should be done late to detect stacking of default expunge operations. */ + cache->header->ncleanups++; + apc_cache_wunlock(cache); + return 1; } /* }}} */ diff --git a/apc_cache.h b/apc_cache.h index a9c60803..d90327a7 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -254,7 +254,7 @@ PHP_APCU_API void apc_cache_serializer(apc_cache_t* cache, const char* name); * * The TTL of an entry takes precedence over the TTL of a cache */ -PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size); +PHP_APCU_API zend_bool apc_cache_default_expunge(apc_cache_t* cache, size_t size); /* * apc_cache_entry: generate and create or fetch an entry diff --git a/apc_sma.c b/apc_sma.c index 128ade53..3964a6ac 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -326,8 +326,8 @@ PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n) { /* Expunge cache in hope of freeing up memory, but only once */ if (!nuked) { - sma->expunge(*sma->data, n); - nuked = 1; + /* nuke is not set if expunge() was skipped internally to get another try */ + nuked = sma->expunge(*sma->data, n); goto restart; } diff --git a/apc_sma.h b/apc_sma.h index c3204fbf..2c6784f2 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -52,7 +52,7 @@ struct apc_sma_info_t { }; /* }}} */ -typedef void (*apc_sma_expunge_f)(void *pointer, size_t size); /* }}} */ +typedef zend_bool (*apc_sma_expunge_f)(void *pointer, size_t size); /* }}} */ /* {{{ struct definition: apc_sma_t */ typedef struct _apc_sma_t { From 66b4f12952a956736812305e1cbda03904270086 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Tue, 5 Aug 2025 21:31:35 +0200 Subject: [PATCH 082/101] Fix race condition during allocation / defragmentation (#584) This fixes a race condition that can occur when a newly allocated entry is moved by defragmentation before it is inserted into the hash table. --- apc_cache.c | 8 ++++++-- apc_persist.c | 9 ++++++++- apc_sma.c | 13 ++++++++++--- apc_sma.h | 7 +++++-- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 981cb63d..720c5932 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -330,7 +330,7 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser cache_size = sizeof(apc_cache_header_t) + nslots * sizeof(uintptr_t); /* allocate shm */ - cache->header = apc_sma_malloc(sma, cache_size); + cache->header = apc_sma_malloc(sma, cache_size, NULL); if (!cache->header) { zend_error_noreturn(E_CORE_ERROR, "Unable to allocate " ZEND_LONG_FMT " bytes of shared memory for cache structures. Either apc.shm_size is too small or apc.entries_hint too large", cache_size); @@ -426,7 +426,6 @@ static void apc_cache_set_entry_values(apc_cache_entry_t *entry, const int32_t t entry->ttl = ttl; entry->next = 0; entry->prev = 0; - entry->ref_count = 0; entry->nhits = 0; entry->ctime = t; entry->mtime = t; @@ -459,6 +458,9 @@ static inline zend_bool apc_cache_store_internal( return 0; } + /* release entry, because the ref_count of a new entry is initialized to 1 during allocation */ + apc_cache_entry_release(cache, entry); + return 1; } @@ -571,6 +573,8 @@ PHP_APCU_API zend_bool apc_cache_store( php_apc_try { ret = apc_cache_wlocked_insert(cache, entry, exclusive); } php_apc_finally { + /* release entry, because the ref_count of a new entry is initialized to 1 during allocation */ + apc_cache_entry_release(cache, entry); apc_cache_wunlock(cache); } php_apc_end_try(); diff --git a/apc_persist.c b/apc_persist.c index 9f7f14e2..e322b8d2 100644 --- a/apc_persist.c +++ b/apc_persist.c @@ -475,6 +475,13 @@ static apc_cache_entry_t *apc_persist_create_entry( return entry; } +static void apc_persist_sma_init_entry(apc_cache_entry_t *entry) { + /* The ref_count must be initialized during allocation. This ensures that the entry + * is not moved by defragmentation before all persistence operations are completed + * and the entry is stored in the hash table. */ + entry->ref_count = 1; +} + apc_cache_entry_t *apc_persist( apc_sma_t *sma, apc_serializer_t *serializer, zend_string *key, const zval *val) { apc_persist_context_t ctxt; @@ -516,7 +523,7 @@ apc_cache_entry_t *apc_persist( } } - ctxt.alloc = ctxt.alloc_cur = apc_sma_malloc(sma, ctxt.size); + ctxt.alloc = ctxt.alloc_cur = apc_sma_malloc(sma, ctxt.size, (apc_sma_malloc_init_f)apc_persist_sma_init_entry); if (!ctxt.alloc) { apc_persist_destroy_context(&ctxt); return NULL; diff --git a/apc_sma.c b/apc_sma.c index 3964a6ac..c7151fca 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -301,7 +301,7 @@ PHP_APCU_API void apc_sma_detach(apc_sma_t* sma) { #endif } -PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n) { +PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n, apc_sma_malloc_init_f init_callback) { size_t off; zend_bool nuked = 0; @@ -314,16 +314,23 @@ PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n) { off = sma_allocate(SMA_HDR(sma), n); - SMA_UNLOCK(sma); - if (off != SIZE_MAX) { void *p = (void *)(SMA_ADDR(sma) + off); + + if (init_callback) { + /* Perform initializations that must be done before releasing the lock */ + init_callback(p); + } + + SMA_UNLOCK(sma); #ifdef VALGRIND_MALLOCLIKE_BLOCK VALGRIND_MALLOCLIKE_BLOCK(p, n, 0, 0); #endif return p; } + SMA_UNLOCK(sma); + /* Expunge cache in hope of freeing up memory, but only once */ if (!nuked) { /* nuke is not set if expunge() was skipped internally to get another try */ diff --git a/apc_sma.h b/apc_sma.h index 2c6784f2..91e155c8 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -82,9 +82,12 @@ PHP_APCU_API void apc_sma_init( PHP_APCU_API void apc_sma_detach(apc_sma_t* sma); /* -* apc_smap_api_malloc will allocate a block from the sma of the given size +* apc_smap_api_malloc will allocate a block from the sma of the given size. +* The init_callack() can be used to perform initializations that must be completed +* before the lock of the sma layer is released. */ -PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t size); +typedef void (*apc_sma_malloc_init_f)(void *pointer); +PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t size, apc_sma_malloc_init_f init_callback); /* * apc_sma_api_free will free p (which should be a pointer to a block allocated from sma) From 43719b82a7a1344b80d4609397f1618b21283126 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 5 Aug 2025 21:43:21 +0200 Subject: [PATCH 083/101] Release apcu 5.1.26 --- package.xml | 7 +++++-- php_apc.h | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package.xml b/package.xml index 7231224c..818a8f03 100644 --- a/package.xml +++ b/package.xml @@ -28,9 +28,9 @@ nikic@php.net yes - 2025-07-28 + 2025-08-05 - 5.1.26-dev + 5.1.26 5.1.18 @@ -39,6 +39,9 @@ PHP License + - Fixed hang introduced in apcu 5.1.25, which can occur when defragmentation is triggered under + load. + - Fixed a test on PHP 8.5. diff --git a/php_apc.h b/php_apc.h index 6a26e99a..6230ab7b 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.26-dev" +#define PHP_APCU_VERSION "5.1.26" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From 3625ba49b397e9e95d68c9e510f8ffb98265704d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 5 Aug 2025 21:47:33 +0200 Subject: [PATCH 084/101] Back to dev --- package.xml | 22 ++++++++++++++++++---- php_apc.h | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/package.xml b/package.xml index 818a8f03..7addbb23 100644 --- a/package.xml +++ b/package.xml @@ -30,7 +30,7 @@ 2025-08-05 - 5.1.26 + 5.1.27-dev 5.1.18 @@ -39,9 +39,6 @@ PHP License - - Fixed hang introduced in apcu 5.1.25, which can occur when defragmentation is triggered under - load. - - Fixed a test on PHP 8.5. @@ -195,6 +192,23 @@ + + 2025-08-05 + + 5.1.26 + 5.1.18 + + + stable + stable + + PHP License + + - Fixed hang introduced in apcu 5.1.25, which can occur when defragmentation is triggered under + load. + - Fixed a test on PHP 8.5. + + 2025-07-28 diff --git a/php_apc.h b/php_apc.h index 6230ab7b..b702b73c 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.26" +#define PHP_APCU_VERSION "5.1.27-dev" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From 3c53e4ca53f8463232934fb31182c965e6843ed8 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Wed, 13 Aug 2025 19:04:25 +0200 Subject: [PATCH 085/101] Cleanup code comments (#585) The comments for a legacy code folding mechanism and other useless comments have been removed. In addition, several other comments have been improved. --- apc.c | 15 +++------ apc.h | 20 +++++------- apc_cache.c | 75 +++++++++++--------------------------------- apc_cache.h | 45 +++++++++++++-------------- apc_iterator.c | 27 +--------------- apc_iterator.h | 4 --- apc_lock.h | 24 +++++++-------- apc_signal.c | 32 +++++++------------ apc_sma.c | 9 ++---- apc_sma.h | 20 +++++------- apc_stack.c | 1 - php_apc.c | 84 ++++++++++++-------------------------------------- 12 files changed, 103 insertions(+), 253 deletions(-) diff --git a/apc.c b/apc.c index 893bdeab..e2d039ca 100644 --- a/apc.c +++ b/apc.c @@ -34,7 +34,7 @@ #include "apc_globals.h" #include "php.h" -/* {{{ console display functions */ +/* console display functions */ #define APC_PRINT_FUNCTION(name, verbosity) \ void apc_##name(const char *format, ...) \ { \ @@ -54,9 +54,7 @@ APC_PRINT_FUNCTION(debug, E_NOTICE) #else void apc_debug(const char *format, ...) {} #endif -/* }}} */ -/* {{{ apc_flip_hash */ HashTable* apc_flip_hash(HashTable *hash) { zval data, *entry; HashTable *new_hash; @@ -79,7 +77,6 @@ HashTable* apc_flip_hash(HashTable *hash) { return new_hash; } -/* }}} */ /* * Serializer API @@ -88,9 +85,7 @@ HashTable* apc_flip_hash(HashTable *hash) { /* pointer to the list of serializers */ static apc_serializer_t apc_serializers[APC_MAX_SERIALIZERS] = {{0,}}; -/* }}} */ -/* {{{ apc_register_serializer */ PHP_APCU_API int _apc_register_serializer( const char* name, apc_serialize_t serialize, apc_unserialize_t unserialize, void *config) { int i; @@ -112,14 +107,12 @@ PHP_APCU_API int _apc_register_serializer( } return 0; -} /* }}} */ +} -/* {{{ apc_get_serializers */ PHP_APCU_API apc_serializer_t* apc_get_serializers() { return &(apc_serializers[0]); -} /* }}} */ +} -/* {{{ apc_find_serializer */ PHP_APCU_API apc_serializer_t* apc_find_serializer(const char* name) { int i; apc_serializer_t *serializer; @@ -131,7 +124,7 @@ PHP_APCU_API apc_serializer_t* apc_find_serializer(const char* name) { } } return NULL; -} /* }}} */ +} /* * Local variables: diff --git a/apc.h b/apc.h index 9335499c..d913c96b 100644 --- a/apc.h +++ b/apc.h @@ -107,32 +107,26 @@ PHP_APCU_API HashTable* apc_flip_hash(HashTable *hash); typedef int (*apc_serialize_t)(APC_SERIALIZER_ARGS); typedef int (*apc_unserialize_t)(APC_UNSERIALIZER_ARGS); -/* {{{ struct definition: apc_serializer_t */ typedef struct apc_serializer_t { const char* name; apc_serialize_t serialize; apc_unserialize_t unserialize; void* config; } apc_serializer_t; -/* }}} */ -/* {{{ _apc_register_serializer - registers the serializer using the given name and parameters */ +/* registers the serializer using the given name and parameters */ PHP_APCU_API int _apc_register_serializer( const char* name, apc_serialize_t serialize, apc_unserialize_t unserialize, void *config); -/* }}} */ -/* {{{ apc_get_serializers - fetches the list of serializers */ -PHP_APCU_API apc_serializer_t* apc_get_serializers(void); /* }}} */ +/* fetches the list of serializers */ +PHP_APCU_API apc_serializer_t* apc_get_serializers(void); -/* {{{ apc_find_serializer - finds a previously registered serializer by name */ -PHP_APCU_API apc_serializer_t* apc_find_serializer(const char* name); /* }}} */ +/* finds a previously registered serializer by name */ +PHP_APCU_API apc_serializer_t* apc_find_serializer(const char* name); -/* {{{ default serializers */ +/* default serializers */ PHP_APCU_API int APC_SERIALIZER_NAME(php) (APC_SERIALIZER_ARGS); -PHP_APCU_API int APC_UNSERIALIZER_NAME(php) (APC_UNSERIALIZER_ARGS); /* }}} */ +PHP_APCU_API int APC_UNSERIALIZER_NAME(php) (APC_UNSERIALIZER_ARGS); #define php_apc_try \ { \ diff --git a/apc_cache.c b/apc_cache.c index 720c5932..514734fa 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -60,7 +60,7 @@ apc_cache_entry_t *apc_persist( apc_sma_t *sma, apc_serializer_t *serializer, zend_string *key, const zval *val); zend_bool apc_unpersist(zval *dst, const apc_cache_entry_t *entry, apc_serializer_t *serializer); -/* {{{ make_prime */ +/* make_prime */ static int const primes[] = { 257, /* 256 */ 521, /* 512 */ @@ -120,19 +120,17 @@ static int make_prime(int n) } return *(k-1); } -/* }}} */ static inline void free_entry(apc_cache_t *cache, apc_cache_entry_t *entry) { apc_sma_free(cache->sma, entry); } -/* {{{ apc_cache_hash_slot - Note: These calculations can and should be done outside of a lock */ +/* These calculations can and should be done outside of a lock */ static inline void apc_cache_hash_slot( apc_cache_t* cache, zend_string *key, zend_ulong* hash, size_t* slot) { *hash = ZSTR_HASH(key); *slot = *hash % cache->nslots; -} /* }}} */ +} static inline zend_bool apc_entry_key_equals(const apc_cache_entry_t *entry, zend_string *key, zend_ulong hash) { return ZSTR_H(&entry->key) == hash @@ -140,8 +138,8 @@ static inline zend_bool apc_entry_key_equals(const apc_cache_entry_t *entry, zen && memcmp(ZSTR_VAL(&entry->key), ZSTR_VAL(key), ZSTR_LEN(key)) == 0; } -/* An entry is hard expired if the creation time if older than the per-entry TTL. - * Hard expired entries must be treated indentially to non-existent entries. */ +/* An entry is hard expired if the creation time is older than the per-entry TTL. + * Hard expired entries must be treated identically to non-existent entries. */ static zend_bool apc_cache_entry_hard_expired(apc_cache_entry_t *entry, time_t t) { return entry->ttl && (time_t) (entry->ctime + entry->ttl) < t; } @@ -199,7 +197,6 @@ static void apc_cache_wlocked_unlink_entry(apc_cache_t *cache, apc_cache_entry_t } } -/* {{{ apc_cache_wlocked_remove_entry */ static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, apc_cache_entry_t *entry) { /* unlink entry from list */ @@ -221,9 +218,7 @@ static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, apc_cache_entry_t apc_cache_wlocked_link_entry(cache, &cache->header->gc, entry); } } -/* }}} */ -/* {{{ apc_cache_wlocked_gc */ static void apc_cache_wlocked_gc(apc_cache_t* cache) { /* This function scans the list of removed cache entries and deletes any @@ -260,9 +255,8 @@ static void apc_cache_wlocked_gc(apc_cache_t* cache) free_entry(cache, entry); } } -/* }}} */ -/* {{{ php serializer */ +/* php serializer */ PHP_APCU_API int APC_SERIALIZER_NAME(php) (APC_SERIALIZER_ARGS) { smart_str strbuf = {0}; @@ -290,9 +284,9 @@ PHP_APCU_API int APC_SERIALIZER_NAME(php) (APC_SERIALIZER_ARGS) return 1; } return 0; -} /* }}} */ +} -/* {{{ php unserializer */ +/* php unserializer */ PHP_APCU_API int APC_UNSERIALIZER_NAME(php) (APC_UNSERIALIZER_ARGS) { const unsigned char *tmp = buf; @@ -312,9 +306,8 @@ PHP_APCU_API int APC_UNSERIALIZER_NAME(php) (APC_UNSERIALIZER_ARGS) return 0; } return 1; -} /* }}} */ +} -/* {{{ apc_cache_create */ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* serializer, zend_long size_hint, zend_long gc_ttl, zend_long ttl, zend_long smart, zend_bool defend) { apc_cache_t* cache; zend_long cache_size; @@ -364,7 +357,7 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser CREATE_LOCK(&cache->header->lock); return cache; -} /* }}} */ +} static inline zend_bool apc_cache_wlocked_insert( apc_cache_t *cache, apc_cache_entry_t *new_entry, zend_bool exclusive) { @@ -539,7 +532,6 @@ static inline apc_cache_entry_t *apc_cache_rlocked_find_incref( return entry; } -/* {{{ apc_cache_store */ PHP_APCU_API zend_bool apc_cache_store( apc_cache_t* cache, zend_string *key, const zval *val, const int32_t ttl, const zend_bool exclusive) { @@ -583,10 +575,9 @@ PHP_APCU_API zend_bool apc_cache_store( } return ret; -} /* }}} */ +} #ifndef ZTS -/* {{{ data_unserialize */ static zval data_unserialize(const char *filename) { zval retval; @@ -669,7 +660,7 @@ static int apc_load_data(apc_cache_t* cache, const char *data_file) } #endif -/* {{{ apc_cache_preload shall load the prepared data files in path into the specified cache */ +/* apc_cache_preload shall load the prepared data files in path into the specified cache */ PHP_APCU_API zend_bool apc_cache_preload(apc_cache_t* cache, const char *path) { #ifndef ZTS @@ -703,16 +694,13 @@ PHP_APCU_API zend_bool apc_cache_preload(apc_cache_t* cache, const char *path) apc_error("Cannot load data from apc.preload_path=%s in thread-safe mode", path); return 0; #endif -} /* }}} */ +} -/* {{{ apc_cache_entry_release */ PHP_APCU_API void apc_cache_entry_release(apc_cache_t *cache, apc_cache_entry_t *entry) { ATOMIC_DEC(entry->ref_count); } -/* }}} */ -/* {{{ apc_cache_detach */ PHP_APCU_API void apc_cache_detach(apc_cache_t *cache) { /* Important: This function should not clean up anything that's in shared memory, @@ -725,9 +713,7 @@ PHP_APCU_API void apc_cache_detach(apc_cache_t *cache) free(cache); } -/* }}} */ -/* {{{ apc_cache_wlocked_real_expunge */ static void apc_cache_wlocked_real_expunge(apc_cache_t* cache) { size_t i; @@ -753,9 +739,8 @@ static void apc_cache_wlocked_real_expunge(apc_cache_t* cache) { /* resets lastkey */ memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t)); -} /* }}} */ +} -/* {{{ apc_cache_clear */ PHP_APCU_API void apc_cache_clear(apc_cache_t* cache) { if (!cache) { @@ -777,9 +762,7 @@ PHP_APCU_API void apc_cache_clear(apc_cache_t* cache) apc_cache_wunlock(cache); } -/* }}} */ -/* {{{ apc_cache_default_expunge */ PHP_APCU_API zend_bool apc_cache_default_expunge(apc_cache_t* cache, size_t size) { time_t t; @@ -859,9 +842,7 @@ PHP_APCU_API zend_bool apc_cache_default_expunge(apc_cache_t* cache, size_t size apc_cache_wunlock(cache); return 1; } -/* }}} */ -/* {{{ apc_cache_fetch */ PHP_APCU_API zend_bool apc_cache_fetch(apc_cache_t* cache, zend_string *key, time_t t, zval *dst) { apc_cache_entry_t *entry; @@ -889,9 +870,8 @@ PHP_APCU_API zend_bool apc_cache_fetch(apc_cache_t* cache, zend_string *key, tim } php_apc_end_try(); return retval; -} /* }}} */ +} -/* {{{ apc_cache_exists */ PHP_APCU_API zend_bool apc_cache_exists(apc_cache_t* cache, zend_string *key, time_t t) { apc_cache_entry_t *entry; @@ -909,9 +889,7 @@ PHP_APCU_API zend_bool apc_cache_exists(apc_cache_t* cache, zend_string *key, ti return entry != NULL; } -/* }}} */ -/* {{{ apc_cache_update */ PHP_APCU_API zend_bool apc_cache_update( apc_cache_t *cache, zend_string *key, apc_cache_updater_t updater, void *data, zend_bool insert_if_not_found, zend_long ttl) @@ -959,9 +937,7 @@ PHP_APCU_API zend_bool apc_cache_update( return 0; } -/* }}} */ -/* {{{ apc_cache_atomic_update_long */ PHP_APCU_API zend_bool apc_cache_atomic_update_long( apc_cache_t *cache, zend_string *key, apc_cache_atomic_updater_t updater, void *data, zend_bool insert_if_not_found, zend_long ttl) @@ -1009,9 +985,7 @@ PHP_APCU_API zend_bool apc_cache_atomic_update_long( return 0; } -/* }}} */ -/* {{{ apc_cache_delete */ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key) { zend_ulong h; @@ -1048,15 +1022,12 @@ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key) apc_cache_wunlock(cache); return 0; } -/* }}} */ -/* {{{ apc_cache_entry_fetch_zval */ PHP_APCU_API zend_bool apc_cache_entry_fetch_zval( apc_cache_t *cache, apc_cache_entry_t *entry, zval *dst) { return apc_unpersist(dst, entry, cache->serializer); } -/* }}} */ static inline void array_add_long(zval *array, zend_string *key, zend_long lval) { zval zv; @@ -1070,7 +1041,6 @@ static inline void array_add_double(zval *array, zend_string *key, double dval) zend_hash_add_new(Z_ARRVAL_P(array), key, &zv); } -/* {{{ apc_cache_link_info */ static zval apc_cache_link_info(apc_cache_t *cache, apc_cache_entry_t *p) { zval link, zv; @@ -1090,9 +1060,7 @@ static zval apc_cache_link_info(apc_cache_t *cache, apc_cache_entry_t *p) return link; } -/* }}} */ -/* {{{ apc_cache_info */ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool limited) { zval list; @@ -1175,11 +1143,8 @@ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool return 1; } -/* }}} */ -/* - fetches information about the key provided -*/ +/* fetches information about the key provided */ PHP_APCU_API void apc_cache_stat(apc_cache_t *cache, zend_string *key, zval *stat) { zend_ulong h; size_t s; @@ -1223,7 +1188,6 @@ PHP_APCU_API void apc_cache_stat(apc_cache_t *cache, zend_string *key, zval *sta } php_apc_end_try(); } -/* {{{ apc_cache_defense */ PHP_APCU_API zend_bool apc_cache_defense(apc_cache_t *cache, zend_string *key, time_t t) { /* only continue if slam defense is enabled */ @@ -1263,16 +1227,14 @@ PHP_APCU_API zend_bool apc_cache_defense(apc_cache_t *cache, zend_string *key, t return 0; } -/* }}} */ -/* {{{ apc_cache_serializer */ PHP_APCU_API void apc_cache_serializer(apc_cache_t* cache, const char* name) { if (cache && !cache->serializer) { cache->serializer = apc_find_serializer(name); } -} /* }}} */ +} -PHP_APCU_API void apc_cache_entry(apc_cache_t *cache, zend_string *key, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_long ttl, zend_long now, zval *return_value) {/*{{{*/ +PHP_APCU_API void apc_cache_entry(apc_cache_t *cache, zend_string *key, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_long ttl, zend_long now, zval *return_value) { apc_cache_entry_t *entry = NULL; if (!cache) { @@ -1312,7 +1274,6 @@ PHP_APCU_API void apc_cache_entry(apc_cache_t *cache, zend_string *key, zend_fca apc_cache_wunlock(cache); } php_apc_end_try(); } -/*}}}*/ /* * Local variables: diff --git a/apc_cache.h b/apc_cache.h index d90327a7..90aebb22 100644 --- a/apc_cache.h +++ b/apc_cache.h @@ -46,7 +46,6 @@ struct apc_cache_slam_key_t { #endif }; -/* {{{ struct definition: apc_cache_entry_t */ typedef struct apc_cache_entry_t apc_cache_entry_t; struct apc_cache_entry_t { uintptr_t next; /* offset to next entry (MUST BE THE 1st FIELD OF THE STRUCT!) */ @@ -62,10 +61,8 @@ struct apc_cache_entry_t { zval val; /* the zval copied at store time */ zend_string key; /* entry key (MUST BE THE LAST FIELD OF THE STRUCT!) */ }; -/* }}} */ -/* {{{ struct definition: apc_cache_header_t - Any values that must be shared among processes should go in here. */ +/* Any values that must be shared among processes should go in here. */ typedef struct _apc_cache_header_t { apc_lock_t lock; /* header lock */ zend_long nhits; /* hit count */ @@ -79,9 +76,8 @@ typedef struct _apc_cache_header_t { time_t stime; /* start time */ apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */ uintptr_t gc; /* offset in shm to the first entry of gc list */ -} apc_cache_header_t; /* }}} */ +} apc_cache_header_t; -/* {{{ struct definition: apc_cache_t */ typedef struct _apc_cache_t { apc_cache_header_t* header; /* cache header (stored in SHM) */ uintptr_t* slots; /* array of cache slots (stored in SHM) */ @@ -92,13 +88,11 @@ typedef struct _apc_cache_t { zend_long ttl; /* if slot is needed and entry's access time is older than this ttl, remove it */ zend_long smart; /* smart parameter for gc */ zend_bool defend; /* defense parameter for runtime */ -} apc_cache_t; /* }}} */ +} apc_cache_t; -/* {{{ typedef: apc_cache_updater_t */ -typedef zend_bool (*apc_cache_updater_t)(apc_cache_t*, apc_cache_entry_t*, void* data); /* }}} */ +typedef zend_bool (*apc_cache_updater_t)(apc_cache_t*, apc_cache_entry_t*, void* data); -/* {{{ typedef: apc_cache_atomic_updater_t */ -typedef zend_bool (*apc_cache_atomic_updater_t)(apc_cache_t*, zend_long*, void* data); /* }}} */ +typedef zend_bool (*apc_cache_atomic_updater_t)(apc_cache_t*, zend_long*, void* data); /* * apc_cache_create creates the shared memory cache. @@ -129,6 +123,7 @@ typedef zend_bool (*apc_cache_atomic_updater_t)(apc_cache_t*, zend_long*, void* PHP_APCU_API apc_cache_t* apc_cache_create( apc_sma_t* sma, apc_serializer_t* serializer, zend_long size_hint, zend_long gc_ttl, zend_long ttl, zend_long smart, zend_bool defend); + /* * apc_cache_preload preloads the data at path into the specified cache */ @@ -152,6 +147,7 @@ PHP_APCU_API void apc_cache_clear(apc_cache_t* cache); PHP_APCU_API zend_bool apc_cache_store( apc_cache_t* cache, zend_string *key, const zval *val, const int32_t ttl, const zend_bool exclusive); + /* * apc_cache_update updates an entry in place. The updater function must not bailout. * The update is performed under write-lock and doesn't have to be atomic. @@ -170,7 +166,6 @@ PHP_APCU_API zend_bool apc_cache_atomic_update_long( /* * apc_cache_fetch fetches an entry from the cache directly into dst - * */ PHP_APCU_API zend_bool apc_cache_fetch(apc_cache_t* cache, zend_string *key, time_t t, zval *dst); @@ -185,7 +180,8 @@ PHP_APCU_API zend_bool apc_cache_exists(apc_cache_t* cache, zend_string *key, ti */ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t* cache, zend_string *key); -/* apc_cache_fetch_zval copies a cache entry value to be usable at runtime. +/* + * apc_cache_fetch_zval copies a cache entry value to be usable at runtime. */ PHP_APCU_API zend_bool apc_cache_entry_fetch_zval( apc_cache_t *cache, apc_cache_entry_t *entry, zval *dst); @@ -202,20 +198,20 @@ PHP_APCU_API zend_bool apc_cache_entry_fetch_zval( PHP_APCU_API void apc_cache_entry_release(apc_cache_t *cache, apc_cache_entry_t *entry); /* - fetches information about the cache provided for userland status functions -*/ + * fetches information about the cache provided for userland status functions + */ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool limited); /* - fetches information about the key provided -*/ + * fetches information about the key provided + */ PHP_APCU_API void apc_cache_stat(apc_cache_t *cache, zend_string *key, zval *stat); /* * apc_cache_defense: guard against slamming a key -* will return true if the following conditions are met: -* the key provided has a matching hash and length to the last key inserted into cache -* the last key has a different owner +* will return true if the following conditions are met: +* - the key provided has a matching hash and length to the last key inserted into cache +* - the last key has a different owner * in ZTS mode, TSRM determines owner * in non-ZTS mode, PID determines owner * Note: this function sets the owner of key during execution @@ -223,8 +219,7 @@ PHP_APCU_API void apc_cache_stat(apc_cache_t *cache, zend_string *key, zval *sta PHP_APCU_API zend_bool apc_cache_defense(apc_cache_t *cache, zend_string *key, time_t t); /* -* apc_cache_serializer -* sets the serializer for a cache, and by proxy contexts created for the cache +* apc_cache_serializer sets the serializer for a cache, and by proxy contexts created for the cache. * Note: this avoids race conditions between third party serializers and APCu */ PHP_APCU_API void apc_cache_serializer(apc_cache_t* cache, const char* name); @@ -243,7 +238,11 @@ PHP_APCU_API void apc_cache_serializer(apc_cache_t* cache, const char* name); * Note: beware of locking (copy it exactly), setting states is also important */ -/* {{{ apc_cache_default_expunge +/* +* apc_cache_default_expunge() is executed by the sma layer when there is not enough +* free shared memory to satisfy an allocation request. It attempts to free memory +* (e.g., by removing entries) so that the allocation request can be satisfied. +* * Where smart is not set: * 1) Perform cleanup of stale entries * 2) If available memory is less than the size requested, run full expunge diff --git a/apc_iterator.c b/apc_iterator.c index 9181085c..535a9df7 100644 --- a/apc_iterator.c +++ b/apc_iterator.c @@ -45,7 +45,6 @@ zend_class_entry* apc_iterator_get_ce(void) { return; \ } -/* {{{ apc_iterator_item */ static apc_iterator_item_t* apc_iterator_item_ctor( apc_iterator_t *iterator, apc_cache_entry_t *entry) { zval zv; @@ -108,17 +107,13 @@ static apc_iterator_item_t* apc_iterator_item_ctor( return item; } -/* }}} */ -/* {{{ apc_iterator_item_dtor */ static void apc_iterator_item_dtor(apc_iterator_item_t *item) { zend_string_release(item->key); zval_ptr_dtor(&item->value); efree(item); } -/* }}} */ -/* {{{ acp_iterator_free */ static void apc_iterator_free(zend_object *object) { apc_iterator_t *iterator = apc_iterator_fetch_from(object); @@ -148,9 +143,7 @@ static void apc_iterator_free(zend_object *object) { zend_object_std_dtor(object); } -/* }}} */ -/* {{{ apc_iterator_create */ zend_object* apc_iterator_create(zend_class_entry *ce) { apc_iterator_t *iterator = (apc_iterator_t*) emalloc(sizeof(apc_iterator_t) + zend_object_properties_size(ce)); @@ -166,11 +159,8 @@ zend_object* apc_iterator_create(zend_class_entry *ce) { return &iterator->obj; } -/* }}} */ -/* {{{ apc_iterator_search_match - * Verify if the key matches our search parameters - */ +/* Verifies if the key matches our search parameters */ static int apc_iterator_search_match(apc_iterator_t *iterator, apc_cache_entry_t *entry) { int rval = 1; @@ -194,9 +184,7 @@ static int apc_iterator_search_match(apc_iterator_t *iterator, apc_cache_entry_t return rval; } -/* }}} */ -/* {{{ apc_iterator_check_expiry */ static int apc_iterator_check_expiry(apc_cache_t* cache, apc_cache_entry_t *entry, time_t t) { if (entry->ttl) { @@ -207,9 +195,7 @@ static int apc_iterator_check_expiry(apc_cache_t* cache, apc_cache_entry_t *entr return 1; } -/* }}} */ -/* {{{ apc_iterator_fetch_active */ static size_t apc_iterator_fetch_active(apc_iterator_t *iterator) { apc_cache_t *cache = apc_user_cache; size_t count = 0; @@ -249,9 +235,7 @@ static size_t apc_iterator_fetch_active(apc_iterator_t *iterator) { return count; } -/* }}} */ -/* {{{ apc_iterator_fetch_deleted */ static size_t apc_iterator_fetch_deleted(apc_iterator_t *iterator) { apc_cache_t *cache = apc_user_cache; size_t count = 0; @@ -287,9 +271,7 @@ static size_t apc_iterator_fetch_deleted(apc_iterator_t *iterator) { return count; } -/* }}} */ -/* {{{ apc_iterator_totals */ static void apc_iterator_totals(apc_iterator_t *iterator) { apc_cache_t *cache = apc_user_cache; time_t t = apc_time(); @@ -320,7 +302,6 @@ static void apc_iterator_totals(apc_iterator_t *iterator) { apc_cache_runlock(cache); } php_apc_end_try(); } -/* }}} */ void apc_iterator_obj_init(apc_iterator_t *iterator, zval *search, zend_long format, size_t chunk_size, zend_long list) { @@ -506,7 +487,6 @@ PHP_METHOD(APCUIterator, getTotalHits) { RETURN_LONG(iterator->hits); } -/* }}} */ PHP_METHOD(APCUIterator, getTotalSize) { apc_iterator_t *iterator = apc_iterator_fetch(getThis()); @@ -540,7 +520,6 @@ PHP_METHOD(APCUIterator, getTotalCount) { RETURN_LONG(iterator->count); } -/* {{{ apc_iterator_init */ int apc_iterator_init(int module_number) { zend_class_entry ce; @@ -573,13 +552,11 @@ int apc_iterator_init(int module_number) { return SUCCESS; } -/* }}} */ int apc_iterator_shutdown(int module_number) { return SUCCESS; } -/* {{{ apc_iterator_delete */ int apc_iterator_delete(zval *zobj) { apc_iterator_t *iterator; zend_class_entry *ce = Z_OBJCE_P(zobj); @@ -606,8 +583,6 @@ int apc_iterator_delete(zval *zobj) { return 1; } -/* }}} */ - /* * Local variables: diff --git a/apc_iterator.h b/apc_iterator.h index 9feda387..d38a5a35 100644 --- a/apc_iterator.h +++ b/apc_iterator.h @@ -46,7 +46,6 @@ #define APC_ITER_NONE 0 #define APC_ITER_ALL (0xffffffffL) -/* {{{ apc_iterator_t */ typedef struct _apc_iterator_t { short int initialized; /* sanity check in case __construct failed */ zend_long format; /* format bitmask of the return values ie: key, value, info */ @@ -69,17 +68,14 @@ typedef struct _apc_iterator_t { zend_long count; /* count total */ zend_object obj; } apc_iterator_t; -/* }}} */ #define apc_iterator_fetch_from(o) ((apc_iterator_t*)((char*)o - XtOffsetOf(apc_iterator_t, obj))) #define apc_iterator_fetch(z) apc_iterator_fetch_from(Z_OBJ_P(z)) -/* {{{ apc_iterator_item */ typedef struct _apc_iterator_item_t { zend_string *key; zval value; } apc_iterator_item_t; -/* }}} */ PHP_APCU_API void apc_iterator_obj_init( apc_iterator_t *iterator, diff --git a/apc_lock.h b/apc_lock.h index 7288cb9f..a808814c 100644 --- a/apc_lock.h +++ b/apc_lock.h @@ -22,10 +22,10 @@ /* APCu works most efficiently where there is access to native read/write locks If the current system has native rwlocks present they will be used, if they are - not present, APCu will emulate their behavior with standard mutex. + not present, APCu will emulate their behavior with standard mutex. While APCu is emulating read/write locks, reads and writes are exclusive, - additionally the write lock prefers readers, as is the default behaviour of - the majority of Posix rwlock implementations + additionally the write lock prefers readers, as is the default behavior of + the majority of Posix rwlock implementations */ #ifdef HAVE_CONFIG_H @@ -65,13 +65,14 @@ typedef apc_windows_cs_rwlock_t apc_lock_t; # define APC_LOCK_SHARED #endif -/* {{{ functions */ /* - The following functions should be called once per process: - apc_lock_init initializes attributes suitable for all locks - apc_lock_cleanup destroys those attributes - This saves us from having to create and destroy attributes for - every lock we use at runtime */ + The following functions should be called once per process: + - apc_lock_init initializes attributes suitable for all locks + - apc_lock_cleanup destroys those attributes + + This saves us from having to create and destroy attributes for + every lock we use at runtime + */ PHP_APCU_API zend_bool apc_lock_init(void); PHP_APCU_API void apc_lock_cleanup(void); /* @@ -82,16 +83,15 @@ PHP_APCU_API zend_bool apc_lock_rlock(apc_lock_t *lock); PHP_APCU_API zend_bool apc_lock_wlock(apc_lock_t *lock); PHP_APCU_API zend_bool apc_lock_runlock(apc_lock_t *lock); PHP_APCU_API zend_bool apc_lock_wunlock(apc_lock_t *lock); -PHP_APCU_API void apc_lock_destroy(apc_lock_t *lock); /* }}} */ +PHP_APCU_API void apc_lock_destroy(apc_lock_t *lock); -/* {{{ generic locking macros */ +/* generic locking macros */ #define CREATE_LOCK(lock) apc_lock_create(lock) #define DESTROY_LOCK(lock) apc_lock_destroy(lock) #define WLOCK(lock) apc_lock_wlock(lock) #define WUNLOCK(lock) { apc_lock_wunlock(lock); HANDLE_UNBLOCK_INTERRUPTIONS(); } #define RLOCK(lock) apc_lock_rlock(lock) #define RUNLOCK(lock) { apc_lock_runlock(lock); HANDLE_UNBLOCK_INTERRUPTIONS(); } -/* }}} */ /* atomic operations */ #ifdef PHP_WIN32 diff --git a/apc_signal.c b/apc_signal.c index bb47f77f..bb12026e 100644 --- a/apc_signal.c +++ b/apc_signal.c @@ -52,9 +52,7 @@ static void apc_clear_cache(int signo, siginfo_t *siginfo, void *context); extern apc_cache_t* apc_user_cache; -/* {{{ apc_core_unmap - * Coredump signal handler, detached from shm and calls previously installed handlers - */ +/* Coredump signal handler, detached from shm and calls previously installed handlers */ static void apc_core_unmap(int signo, siginfo_t *siginfo, void *context) { if (apc_user_cache) { @@ -67,11 +65,11 @@ static void apc_core_unmap(int signo, siginfo_t *siginfo, void *context) #else raise(signo); #endif -} /* }}} */ +} #if defined(SIGUSR1) && defined(APC_CLEAR_SIGNAL) -/* {{{ apc_reload_cache */ +/* Clears the cache on SIGUSR1 */ static void apc_clear_cache(int signo, siginfo_t *siginfo, void *context) { if (apc_user_cache) { apc_cache_clear(apc_user_cache); @@ -84,12 +82,10 @@ static void apc_clear_cache(int signo, siginfo_t *siginfo, void *context) { #else raise(signo); #endif -} /* }}} */ +} #endif -/* {{{ apc_rehandle_signal - * Call the previously registered handler for a signal - */ +/* Call the previously registered handler for a signal */ static void apc_rehandle_signal(int signo, siginfo_t *siginfo, void *context) { int i; @@ -106,12 +102,9 @@ static void apc_rehandle_signal(int signo, siginfo_t *siginfo, void *context) } } -} /* }}} */ +} -/* {{{ apc_register_signal - * Set a handler for a previously installed signal and save so we can - * callback when handled - */ +/* Set a handler for a previously installed signal and save so we can callback when handled */ static int apc_register_signal(int signo, void (*handler)(int, siginfo_t*, void*)) { struct sigaction sa; @@ -150,10 +143,9 @@ static int apc_register_signal(int signo, void (*handler)(int, siginfo_t*, void* return SUCCESS; } return FAILURE; -} /* }}} */ +} -/* {{{ apc_set_signals - * Install our signal handlers */ +/* Install our signal handlers */ void apc_set_signals() { if (apc_signal_info.installed == 0) { @@ -196,10 +188,9 @@ void apc_set_signals() #endif } } -} /* }}} */ +} -/* {{{ apc_set_signals - * cleanup signals for shutdown */ +/* Cleanup signals for shutdown */ void apc_shutdown_signals() { int i=0; @@ -211,7 +202,6 @@ void apc_shutdown_signals() apc_signal_info.installed = 0; /* just in case */ } } -/* }}} */ #endif /* HAVE_SIGACTION */ diff --git a/apc_sma.c b/apc_sma.c index c7151fca..af47f622 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -135,7 +135,7 @@ static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) { return found; } -/* {{{ sma_allocate: tries to allocate at least size bytes of shared memory */ +/* sma_allocate: tries to allocate at least size bytes of shared memory */ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size) { block_t* prv; /* block prior to working block */ @@ -185,9 +185,8 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size) return OFFSET(cur) + ALIGNWORD(sizeof(block_t)); } -/* }}} */ -/* {{{ sma_deallocate: deallocates the block at the given offset */ +/* sma_deallocate: deallocates the block at the given offset */ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset) { block_t* cur; /* the new block to insert */ @@ -240,9 +239,7 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset) return size; } -/* }}} */ -/* {{{ APC SMA API */ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, size_t size, size_t min_alloc_size, char *mask, zend_long hugepage_size) { if (sma->initialized) { return; @@ -509,8 +506,6 @@ PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move SMA_UNLOCK(sma); } -/* }}} */ - /* * Local variables: * tab-width: 4 diff --git a/apc_sma.h b/apc_sma.h index 91e155c8..522f7d78 100644 --- a/apc_sma.h +++ b/apc_sma.h @@ -28,33 +28,29 @@ #ifndef APC_SMA_H #define APC_SMA_H -/* {{{ SMA API - APC SMA API provides support for shared memory allocators to external libraries ( and to APC ) - Skip to the bottom macros for error free usage of the SMA API +/* + * SMA API + * APC SMA API provides support for shared memory allocators to external libraries ( and to APC ) + * Skip to the bottom macros for error free usage of the SMA API */ #include "apc.h" -/* {{{ struct definition: apc_sma_link_t */ typedef struct apc_sma_link_t apc_sma_link_t; struct apc_sma_link_t { zend_long size; /* size of this free block */ zend_long offset; /* offset in segment of this block */ apc_sma_link_t* next; /* link to next free block */ }; -/* }}} */ -/* {{{ struct definition: apc_sma_info_t */ typedef struct apc_sma_info_t apc_sma_info_t; struct apc_sma_info_t { size_t seg_size; /* segment size */ apc_sma_link_t* list; /* list of free blocks */ }; -/* }}} */ -typedef zend_bool (*apc_sma_expunge_f)(void *pointer, size_t size); /* }}} */ +typedef zend_bool (*apc_sma_expunge_f)(void *pointer, size_t size); -/* {{{ struct definition: apc_sma_t */ typedef struct _apc_sma_t { zend_bool initialized; /* flag to indicate this sma has been initialized */ @@ -65,7 +61,7 @@ typedef struct _apc_sma_t { /* info */ size_t size; /* segment size */ void *shmaddr; /* address of shm segment */ -} apc_sma_t; /* }}} */ +} apc_sma_t; /* * apc_sma_init will initialize a shared memory allocator with the given size of shared memory @@ -132,9 +128,7 @@ PHP_APCU_API zend_bool apc_sma_check_avail_contiguous(apc_sma_t *sma, size_t siz typedef zend_bool (*apc_sma_move_f)(void *data, void *pointer_old, void *pointer_new); PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move); -/* {{{ ALIGNWORD: pad up x, aligned to the system's word boundary */ +/* ALIGNWORD: pad up x, aligned to the system's word boundary */ #define ALIGNWORD(x) ZEND_MM_ALIGNED_SIZE(x) -/* }}} */ #endif - diff --git a/apc_stack.c b/apc_stack.c index 22d81b9f..57823e33 100644 --- a/apc_stack.c +++ b/apc_stack.c @@ -93,7 +93,6 @@ int apc_stack_size(apc_stack_t* stack) return stack->size; } - /* * Local variables: * tab-width: 4 diff --git a/php_apc.c b/php_apc.c index a00a04e1..392b05ac 100644 --- a/php_apc.c +++ b/php_apc.c @@ -62,7 +62,6 @@ #include "apc_signal.h" #endif -/* {{{ ZEND_DECLARE_MODULE_GLOBALS(apcu) */ ZEND_DECLARE_MODULE_GLOBALS(apcu) /* True globals */ @@ -87,11 +86,10 @@ static void php_apc_init_globals(zend_apcu_globals* apcu_globals) apcu_globals->serializer_name = NULL; apcu_globals->entry_level = 0; } -/* }}} */ -/* {{{ PHP_INI */ +/* PHP_INI */ -static PHP_INI_MH(OnUpdateShmSize) /* {{{ */ +static PHP_INI_MH(OnUpdateShmSize) { #if PHP_VERSION_ID >= 80200 zend_long s = zend_ini_parse_quantity_warn(new_value, entry->name); @@ -114,10 +112,9 @@ static PHP_INI_MH(OnUpdateShmSize) /* {{{ */ return SUCCESS; } -/* }}} */ #if defined(APC_MMAP) -static PHP_INI_MH(OnUpdateMmapHugepageSize) /* {{{ */ +static PHP_INI_MH(OnUpdateMmapHugepageSize) { zend_long s; @@ -140,7 +137,6 @@ static PHP_INI_MH(OnUpdateMmapHugepageSize) /* {{{ */ APCG(mmap_hugepage_size) = s; return SUCCESS; } -/* }}} */ #endif PHP_INI_BEGIN() @@ -162,14 +158,11 @@ STD_PHP_INI_BOOLEAN("apc.use_request_time", "0", PHP_INI_ALL, OnUpdateBool, use_ STD_PHP_INI_ENTRY("apc.serializer", "php", PHP_INI_SYSTEM, OnUpdateStringUnempty, serializer_name, zend_apcu_globals, apcu_globals) PHP_INI_END() -/* }}} */ - zend_bool apc_is_enabled(void) { return APCG(enabled); } -/* {{{ PHP_MINFO_FUNCTION(apcu) */ static PHP_MINFO_FUNCTION(apcu) { php_info_print_table_start(); @@ -216,9 +209,7 @@ static PHP_MINFO_FUNCTION(apcu) php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } -/* }}} */ -/* {{{ PHP_MINIT_FUNCTION(apcu) */ static PHP_MINIT_FUNCTION(apcu) { #if defined(ZTS) && defined(COMPILE_DL_APCU) @@ -291,9 +282,7 @@ static PHP_MINIT_FUNCTION(apcu) return SUCCESS; } -/* }}} */ -/* {{{ PHP_MSHUTDOWN_FUNCTION(apcu) */ static PHP_MSHUTDOWN_FUNCTION(apcu) { #define X(str) zend_string_release(apc_str_ ## str); @@ -323,9 +312,8 @@ static PHP_MSHUTDOWN_FUNCTION(apcu) UNREGISTER_INI_ENTRIES(); return SUCCESS; -} /* }}} */ +} -/* {{{ PHP_RINIT_FUNCTION(apcu) */ static PHP_RINIT_FUNCTION(apcu) { #if defined(ZTS) && defined(COMPILE_DL_APCU) @@ -345,9 +333,8 @@ static PHP_RINIT_FUNCTION(apcu) } return SUCCESS; } -/* }}} */ -/* {{{ proto void apcu_clear_cache() */ +/* proto void apcu_clear_cache() */ PHP_FUNCTION(apcu_clear_cache) { if (zend_parse_parameters_none() == FAILURE) { @@ -357,9 +344,8 @@ PHP_FUNCTION(apcu_clear_cache) apc_cache_clear(apc_user_cache); RETURN_TRUE; } -/* }}} */ -/* {{{ proto array apcu_cache_info([bool limited]) */ +/* proto array apcu_cache_info([bool limited]) */ PHP_FUNCTION(apcu_cache_info) { zend_bool limited = 0; @@ -374,9 +360,8 @@ PHP_FUNCTION(apcu_cache_info) RETURN_FALSE; } } -/* }}} */ -/* {{{ proto array apcu_key_info(string key) */ +/* proto array apcu_key_info(string key) */ PHP_FUNCTION(apcu_key_info) { zend_string *key; @@ -386,9 +371,9 @@ PHP_FUNCTION(apcu_key_info) ZEND_PARSE_PARAMETERS_END(); apc_cache_stat(apc_user_cache, key, return_value); -} /* }}} */ +} -/* {{{ proto array apcu_sma_info([bool limited]) */ +/* proto array apcu_sma_info([bool limited]) */ PHP_FUNCTION(apcu_sma_info) { zend_bool limited = 0; @@ -437,9 +422,7 @@ PHP_FUNCTION(apcu_sma_info) add_assoc_zval(return_value, "block_lists", &block_lists); apc_sma_free_info(&apc_sma, info); } -/* }}} */ -/* {{{ php_apc_update */ zend_bool php_apc_update( zend_string *key, apc_cache_atomic_updater_t updater, void *data, zend_bool insert_if_not_found, time_t ttl) @@ -451,10 +434,7 @@ zend_bool php_apc_update( return apc_cache_atomic_update_long(apc_user_cache, key, updater, data, insert_if_not_found, ttl); } -/* }}} */ -/* {{{ apc_store_helper(INTERNAL_FUNCTION_PARAMETERS, const zend_bool exclusive) - */ static void apc_store_helper(INTERNAL_FUNCTION_PARAMETERS, const zend_bool exclusive) { zval *key; @@ -510,33 +490,24 @@ static void apc_store_helper(INTERNAL_FUNCTION_PARAMETERS, const zend_bool exclu RETURN_FALSE; } } -/* }}} */ -/* {{{ proto bool apcu_enabled(void) - returns true when apcu is usable in the current environment */ +/* proto bool apcu_enabled(void): returns true when apcu is usable in the current environment */ PHP_FUNCTION(apcu_enabled) { if (zend_parse_parameters_none() == FAILURE) { return; } RETURN_BOOL(APCG(enabled)); } -/* }}} */ -/* {{{ proto int apcu_store(mixed key, mixed var [, long ttl ]) - */ +/* proto int apcu_store(mixed key, mixed var [, long ttl ]) */ PHP_FUNCTION(apcu_store) { apc_store_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } -/* }}} */ -/* {{{ proto int apcu_add(mixed key, mixed var [, long ttl ]) - */ +/* proto int apcu_add(mixed key, mixed var [, long ttl ]) */ PHP_FUNCTION(apcu_add) { apc_store_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } -/* }}} */ - -/* {{{ php_inc_updater */ struct php_inc_updater_args { zend_long step; @@ -549,8 +520,7 @@ static zend_bool php_inc_updater(apc_cache_t *cache, zend_long *entry, void *dat return 1; } -/* {{{ proto long apcu_inc(string key [, long step [, bool& success [, long ttl]]]) - */ +/* proto long apcu_inc(string key [, long step [, bool& success [, long ttl]]]) */ PHP_FUNCTION(apcu_inc) { zend_string *key; struct php_inc_updater_args args; @@ -579,10 +549,8 @@ PHP_FUNCTION(apcu_inc) { RETURN_FALSE; } -/* }}} */ -/* {{{ proto long apcu_dec(string key [, long step [, bool &success [, long ttl]]]) - */ +/* proto long apcu_dec(string key [, long step [, bool &success [, long ttl]]]) */ PHP_FUNCTION(apcu_dec) { zend_string *key; struct php_inc_updater_args args; @@ -612,19 +580,15 @@ PHP_FUNCTION(apcu_dec) { RETURN_FALSE; } -/* }}} */ -/* {{{ php_cas_updater */ static zend_bool php_cas_updater(apc_cache_t *cache, zend_long *entry, void *data) { zend_long *vals = (zend_long *) data; zend_long old = vals[0]; zend_long new = vals[1]; return ATOMIC_CAS(*entry, old, new); } -/* }}} */ -/* {{{ proto int apcu_cas(string key, int old, int new) - */ +/* proto int apcu_cas(string key, int old, int new) */ PHP_FUNCTION(apcu_cas) { zend_string *key; zend_long vals[2]; @@ -642,10 +606,8 @@ PHP_FUNCTION(apcu_cas) { RETURN_BOOL(apc_cache_atomic_update_long(apc_user_cache, key, php_cas_updater, &vals, 0, 0)); } -/* }}} */ -/* {{{ proto mixed apcu_fetch(mixed key[, bool &success]) - */ +/* proto mixed apcu_fetch(mixed key[, bool &success]) */ PHP_FUNCTION(apcu_fetch) { zval *key; zval *success = NULL; @@ -697,10 +659,8 @@ PHP_FUNCTION(apcu_fetch) { RETURN_FALSE; } } -/* }}} */ -/* {{{ proto mixed apcu_exists(mixed key) - */ +/* proto mixed apcu_exists(mixed key) */ PHP_FUNCTION(apcu_exists) { zval *key; time_t t; @@ -740,10 +700,8 @@ PHP_FUNCTION(apcu_exists) { RETURN_FALSE; } } -/* }}} */ -/* {{{ proto mixed apcu_delete(mixed keys) - */ +/* proto mixed apcu_delete(mixed keys) */ PHP_FUNCTION(apcu_delete) { zval *keys; @@ -792,7 +750,6 @@ PHP_FUNCTION(apcu_entry) { apc_cache_entry(apc_user_cache, key, &fci, &fcc, ttl, now, return_value); } -/* }}} */ #ifdef APC_DEBUG /* This function is used to test TTL behavior without having to perform sleeps. */ @@ -817,8 +774,7 @@ PHP_FUNCTION(apcu_inc_request_time) { } #endif -/* {{{ module definition structure */ - +/* module definition structure */ zend_module_entry apcu_module_entry = { STANDARD_MODULE_HEADER, PHP_APCU_EXTNAME, @@ -831,7 +787,6 @@ zend_module_entry apcu_module_entry = { PHP_APCU_VERSION, STANDARD_MODULE_PROPERTIES }; -/* }}} */ #ifdef COMPILE_DL_APCU ZEND_GET_MODULE(apcu) @@ -839,7 +794,6 @@ ZEND_GET_MODULE(apcu) ZEND_TSRMLS_CACHE_DEFINE(); #endif #endif -/* }}} */ /* * Local variables: From fe599cd1045a664fb83e633fbe8705184dc08fee Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Thu, 14 Aug 2025 22:02:25 +0200 Subject: [PATCH 086/101] Fix releasing of ref_count in apc_cache_store() (#587) In apc_cache_store(), the ref_count was decremented even when the entry needed to be deleted. This allowed the entry to be moved by defragmentation (by another process) before deletion. As a result, the pointer no longer pointed to the desired entry during deletion, causing segmentation faults. --- apc_cache.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apc_cache.c b/apc_cache.c index 514734fa..7a2b92cd 100644 --- a/apc_cache.c +++ b/apc_cache.c @@ -565,14 +565,16 @@ PHP_APCU_API zend_bool apc_cache_store( php_apc_try { ret = apc_cache_wlocked_insert(cache, entry, exclusive); } php_apc_finally { - /* release entry, because the ref_count of a new entry is initialized to 1 during allocation */ - apc_cache_entry_release(cache, entry); apc_cache_wunlock(cache); - } php_apc_end_try(); - if (!ret) { - free_entry(cache, entry); - } + if (ret) { + /* release entry, because the ref_count of a new entry is initialized to 1 during allocation */ + apc_cache_entry_release(cache, entry); + } else { + /* the entry mustn't be released before it is freed to prevent defragmentation from moving the entry */ + free_entry(cache, entry); + } + } php_apc_end_try(); return ret; } From 25cd3bc1977bdf640dc17660473e34962724ef94 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Wed, 20 Aug 2025 11:48:22 +0200 Subject: [PATCH 087/101] Remove report_memleaks ini directive from tests (#590) The "report_memleaks" ini directive is deprecated in PHP 8.5 and has therefore been removed from the tests. --- tests/apc_006.phpt | 1 - tests/apc_006_php73.phpt | 1 - tests/apc_006_php81.phpt | 1 - 3 files changed, 3 deletions(-) diff --git a/tests/apc_006.phpt b/tests/apc_006.phpt index 6ec03915..6df3b7c8 100644 --- a/tests/apc_006.phpt +++ b/tests/apc_006.phpt @@ -9,7 +9,6 @@ if (PHP_VERSION_ID >= 70300) die('skip Only for PHP < 7.3'); apc.enabled=1 apc.enable_cli=1 apc.serializer=php -report_memleaks=0 --FILE-- = 80100) die('skip Only for PHP < 8.1'); apc.enabled=1 apc.enable_cli=1 apc.serializer=php -report_memleaks=0 --FILE-- = 8.1'); apc.enabled=1 apc.enable_cli=1 apc.serializer=php -report_memleaks=0 --FILE-- Date: Thu, 28 Aug 2025 11:32:57 +0200 Subject: [PATCH 088/101] Release apcu 5.1.27 --- package.xml | 10 ++++++++-- php_apc.h | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package.xml b/package.xml index 7addbb23..2183d8c2 100644 --- a/package.xml +++ b/package.xml @@ -28,9 +28,9 @@ nikic@php.net yes - 2025-08-05 + 2025-08-28 - 5.1.27-dev + 5.1.27 5.1.18 @@ -39,6 +39,12 @@ PHP License + - Fixed another hang introduced in apcu 5.1.25, which can occur when defragmentation is triggered + under load. + + Internal changes: + - The report_memleaks INI directive has been removed from all tests as it will be deprecated in + PHP 8.5. diff --git a/php_apc.h b/php_apc.h index b702b73c..6e20f2f1 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.27-dev" +#define PHP_APCU_VERSION "5.1.27" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From 3245a254e39b2f21e8a176a8e2d3d42d83e43cc2 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Thu, 28 Aug 2025 11:44:24 +0200 Subject: [PATCH 089/101] Back to dev --- package.xml | 28 +++++++++++++++++++++------- php_apc.h | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/package.xml b/package.xml index 2183d8c2..b3291d95 100644 --- a/package.xml +++ b/package.xml @@ -30,7 +30,7 @@ 2025-08-28 - 5.1.27 + 5.1.28-dev 5.1.18 @@ -39,12 +39,6 @@ PHP License - - Fixed another hang introduced in apcu 5.1.25, which can occur when defragmentation is triggered - under load. - - Internal changes: - - The report_memleaks INI directive has been removed from all tests as it will be deprecated in - PHP 8.5. @@ -198,6 +192,26 @@ + + 2025-08-28 + + 5.1.27 + 5.1.18 + + + stable + stable + + PHP License + + - Fixed another hang introduced in apcu 5.1.25, which can occur when defragmentation is triggered + under load. + + Internal changes: + - The report_memleaks INI directive has been removed from all tests as it will be deprecated in + PHP 8.5. + + 2025-08-05 diff --git a/php_apc.h b/php_apc.h index 6e20f2f1..640e9f67 100644 --- a/php_apc.h +++ b/php_apc.h @@ -33,7 +33,7 @@ #include "apc.h" #include "apc_globals.h" -#define PHP_APCU_VERSION "5.1.27" +#define PHP_APCU_VERSION "5.1.28-dev" #define PHP_APCU_EXTNAME "apcu" PHP_APCU_API zend_bool apc_is_enabled(void); From 023c16b809e653db57a6f87dc8ecf058a28c94c6 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Tue, 19 Aug 2025 23:24:14 +0200 Subject: [PATCH 090/101] Reclaim unused space from allocated blocks during defragmentation The fprev field now stores the used memory size for allocated blocks. This is used during defragmentation to shrink blocks if more memory has been allocated than necessary. --- apc_sma.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apc_sma.c b/apc_sma.c index af47f622..98a3e962 100644 --- a/apc_sma.c +++ b/apc_sma.c @@ -178,6 +178,9 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size) cur->fnext = 0; + /* store used space to be able to reclaim unused space during defragmentation */ + cur->fprev = realsize; + /* update the segment header */ smaheader->avail -= cur->size; @@ -454,6 +457,7 @@ PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move sma_header_t *smaheader = SMA_HDR(sma); block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)) + ALIGNWORD(sizeof(block_t))); block_t *first = BLOCKAT(ALIGNWORD(sizeof(sma_header_t))); + size_t reclaimed_size = 0; if (!SMA_LOCK(sma)) { return; @@ -488,8 +492,13 @@ PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move continue; } + /* reclaim unused space from the allocated block (nxt->fprev contains the used space) */ + size_t free_size = nxt->size - nxt->fprev; + reclaimed_size += free_size; + nxt->size -= free_size; + free_size += cur->size; + /* swap cur and nxt by moving nxt (incl. header) and initializing a new block header for cur behind it */ - size_t free_size = cur->size; memmove(cur, nxt, nxt->size); cur->prev_size = 0; cur = NEXT_SBLOCK(cur); @@ -503,6 +512,8 @@ PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move } } + smaheader->avail += reclaimed_size; + SMA_UNLOCK(sma); } From a6b94a5dea803607257ee0a74713ead25cf64bf1 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Sun, 21 Sep 2025 20:34:50 +0200 Subject: [PATCH 091/101] Remove test bug63224.phpt The test has been removed because it serves no purpose anymore, as the opcode caching functionality was removed from the project long ago. Furthermore, the magic method __sleep() is deprecated in PHP 8.5, causing this test to fail the CI pipeline starting with PHP 8.5. --- package.xml | 1 - tests/bug63224.phpt | 310 -------------------------------------------- 2 files changed, 311 deletions(-) delete mode 100644 tests/bug63224.phpt diff --git a/package.xml b/package.xml index b3291d95..29250ed8 100644 --- a/package.xml +++ b/package.xml @@ -96,7 +96,6 @@ - diff --git a/tests/bug63224.phpt b/tests/bug63224.phpt deleted file mode 100644 index 92ea3c90..00000000 --- a/tests/bug63224.phpt +++ /dev/null @@ -1,310 +0,0 @@ ---TEST-- -APC: Bug #63224 error in __sleep whit reference to other classes ---SKIPIF-- - ---CONFLICTS-- -server ---FILE-- -b->f(); - return array('b'); - } -} - - -class B{ - const A_CONSTANT = 1; - public \$var; - - public function f(){ - \$this->var = self::A_CONSTANT; - } -} - - -if(isset(\$_SESSION['lalala'])){ - echo "
";
-	\$a = \$_SESSION['lalala'];
-	print_r(\$a);
-} else {
-	echo "no session yet, first run\n";
-}
-
-//	another file
-//	class A and B use autoload
-\$b = new B();
-\$a = new A();
-\$a->b = \$b;
-
-\$_SESSION['lalala'] = \$a;
-session_write_close();
-FL;
-
-$args = array(
-	'apc.enabled=1',
-	'apc.cache_by_default=1',
-	'apc.enable_cli=1',
-    'session.gc_probability=0',
-);
-
-server_start($file, $args);
-
-$sid = md5(uniqid("call me maybe", true));
-for ($i = 0; $i < 10; $i++) {
-	$send = "GET / HTTP/1.1\n" .
-			"Host: " . PHP_CLI_SERVER_HOSTNAME . "\n" .
-			"Cookie: PHPSESSID=$sid;" .
-			"\r\n\r\n";
-	for ($j = 0; $j < $num_servers; $j++) {
-		run_test(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT+$j, $send);
-	}
-}
-echo 'done';
---EXPECT--
-no session yet, first run
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-
A Object
-(
-    [b] => B Object
-        (
-            [var] => 1
-        )
-
-)
-done

From d17bf0bb14caa7ebd4573168444428cb36813223 Mon Sep 17 00:00:00 2001
From: Arndt Kaiser 
Date: Wed, 24 Sep 2025 16:08:29 +0200
Subject: [PATCH 092/101] Removed obsolete files from the test directory

These files are no longer used by any test and have therefore been
deleted.
---
 package.xml                       | 3 ---
 tests/get_included_files_inc1.inc | 3 ---
 tests/get_included_files_inc2.inc | 4 ----
 tests/get_included_files_inc3.inc | 4 ----
 4 files changed, 14 deletions(-)
 delete mode 100644 tests/get_included_files_inc1.inc
 delete mode 100644 tests/get_included_files_inc2.inc
 delete mode 100644 tests/get_included_files_inc3.inc

diff --git a/package.xml b/package.xml
index 29250ed8..fd225db4 100644
--- a/package.xml
+++ b/package.xml
@@ -97,9 +97,6 @@
     
     
     
-    
-    
-    
     
     
     
diff --git a/tests/get_included_files_inc1.inc b/tests/get_included_files_inc1.inc
deleted file mode 100644
index 344e300e..00000000
--- a/tests/get_included_files_inc1.inc
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/tests/get_included_files_inc2.inc b/tests/get_included_files_inc2.inc
deleted file mode 100644
index 318eba00..00000000
--- a/tests/get_included_files_inc2.inc
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/tests/get_included_files_inc3.inc b/tests/get_included_files_inc3.inc
deleted file mode 100644
index f666edf2..00000000
--- a/tests/get_included_files_inc3.inc
+++ /dev/null
@@ -1,4 +0,0 @@
-

From c48075c2af465c001a09be10b2de4671730eb256 Mon Sep 17 00:00:00 2001
From: Arndt Kaiser 
Date: Fri, 26 Sep 2025 15:43:31 +0200
Subject: [PATCH 093/101] Add PHP 8.5 to Windows CI

---
 .github/workflows/config.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml
index 7cbb83d9..aa0999f4 100644
--- a/.github/workflows/config.yml
+++ b/.github/workflows/config.yml
@@ -35,7 +35,7 @@ jobs:
         shell: cmd
     strategy:
       matrix:
-        version: ["8.0", "8.1", "8.2", "8.3", "8.4"]
+        version: ["8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]
         arch: [x64]
         ts: [nts, ts]
     runs-on: windows-2022
@@ -44,7 +44,7 @@ jobs:
         uses: actions/checkout@v4
       - name: Setup PHP
         id: setup-php
-        uses: php/setup-php-sdk@v0.10
+        uses: php/setup-php-sdk@v0.11
         with:
           version: ${{matrix.version}}
           arch: ${{matrix.arch}}

From efcaa355fd2aad17e10eff0b6253290c76a92146 Mon Sep 17 00:00:00 2001
From: Arndt Kaiser 
Date: Fri, 10 Oct 2025 18:15:06 +0200
Subject: [PATCH 094/101] Improve SMA allocation performance and lock
 contention

The function sma_allocate() now inserts the unused part of a split block
at the beginning of the free list, instead of reinserting it at the
position of the original block. This prevents large blocks from falling
behind in the list when many small blocks are inserted at the beginning
of the list by sma_deallocate(). Benchmarks show that this improves
allocation performance and reduces the time the SMA lock must be held.
---
 apc_sma.c   | 21 +++++++++++----------
 package.xml |  3 +++
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/apc_sma.c b/apc_sma.c
index 98a3e962..1929069a 100644
--- a/apc_sma.c
+++ b/apc_sma.c
@@ -138,7 +138,6 @@ static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) {
 /* sma_allocate: tries to allocate at least size bytes of shared memory */
 static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size)
 {
-	block_t* prv;           /* block prior to working block */
 	block_t* cur;           /* working block in list */
 	size_t realsize;        /* actual size of block needed, including block header */
 
@@ -150,11 +149,12 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size)
 		return SIZE_MAX;
 	}
 
+	/* remove cur from the list of free blocks */
+	BLOCKAT(cur->fprev)->fnext = cur->fnext;
+	BLOCKAT(cur->fnext)->fprev = cur->fprev;
+
 	if (cur->size >= realsize && cur->size < (realsize + smaheader->min_block_size)) {
-		/* cur is big enough for realsize, but too small to split - unlink it */
-		prv = BLOCKAT(cur->fprev);
-		prv->fnext = cur->fnext;
-		BLOCKAT(cur->fnext)->fprev = OFFSET(prv);
+		/* cur is big enough for realsize, but too small to split */
 		NEXT_SBLOCK(cur)->prev_size = 0;  /* block is alloc'd */
 	} else {
 		/* cur is too big; split it into two smaller blocks */
@@ -169,11 +169,12 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size)
 		NEXT_SBLOCK(nxt)->prev_size = nxt->size;  /* adjust size */
 		SET_CANARY(nxt);
 
-		/* replace cur with next in free list */
-		nxt->fnext = cur->fnext;
-		nxt->fprev = cur->fprev;
-		BLOCKAT(nxt->fnext)->fprev = OFFSET(nxt);
-		BLOCKAT(nxt->fprev)->fnext = OFFSET(nxt);
+		/* put the remaining block (nxt) back into the free list */
+		block_t *dst = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
+		nxt->fnext = dst->fnext;
+		nxt->fprev = OFFSET(dst);
+		dst->fnext = OFFSET(nxt);
+		BLOCKAT(nxt->fnext)->fprev = dst->fnext;
 	}
 
 	cur->fnext = 0;
diff --git a/package.xml b/package.xml
index fd225db4..9eb3d4c6 100644
--- a/package.xml
+++ b/package.xml
@@ -39,6 +39,9 @@
  
  PHP License
  
+  - Shared memory for new entries is allocated faster in scenarios with many free memory blocks.
+    This should improve APCu's insertion performance when entries are frequently deleted or
+    replaced, or when APCu is used with larger amounts of memory.
  
  
   

From 6846950ac8c26206218146a50f93e17b6524e031 Mon Sep 17 00:00:00 2001
From: Arndt Kaiser 
Date: Fri, 10 Oct 2025 18:29:42 +0200
Subject: [PATCH 095/101] Small SMA code improvements

The new functions link_free_block() and unlink_free_block_at_start()
are used to insert or remove blocks from the free list. This improves
readability and removes duplicate code. Also, some code comments have
been refined.
---
 apc_sma.c | 51 +++++++++++++++++++++++++++++----------------------
 1 file changed, 29 insertions(+), 22 deletions(-)

diff --git a/apc_sma.c b/apc_sma.c
index 1929069a..a848b5d2 100644
--- a/apc_sma.c
+++ b/apc_sma.c
@@ -98,6 +98,21 @@ struct block_t {
 /* How many extra blocks to check for a better fit */
 #define BEST_FIT_LIMIT 3
 
+static inline void link_free_block_at_start(sma_header_t *smaheader, block_t *cur) {
+	block_t *dst = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
+
+	/* insert cur as first block in the free list */
+	cur->fnext = dst->fnext;
+	cur->fprev = OFFSET(dst);
+	dst->fnext = OFFSET(cur);
+	BLOCKAT(cur->fnext)->fprev = dst->fnext;
+}
+
+static inline void unlink_free_block(sma_header_t *smaheader, block_t *cur) {
+	BLOCKAT(cur->fprev)->fnext = cur->fnext;
+	BLOCKAT(cur->fnext)->fprev = cur->fprev;
+}
+
 static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) {
 	block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
 	block_t *found = NULL;
@@ -150,8 +165,7 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size)
 	}
 
 	/* remove cur from the list of free blocks */
-	BLOCKAT(cur->fprev)->fnext = cur->fnext;
-	BLOCKAT(cur->fnext)->fprev = cur->fprev;
+	unlink_free_block(smaheader, cur);
 
 	if (cur->size >= realsize && cur->size < (realsize + smaheader->min_block_size)) {
 		/* cur is big enough for realsize, but too small to split */
@@ -170,13 +184,10 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size)
 		SET_CANARY(nxt);
 
 		/* put the remaining block (nxt) back into the free list */
-		block_t *dst = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
-		nxt->fnext = dst->fnext;
-		nxt->fprev = OFFSET(dst);
-		dst->fnext = OFFSET(nxt);
-		BLOCKAT(nxt->fnext)->fprev = dst->fnext;
+		link_free_block_at_start(smaheader, nxt);
 	}
 
+	/* mark cur as allocated */
 	cur->fnext = 0;
 
 	/* store used space to be able to reclaim unused space during defragmentation */
@@ -209,10 +220,10 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset)
 	size = cur->size;
 
 	if (cur->prev_size != 0) {
-		/* remove prv from list */
+		/* remove prv from the list of free blocks */
 		prv = PREV_SBLOCK(cur);
-		BLOCKAT(prv->fnext)->fprev = prv->fprev;
-		BLOCKAT(prv->fprev)->fnext = prv->fnext;
+		unlink_free_block(smaheader, prv);
+
 		/* cur and prv share an edge, combine them */
 		prv->size += cur->size;
 
@@ -223,23 +234,21 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset)
 	nxt = NEXT_SBLOCK(cur);
 	if (nxt->fnext != 0) {
 		assert(NEXT_SBLOCK(NEXT_SBLOCK(cur))->prev_size == nxt->size);
+		/* remove nxt from the list of free blocks */
+		unlink_free_block(smaheader, nxt);
+
 		/* cur and nxt shared an edge, combine them */
-		BLOCKAT(nxt->fnext)->fprev = nxt->fprev;
-		BLOCKAT(nxt->fprev)->fnext = nxt->fnext;
 		cur->size += nxt->size;
 
 		CHECK_CANARY(nxt);
 		RESET_CANARY(nxt);
 	}
 
+	/* mark in the sequentially next block that the previous block is free */
 	NEXT_SBLOCK(cur)->prev_size = cur->size;
 
-	/* insert new block after prv */
-	prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
-	cur->fnext = prv->fnext;
-	prv->fnext = OFFSET(cur);
-	cur->fprev = OFFSET(prv);
-	BLOCKAT(cur->fnext)->fprev = OFFSET(cur);
+	/* insert cur into the free list */
+	link_free_block_at_start(smaheader, cur);
 
 	return size;
 }
@@ -482,10 +491,8 @@ PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move
 		/* if nxt is the last block, or if nxt can't be moved, cur can't be combined with other free blocks */
 		if (nxt->size == 0 || !move(data, (char *)nxt + ALIGNWORD(sizeof(block_t)), (char *)cur + ALIGNWORD(sizeof(block_t)))) {
 			/* insert cur into the free list */
-			cur->fnext = first->fnext;
-			cur->fprev = OFFSET(first);
-			first->fnext = OFFSET(cur);
-			BLOCKAT(cur->fnext)->fprev = first->fnext;
+			link_free_block_at_start(smaheader, cur);
+
 			cur->prev_size = 0;
 			nxt->prev_size = cur->size;
 

From 9eb62d17a2ade37cd3e5839e94ab864be6efdaf3 Mon Sep 17 00:00:00 2001
From: Arndt Kaiser 
Date: Fri, 17 Oct 2025 18:27:01 +0200
Subject: [PATCH 096/101] Check result of SMA lock acquisitions

This should never happen, but it should still be handled properly to
avoid worse.
---
 apc_sma.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/apc_sma.c b/apc_sma.c
index a848b5d2..e3057b89 100644
--- a/apc_sma.c
+++ b/apc_sma.c
@@ -391,7 +391,11 @@ PHP_APCU_API apc_sma_info_t *apc_sma_info(apc_sma_t* sma, zend_bool limited) {
 		return info;
 	}
 
-	SMA_LOCK(sma);
+	if (!SMA_LOCK(sma)) {
+		efree(info);
+		return NULL;
+	}
+
 	sma_header_t *smaheader = SMA_HDR(sma);
 	block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
 	apc_sma_link_t **link = &info->list;
@@ -445,7 +449,10 @@ PHP_APCU_API zend_bool apc_sma_check_avail_contiguous(apc_sma_t *sma, size_t siz
 		return 0;
 	}
 
-	SMA_LOCK(sma);
+	if (!SMA_LOCK(sma)) {
+		return 0;
+	}
+
 	block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
 
 	/* Look for a contiguous block of memory */

From d720b628ed2c6a99237c398ce0e449c7a5574b7a Mon Sep 17 00:00:00 2001
From: Arndt Kaiser 
Date: Wed, 22 Oct 2025 01:36:48 +0200
Subject: [PATCH 097/101] Prevent PHP warnings when using apc.php with APCu
 versions <= 5.1.24

Since the array keys "cleanups" and "defragmentations" only exist since
APCu 5.1.25, PHP warnings could occur if you are using a current version
of apc.php with APCu versions <= 5.1.24.
---
 apc.php | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/apc.php b/apc.php
index afe7b8e5..046f2286 100644
--- a/apc.php
+++ b/apc.php
@@ -766,6 +766,8 @@ function block_sort($array1, $array2)
     $insert_rate_user = sprintf("%.2f", $cache['num_inserts'] ? (($cache['num_inserts'])/$elapsed) : 0);
     $apcversion = phpversion('apcu');
     $phpversion = phpversion();
+    $cleanups = $cache['cleanups'] ?? '-';
+    $defragmentations = $cache['defragmentations'] ?? '-';
     $number_vars = $cache['num_entries'];
     $size_vars = bsize($cache['mem_size']);
     $num_hits_and_misses = $cache['num_hits'] + $cache['num_misses'];
@@ -804,8 +806,8 @@ function block_sort($array1, $array2)
             Hit Rate$hit_rate_user cache requests/second
             Miss Rate$miss_rate_user cache requests/second
             Insert Rate$insert_rate_user cache requests/second
-            Cache cleanup count{$cache['cleanups']}
-            Cache defragmentation count{$cache['defragmentations']}
+            Cache cleanup count$cleanups
+            Cache defragmentation count$defragmentations
             Cache full count{$cache['expunges']}
         
         

From 7d1c95b0f5fe045f088b78cfefb885f93015460d Mon Sep 17 00:00:00 2001
From: k0d3r1s 
Date: Thu, 4 Dec 2025 16:09:14 +0200
Subject: [PATCH 098/101] The zval_dtor() alias of zval_ptr_dtor_nogc() has
 been removed

Call zval_ptr_dtor_nogc() directly instead
---
 apc_cache.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apc_cache.c b/apc_cache.c
index 7a2b92cd..a12a9377 100644
--- a/apc_cache.c
+++ b/apc_cache.c
@@ -652,7 +652,7 @@ static int apc_load_data(apc_cache_t* cache, const char *data_file)
 				apc_cache_store(
 					cache, name, &data, 0, 1);
 				zend_string_release(name);
-				zval_dtor(&data);
+				zval_ptr_dtor_nogc(&data);
 			}
 			return 1;
 		}

From 352caaf7d52a088cb28242858ff445b981ee3e95 Mon Sep 17 00:00:00 2001
From: Arndt Kaiser 
Date: Sun, 7 Dec 2025 08:52:22 +0100
Subject: [PATCH 099/101] Prevent cache wipes caused by huge allocation
 attempts

Trying to insert entries larger than the shared memory no longer results
in the whole cache being discarded.
---
 apc_sma.c          |  6 ++++++
 apc_sma.h          |  1 +
 package.xml        |  2 ++
 tests/apc_026.phpt | 21 +++++++++++++++++++++
 4 files changed, 30 insertions(+)
 create mode 100644 tests/apc_026.phpt

diff --git a/apc_sma.c b/apc_sma.c
index e3057b89..1d31256e 100644
--- a/apc_sma.c
+++ b/apc_sma.c
@@ -273,6 +273,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex
 	SMA_CREATE_LOCK(&smaheader->sma_lock);
 	smaheader->min_block_size = min_alloc_size > 0 ? ALIGNWORD(min_alloc_size + ALIGNWORD(sizeof(block_t))) : MINBLOCKSIZE;
 	smaheader->avail = sma->size - ALIGNWORD(sizeof(sma_header_t)) - ALIGNWORD(sizeof(block_t)) - ALIGNWORD(sizeof(block_t));
+	sma->max_alloc_size = smaheader->avail - ALIGNWORD(sizeof(block_t));
 
 	block_t *first = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
 	first->size = 0;
@@ -318,6 +319,11 @@ PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n, apc_sma_malloc_init_
 restart:
 	assert(sma->initialized);
 
+	/* Prevent cache wipes caused by huge allocations that don't fit into shm */
+	if (n > sma->max_alloc_size) {
+		return NULL;
+	}
+
 	if (!SMA_LOCK(sma)) {
 		return NULL;
 	}
diff --git a/apc_sma.h b/apc_sma.h
index 522f7d78..6bd35a65 100644
--- a/apc_sma.h
+++ b/apc_sma.h
@@ -60,6 +60,7 @@ typedef struct _apc_sma_t {
 
 	/* info */
 	size_t size;                   /* segment size */
+	size_t max_alloc_size;         /* max size of memory available for allocation */
 	void  *shmaddr;                /* address of shm segment */
 } apc_sma_t;
 
diff --git a/package.xml b/package.xml
index 9eb3d4c6..d3f47ac5 100644
--- a/package.xml
+++ b/package.xml
@@ -39,9 +39,11 @@
  
  PHP License
  
+  - Defragmentation now reclaims unused space from moved entries, resulting in better compaction.
   - Shared memory for new entries is allocated faster in scenarios with many free memory blocks.
     This should improve APCu's insertion performance when entries are frequently deleted or
     replaced, or when APCu is used with larger amounts of memory.
+  - Trying to insert entries larger than the shared memory no longer results in cache wipes.
  
  
   
diff --git a/tests/apc_026.phpt b/tests/apc_026.phpt
new file mode 100644
index 00000000..947f016e
--- /dev/null
+++ b/tests/apc_026.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Huge allocations which don't fit into shm shouldn't cause cache wipes
+--INI--
+apc.enabled=1
+apc.enable_cli=1
+apc.shm_size=1M
+--FILE--
+
+--EXPECT--
+bool(false)
+bool(true)

From 819fce90c469a5666b1706036cd66120af419f1f Mon Sep 17 00:00:00 2001
From: Nikita Popov 
Date: Sun, 7 Dec 2025 08:58:16 +0100
Subject: [PATCH 100/101] Release apcu 5.1.28

---
 package.xml | 6 ++++--
 php_apc.h   | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/package.xml b/package.xml
index d3f47ac5..8c995b22 100644
--- a/package.xml
+++ b/package.xml
@@ -28,9 +28,9 @@
   nikic@php.net
   yes
  
- 2025-08-28
+ 2025-12-07
  
-  5.1.28-dev
+  5.1.28
   5.1.18
  
  
@@ -44,6 +44,8 @@
     This should improve APCu's insertion performance when entries are frequently deleted or
     replaced, or when APCu is used with larger amounts of memory.
   - Trying to insert entries larger than the shared memory no longer results in cache wipes.
+  - Fix build against PHP 8.6.
+  - Fix apc.php compatibility with older apcu versions.
  
  
   
diff --git a/php_apc.h b/php_apc.h
index 640e9f67..96f5b01e 100644
--- a/php_apc.h
+++ b/php_apc.h
@@ -33,7 +33,7 @@
 #include "apc.h"
 #include "apc_globals.h"
 
-#define PHP_APCU_VERSION "5.1.28-dev"
+#define PHP_APCU_VERSION "5.1.28"
 #define PHP_APCU_EXTNAME "apcu"
 
 PHP_APCU_API zend_bool apc_is_enabled(void);

From ca7e208b34f9c10ff6b1538de503a37b11af560d Mon Sep 17 00:00:00 2001
From: Albert Skonieczny <50720306+albertsko@users.noreply.github.com>
Date: Wed, 26 Aug 2026 08:24:15 +0200
Subject: [PATCH 101/101] chore(PLATFORM-11898): cover soft expiry of entries
 with per-entry TTL

---
 package.xml                 |  1 +
 tests/apc_019.phpt          |  6 ++--
 tests/apc_020.phpt          | 23 ++++++++------
 tests/apc_soft_expired.phpt | 60 +++++++++++++++++++++++++++++++++++++
 4 files changed, 78 insertions(+), 12 deletions(-)
 create mode 100644 tests/apc_soft_expired.phpt

diff --git a/package.xml b/package.xml
index 8c995b22..692dafeb 100644
--- a/package.xml
+++ b/package.xml
@@ -91,6 +91,7 @@
     
     
     
+    
     
     
     
diff --git a/tests/apc_019.phpt b/tests/apc_019.phpt
index a9baeb6e..8d827e59 100644
--- a/tests/apc_019.phpt
+++ b/tests/apc_019.phpt
@@ -1,9 +1,9 @@
 --TEST--
-The per-entry TTL should take precedence over the global TTL
+The global TTL may soft-expire entries with a per-entry TTL
 --SKIPIF--
 
 --INI--
 apc.enabled=1
@@ -34,7 +34,7 @@ var_dump(apcu_fetch("EzFY"));
 --EXPECT--
 T+2
 bool(false)
-int(42)
+bool(false)
 T+4
 bool(false)
 bool(false)
diff --git a/tests/apc_020.phpt b/tests/apc_020.phpt
index fa038ae9..bd497f11 100644
--- a/tests/apc_020.phpt
+++ b/tests/apc_020.phpt
@@ -3,7 +3,7 @@ Test default expunge logic wrt global and per-entry TTLs
 --SKIPIF--
 
 --INI--
 apc.enabled=1
@@ -18,18 +18,23 @@ apcu_store("no_ttl_unaccessed", str_repeat('x', 500));
 apcu_store("no_ttl_accessed", 24);
 apcu_store("ttl", 42, 3);
 
+// Fill the cache without triggering an expunge.
+$entry_size = apcu_sma_info(true)['avail_mem'];
+apcu_store(sprintf("key%06d", 0), str_repeat('x', 500));
+$entry_size -= apcu_sma_info(true)['avail_mem'];
+$i = 1;
+while (apcu_sma_info(true)['avail_mem'] >= $entry_size) {
+    apcu_store(sprintf("key%06d", $i), str_repeat('x', 500));
+    $i++;
+}
+
 apcu_inc_request_time(1);
 apcu_fetch("no_ttl_accessed");
 
 apcu_inc_request_time(1);
 
-// Fill the cache
-$i = 0;
-do {
-    $tmp_avail = apcu_sma_info(true)['avail_mem'];
-    apcu_store("key" . $i, str_repeat('x', 500));
-    $i++;
-} while (apcu_sma_info(true)['avail_mem'] < $tmp_avail);
+// Trigger a default expunge after the entries have soft-expired.
+apcu_store("large_entry", str_repeat('x', 1000));
 
 var_dump(apcu_fetch("no_ttl_unaccessed"));
 var_dump(apcu_fetch("no_ttl_accessed"));
@@ -39,4 +44,4 @@ var_dump(apcu_fetch("ttl"));
 --EXPECT--
 bool(false)
 int(24)
-int(42)
+bool(false)
diff --git a/tests/apc_soft_expired.phpt b/tests/apc_soft_expired.phpt
new file mode 100644
index 00000000..0fcfe9da
--- /dev/null
+++ b/tests/apc_soft_expired.phpt
@@ -0,0 +1,60 @@
+--TEST--
+apcu_inc/dec() should not inc/dec soft expired entries based on global TTL setting
+--SKIPIF--
+
+--INI--
+apc.enabled=1
+apc.enable_cli=1
+apc.use_request_time=1
+apc.ttl=2
+--FILE--
+
+--EXPECT--
+T+0:
+int(1)
+int(1)
+int(-1)
+int(-1)
+T+1:
+int(2)
+int(2)
+int(-2)
+int(-2)
+T+4:
+int(1)
+int(1)
+int(-1)
+int(-1)