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"
-}
diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml
index a7d9e874..aa0999f4 100644
--- a/.github/workflows/config.yml
+++ b/.github/workflows/config.yml
@@ -4,11 +4,13 @@ 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", "8.4", "8.5"]
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@v2
+ uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
@@ -17,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
@@ -31,20 +35,21 @@ jobs:
shell: cmd
strategy:
matrix:
- version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2"]
- arch: [x64]
- ts: [nts, ts]
- runs-on: windows-latest
+ version: ["8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]
+ arch: [x64]
+ ts: [nts, ts]
+ runs-on: windows-2022
steps:
- name: Checkout apcu
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup PHP
id: setup-php
- uses: cmb69/setup-php-sdk@v0.6
+ uses: php/setup-php-sdk@v0.11
with:
version: ${{matrix.version}}
arch: ${{matrix.arch}}
ts: ${{matrix.ts}}
+ cache: true
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1
with:
@@ -60,17 +65,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@v2
+ uses: actions/upload-artifact@v4
with:
name: apcu-${{matrix.version}}-${{matrix.arch}}-${{matrix.ts}}
- path: .install
+ path: win-install
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.
-[](https://travis-ci.org/krakjoe/apcu)
-[](https://ci.appveyor.com/project/krakjoe/apcu/branch/master)
+[](https://github.com/krakjoe/apcu/actions/workflows/config.yml)
+[](https://ci.appveyor.com/project/krakjoe/apcu/branch/master)
Documentation
============
diff --git a/TECHNOTES.txt b/TECHNOTES.txt
index f993b61d..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,305 +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, an apc_cache_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.
-
- Immediately after the header 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 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 | shared |
- +--------+--------+----------------------------------+
+ +-----------+--------+-----------------------------------+
+ | smaheader | 0-size | free-block | 0-size |
+ +-----------+--------+-----------------------------------+
- The blocks are just a simple offset-based 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 *)shmaddr + offset))
+ #define BLOCKAT(offset) ((block_t*)((char *)smaheader + offset))
- where shmaddr = sma->shaddrs[0]
+ - The OFFSET macro goes the other way:
- And the OFFSET macro goes the other way:
+ #define OFFSET(block) ((int)(((char*)block) - (char*)smaheader))
- #define OFFSET(block) ((int)(((char*)block) - (char*)shmaddr))
+ Both macros assume the presence of the variable "smaheader" that points to the beginning
+ of the shared memory segment.
- 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:
+ 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 | block | block | block |
- +--------+-------+-------+-------------------------+
+ +-----------+--------+-------+------------------+--------+
+ | smaheader | 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 allocation. So we actually have:
- +--------+-------+ +-------------------------+
- | header | block |------>| block |
- +--------+-------+ +-------------------------+
+ +-----------+--------+ +------------------+--------+
+ | smaheader | 0-size |<----->| free-block | 0-size |
+ +-----------+--------+ +------------------+--------+
- And header->avail along with block->size of the remaining large
- block are updated accordingly. The arrow there representing the
+ 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 at the front with that
- 0-sized dummy block. This mostly prevents fragmentation.
+ 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 {
- 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_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 */
- 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; /* }}} */
-
- 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.
- entry = &cache->slots[s];
+ 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.
- cache->slots is our array of slots in the segment.
+ 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.
- 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.
+ 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).
- while (*entry) {
- /* process expired entries and check for entry with matching key */
+ 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:
+
+ #define ENTRYAT(offset) ((apc_cache_entry_t *)((uintptr_t)cache->header + (uintptr_t)offset))
+ #define ENTRYOF(entry) (((uintptr_t)entry) - (uintptr_t)cache->header)
+
+ 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.
+
+ apc_cache_wlocked_insert() shows what happens when a cache entry is inserted:
+
+ /* 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_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.
-
- 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 {
- 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 */
- 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 */
- };
- /* }}} */
-
- 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 ...
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.php b/apc.php
index af6b8515..046f2286 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
@@ -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,6 +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 $cleanups
+ Cache defragmentation count $defragmentations
Cache full count {$cache['expunges']}
@@ -961,7 +965,7 @@ function block_sort($array1, $array2)
'Top 500 ',
'All ',
'',
- ' Search: ',
+ ' Search: ',
' ',
'';
@@ -1054,7 +1058,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 ' ';
diff --git a/apc_cache.c b/apc_cache.c
index e2658237..1ff4305e 100644
--- a/apc_cache.c
+++ b/apc_cache.c
@@ -57,10 +57,10 @@
/* 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);
-zend_bool apc_unpersist(zval *dst, const zval *value, apc_serializer_t *serializer);
+ 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,28 +120,26 @@ 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
- && 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.
- * 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;
}
@@ -160,34 +158,67 @@ static zend_bool apc_cache_entry_expired(
|| apc_cache_entry_soft_expired(cache, entry, t);
}
-/* {{{ apc_cache_wlocked_remove_entry */
-static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, apc_cache_entry_t **entry)
-{
- apc_cache_entry_t *dead = *entry;
+/* 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;
+ }
- /* think here is safer */
- *entry = (*entry)->next;
+ /* 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) {
+ 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;
+ }
+}
+
+static void apc_cache_wlocked_remove_entry(apc_cache_t *cache, apc_cache_entry_t *entry)
+{
+ /* 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--;
- /* remove if there are no references */
- if (dead->ref_count <= 0) {
- free_entry(cache, dead);
+ /* free entry if there are no references */
+ if (entry->ref_count <= 0) {
+ free_entry(cache, entry);
} else {
/* add to gc if there are still refs */
- dead->next = cache->header->gc;
- dead->dtime = time(0);
- cache->header->gc = dead;
+ entry->dtime = time(0);
+ 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
@@ -199,38 +230,33 @@ static void apc_cache_wlocked_gc(apc_cache_t* cache)
return;
}
- {
- apc_cache_entry_t **entry = &cache->header->gc;
- time_t now = time(0);
+ time_t now = time(0);
- while (*entry != NULL) {
- time_t gc_sec = cache->gc_ttl ? (now - (*entry)->dtime) : 0;
+ 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;
- if (!(*entry)->ref_count || gc_sec > (time_t)cache->gc_ttl) {
- apc_cache_entry_t *dead = *entry;
-
- /* 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
- );
- }
-
- /* 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 */
+ apc_cache_wlocked_unlink_entry(cache, entry);
+ free_entry(cache, entry);
}
}
-/* }}} */
-/* {{{ php serializer */
+/* php serializer */
PHP_APCU_API int APC_SERIALIZER_NAME(php) (APC_SERIALIZER_ARGS)
{
smart_str strbuf = {0};
@@ -258,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;
@@ -275,52 +301,50 @@ 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;
}
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;
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);
/* 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->shmaddr = apc_sma_malloc(sma, cache_size);
+ cache->header = apc_sma_malloc(sma, cache_size, NULL);
- 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->ncleanups = 0;
+ cache->header->ndefragmentations = 0;
cache->header->nexpunges = 0;
- cache->header->gc = NULL;
+ cache->header->gc = 0;
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 = (uintptr_t *)((uintptr_t)cache->header + sizeof(apc_cache_header_t));
cache->sma = sma;
cache->serializer = serializer;
cache->nslots = nslots;
@@ -333,109 +357,119 @@ 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) {
- zend_string *key = new_entry->key;
+ 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);
+ 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);
+ 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 */
+ apc_cache_wlocked_link_entry(cache, entry_offset, new_entry);
+
+ cache->header->mem_size += new_entry->mem_size;
+ cache->header->nentries++;
+ cache->header->ninserts++;
+
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 = 0;
+ entry->prev = 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);
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;
}
/* 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 */
@@ -446,7 +480,7 @@ static inline apc_cache_entry_t *apc_cache_rlocked_find_nostat(
return entry;
}
- entry = entry->next;
+ entry_offset = entry->next;
}
return NULL;
@@ -455,15 +489,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 */
@@ -478,7 +514,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);
@@ -496,11 +532,9 @@ 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) {
- apc_cache_entry_t tmp_entry, *entry;
time_t t = apc_time();
zend_bool ret = 0;
@@ -513,13 +547,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);
@@ -530,17 +566,20 @@ PHP_APCU_API zend_bool apc_cache_store(
ret = apc_cache_wlocked_insert(cache, entry, exclusive);
} php_apc_finally {
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;
-} /* }}} */
+}
#ifndef ZTS
-/* {{{ data_unserialize */
static zval data_unserialize(const char *filename)
{
zval retval;
@@ -613,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;
}
@@ -623,7 +662,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
@@ -657,16 +696,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,
@@ -679,9 +715,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;
@@ -690,9 +724,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, ENTRYAT(*entry_offset));
}
}
@@ -707,9 +741,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) {
@@ -725,102 +758,93 @@ 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);
}
-/* }}} */
-/* {{{ 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 suitable = 0L;
- size_t available = 0L;
+ 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;
}
- /* make suitable selection */
- suitable = (cache->smart > 0L) ? (size_t) (cache->smart * size) : (size_t) (cache->sma->size/2);
-
- /* gc */
- apc_cache_wlocked_gc(cache);
+ /* 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;
+ }
- /* get available */
- available = apc_sma_get_avail_mem(cache->sma);
+ /* 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;
- /* 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++) {
+ uintptr_t *entry_offset = &cache->slots[i];
+ while (*entry_offset) {
+ apc_cache_entry_t *entry = ENTRYAT(*entry_offset);
- /* 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;
- }
+ 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_offset = &entry->next;
}
}
- apc_cache_wunlock(cache);
-}
-/* }}} */
-
-/* {{{ 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;
+ /* gc */
+ apc_cache_wlocked_gc(cache);
- if (!cache) {
- return NULL;
+ /* 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);
+ goto end_lbl;
}
- if (!apc_cache_rlock(cache)) {
- return NULL;
+ /* 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);
+
+ /* 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);
+ goto end_lbl;
}
- entry = apc_cache_rlocked_find_incref(cache, key, t);
- apc_cache_runlock(cache);
+ /* wipe lastkey */
+ memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t));
- return entry;
+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;
}
-/* }}} */
-/* {{{ 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;
@@ -848,9 +872,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;
@@ -863,14 +886,12 @@ 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;
}
-/* }}} */
-/* {{{ 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)
@@ -918,9 +939,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)
@@ -968,12 +987,9 @@ 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)
{
- apc_cache_entry_t **entry;
zend_ulong h;
size_t s;
@@ -989,11 +1005,12 @@ 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);
@@ -1001,40 +1018,18 @@ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key)
return 1;
}
- entry = &(*entry)->next;
+ entry_offset = &entry->next;
}
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->val, cache->serializer);
-}
-/* }}} */
-
-/* {{{ 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;
+ return apc_unpersist(dst, entry, cache->serializer);
}
-/* }}} */
static inline void array_add_long(zval *array, zend_string *key, zend_long lval) {
zval zv;
@@ -1048,13 +1043,12 @@ 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;
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);
@@ -1068,15 +1062,13 @@ 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;
zval gc;
zval slots;
- apc_cache_entry_t *p;
+ uintptr_t entry_offset;
zend_ulong j;
ZVAL_NULL(info);
@@ -1096,11 +1088,13 @@ 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);
-#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);
@@ -1114,12 +1108,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);
@@ -1129,9 +1126,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);
@@ -1144,11 +1145,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;
@@ -1167,9 +1165,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);
@@ -1184,14 +1183,13 @@ 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);
} 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 */
@@ -1210,7 +1208,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
)
@@ -1231,16 +1229,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) {
@@ -1280,7 +1276,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 e987a546..90aebb22 100644
--- a/apc_cache.h
+++ b/apc_cache.h
@@ -46,12 +46,10 @@ 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 {
- zend_string *key; /* entry key */
- zval val; /* the zval copied at store time */
- apc_cache_entry_t *next; /* 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 */
@@ -60,44 +58,41 @@ 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!) */
};
-/* }}} */
-/* {{{ 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 */
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 */
- 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; /* }}} */
+ 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 {
- 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) */
+ 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_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; /* }}} */
+} 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.
@@ -128,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
*/
@@ -151,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.
@@ -167,16 +164,8 @@ 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
- *
*/
PHP_APCU_API zend_bool apc_cache_fetch(apc_cache_t* cache, zend_string *key, time_t t, zval *dst);
@@ -191,14 +180,15 @@ 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);
/*
* 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.
@@ -208,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
@@ -229,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);
@@ -249,26 +238,22 @@ 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:
-* 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
*/
-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
@@ -314,6 +299,15 @@ 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_globals.h b/apc_globals.h
index c0a4436d..27100bd4 100644
--- a/apc_globals.h
+++ b/apc_globals.h
@@ -37,15 +37,15 @@
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 */
zend_long smart; /* smart value */
-#if APC_MMAP
- char *mmap_file_mask; /* mktemp-style file-mask to pass to mmap */
+#ifdef APC_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_iterator.c b/apc_iterator.c
index 07bd0e4e..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;
@@ -55,7 +54,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);
@@ -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;
@@ -178,25 +168,23 @@ 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;
}
-/* }}} */
-/* {{{ 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,10 +195,9 @@ 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;
apc_iterator_item_t *item;
time_t t = apc_time();
@@ -219,15 +206,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,36 +224,36 @@ 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;
}
-/* }}} */
-/* {{{ 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,48 +261,47 @@ 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;
}
-/* }}} */
-/* {{{ 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();
}
-/* }}} */
void apc_iterator_obj_init(apc_iterator_t *iterator, zval *search, zend_long format, size_t chunk_size, zend_long list)
{
@@ -500,7 +487,6 @@ PHP_METHOD(APCUIterator, getTotalHits) {
RETURN_LONG(iterator->hits);
}
-/* }}} */
PHP_METHOD(APCUIterator, getTotalSize) {
apc_iterator_t *iterator = apc_iterator_fetch(getThis());
@@ -534,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;
@@ -567,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);
@@ -600,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_mmap.c b/apc_mmap.c
index 9337d452..225de401 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
@@ -51,41 +51,58 @@
# define MAP_ANON MAP_ANONYMOUS
#endif
-apc_segment_t apc_mmap(char *file_mask, size_t size)
+static int apc_mmap_hugepage_flags(size_t size, zend_long hugepage_size)
{
- apc_segment_t segment;
+ 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;
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))) {
+ 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;
-#ifdef APC_MEMPROTECT
- remap = 0;
#endif
-#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");
}
-#ifdef APC_MEMPROTECT
- remap = 0; /* cannot remap */
-#endif
} 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) {
@@ -96,44 +113,34 @@ 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;
+ flags |= apc_mmap_hugepage_flags(size, hugepage_size);
+ shmaddr = (void *)mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0);
-#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);
+ if ((long)shmaddr == -1) {
+ 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(segment.shmaddr, size, MADV_HUGEPAGE);
+ /* enable transparent huge pages to reduce TLB misses (Linux only) */
+ if (!hugepage_size) {
+ 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) {
- apc_warning("apc_unmap: munmap failed");
- }
-
-#ifdef APC_MEMPROTECT
- if (segment->roaddr && munmap(segment->roaddr, segment->size) < 0) {
+ if (munmap(shmaddr, size) < 0) {
apc_warning("apc_unmap: munmap failed");
}
-#endif
-
}
#endif
diff --git a/apc_mmap.h b/apc_mmap.h
index 16ababcf..ab5aaec7 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 */
-#if APC_MMAP
-apc_segment_t apc_mmap(char *file_mask, size_t size);
-void apc_unmap(apc_segment_t* segment);
+#ifdef APC_MMAP
+void *apc_mmap(char *file_mask, size_t size, zend_long hugepage_size);
+void apc_unmap(void *shmaddr, size_t size);
#endif
#endif
diff --git a/apc_persist.c b/apc_persist.c
index 1d288fce..e322b8d2 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,9 +96,10 @@ 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));
}
-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 +110,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 +248,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) {
@@ -320,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));
@@ -368,6 +382,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 +398,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 +406,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;
@@ -435,27 +454,47 @@ 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;
}
+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, 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 +502,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,19 +517,19 @@ 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;
}
}
- 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;
}
- 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;
@@ -508,21 +547,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;
@@ -531,6 +585,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;
}
@@ -604,11 +659,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);
@@ -616,14 +671,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);
@@ -633,7 +688,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;
}
@@ -661,7 +716,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;
}
@@ -674,21 +730,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) {
diff --git a/apc_shm.c b/apc_shm.c
index 3f312810..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 */
@@ -53,55 +53,40 @@ 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;
}
-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:");
}
-#ifdef APC_MEMPROTECT
-
- if ((zend_long)(segment.roaddr = shmat(shmid, 0, SHM_RDONLY)) == -1) {
- segment.roaddr = NULL;
- }
-
-#endif
-
- 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:");
}
-
-#ifdef APC_MEMPROTECT
- if (segment->roaddr && shmdt(segment->roaddr) < 0) {
- apc_warning("apc_shm_detach: shmdt failed:");
- }
-#endif
}
/*
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_signal.c b/apc_signal.c
index 8bf58147..bb12026e 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"
@@ -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 51a3198d..1d31256e 100644
--- a/apc_sma.c
+++ b/apc_sma.c
@@ -43,34 +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_RO(sma, i) ((char*)(sma->segs[i]).roaddr)
-#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))
-
-#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
+#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 {
@@ -81,21 +70,17 @@ 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
- * module. Both assume the presence of a variable shmaddr 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))
+ * module. Both assume the presence of a variable smaheader that points to the
+ * beginning of the shared memory segment. */
+#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))
-#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
@@ -113,15 +98,34 @@ 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;
- block_t *cur, *prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
+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;
uint32_t i;
- CHECK_CANARY(prv);
+ CHECK_CANARY(cur);
- while (prv->fnext) {
- cur = BLOCKAT(prv->fnext);
+ /* First, ensure that at least realsize free bytes are available, even if they are not contiguous. */
+ if (smaheader->avail < realsize) {
+ return NULL;
+ }
+
+ while (cur->fnext) {
+ cur = BLOCKAT(cur->fnext);
CHECK_CANARY(cur);
/* Found a suitable block */
@@ -129,129 +133,99 @@ static inline block_t *find_block(sma_header_t *header, 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;
}
}
return found;
}
-/* {{{ 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)
+/* 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)
{
- 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 realsize; /* actual size of block needed, including block header */
- realsize = ALIGNWORD(size + block_size);
-
- /*
- * First, insure that the segment contains at least realsize free bytes,
- * even if they are not contiguous.
- */
- shmaddr = header;
-
- if (header->avail < realsize) {
- return SIZE_MAX;
- }
+ realsize = ALIGNWORD(size + ALIGNWORD(sizeof(block_t)));
- cur = find_block(header, realsize);
+ cur = find_block(smaheader, realsize);
if (!cur) {
/* No suitable block found */
return SIZE_MAX;
}
- if (cur->size == realsize || (cur->size > realsize && cur->size < (realsize + (MINBLOCKSIZE + fragment)))) {
- /* cur is big enough for realsize, but too small to split - unlink it */
- *(allocated) = cur->size - block_size;
- prv = BLOCKAT(cur->fprev);
- prv->fnext = cur->fnext;
- BLOCKAT(cur->fnext)->fprev = OFFSET(prv);
+ /* remove cur from the list of free blocks */
+ 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 */
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 */
oldsize = cur->size;
cur->size = realsize;
- *(allocated) = cur->size - block_size;
nxt = NEXT_SBLOCK(cur);
nxt->prev_size = 0; /* block is alloc'd */
nxt->size = oldsize - realsize; /* and fix the 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);
-#if 0
- nxt->id = -1;
-#endif
+ /* put the remaining block (nxt) back into the free list */
+ link_free_block_at_start(smaheader, nxt);
}
+ /* mark cur as allocated */
cur->fnext = 0;
- /* update the block header */
- header->avail -= cur->size;
+ /* store used space to be able to reclaim unused space during defragmentation */
+ cur->fprev = realsize;
- SET_CANARY(cur);
+ /* update the segment header */
+ smaheader->avail -= cur->size;
-#if 0
- cur->id = ++block_id;
- fprintf(stderr, "allocate(realsize=%d,size=%d,id=%d)\n", (int)(size), (int)(cur->size), cur->id);
-#endif
+ SET_CANARY(cur);
- return OFFSET(cur) + block_size;
+ return OFFSET(cur) + ALIGNWORD(sizeof(block_t));
}
-/* }}} */
-/* {{{ sma_deallocate: deallocates the block at the given offset */
-static APC_HOTSPOT size_t sma_deallocate(void* shmaddr, size_t offset)
+/* sma_deallocate: deallocates the block at the given 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 */
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);
- /* 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) {
- /* 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;
+ prv->size += cur->size;
RESET_CANARY(cur);
cur = prv;
@@ -260,37 +234,26 @@ static APC_HOTSPOT size_t sma_deallocate(void* shmaddr, 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);
-
-#if 0
- nxt->id = -1; /* assert this or set it ? */
-#endif
-
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;
}
-/* }}} */
-
-/* {{{ 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) {
- 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, zend_long hugepage_size) {
if (sma->initialized) {
return;
}
@@ -298,173 +261,103 @@ 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->size = ALIGNWORD(size > 0 ? size : SMA_DEFAULT_SEGSIZE);
-#if 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;
- }
+#ifdef APC_MMAP
+ sma->shmaddr = apc_mmap(mask, sma->size, hugepage_size);
#else
- sma->num = num > 0 ? num : DEFAULT_NUMSEG;
+ sma->shmaddr = apc_shm_attach(sma->size);
#endif
- sma->size = 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* header;
- block_t *first, *empty, *last;
- void* shmaddr;
-
-#if APC_MMAP
- sma->segs[i] = 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);
-#if PHP_WIN32
- /* TODO remove the line below after 7.1 EOL. */
- SetLastError(0);
-#endif
- sma->segs[i] = apc_shm_attach(j, sma->size);
- }
-#endif
-
- 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));
-
- 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);
-#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
- }
+ 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));
+ sma->max_alloc_size = smaheader->avail - 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++) {
-#if APC_MMAP
- apc_unmap(&sma->segs[i]);
+#ifdef APC_MMAP
+ apc_unmap(sma->shmaddr, sma->size);
#else
- apc_shm_detach(&sma->segs[i]);
+ apc_shm_detach(sma->shmaddr);
#endif
- }
-
- free(sma->segs);
}
-PHP_APCU_API void *apc_sma_malloc_ex(apc_sma_t *sma, size_t n, size_t *allocated) {
- size_t fragment = MINBLOCKSIZE;
+PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n, apc_sma_malloc_init_f init_callback) {
size_t off;
- int32_t i;
zend_bool nuked = 0;
- int32_t last = sma->last;
restart:
assert(sma->initialized);
- if (!SMA_LOCK(sma, last)) {
+ /* Prevent cache wipes caused by huge allocations that don't fit into shm */
+ if (n > sma->max_alloc_size) {
return NULL;
}
- off = sma_allocate(SMA_HDR(sma, last), n, fragment, allocated);
-
- if (off != SIZE_MAX) {
- void* p = (void *)(SMA_ADDR(sma, last) + off);
- SMA_UNLOCK(sma, last);
-#ifdef VALGRIND_MALLOCLIKE_BLOCK
- VALGRIND_MALLOCLIKE_BLOCK(p, n, 0, 0);
-#endif
- return p;
+ if (!SMA_LOCK(sma)) {
+ return NULL;
}
- SMA_UNLOCK(sma, last);
+ off = sma_allocate(SMA_HDR(sma), n);
- for (i = 0; i < sma->num; i++) {
- if (i == last) {
- continue;
- }
+ if (off != SIZE_MAX) {
+ void *p = (void *)(SMA_ADDR(sma) + off);
- if (!SMA_LOCK(sma, i)) {
- return NULL;
+ if (init_callback) {
+ /* Perform initializations that must be done before releasing the lock */
+ init_callback(p);
}
- off = sma_allocate(SMA_HDR(sma, i), n, fragment, allocated);
- if (off != SIZE_MAX) {
- void* p = (void *)(SMA_ADDR(sma, i) + off);
- sma->last = i;
- SMA_UNLOCK(sma, i);
+ SMA_UNLOCK(sma);
#ifdef VALGRIND_MALLOCLIKE_BLOCK
- VALGRIND_MALLOCLIKE_BLOCK(p, n, 0, 0);
+ VALGRIND_MALLOCLIKE_BLOCK(p, n, 0, 0);
#endif
- return p;
- }
- SMA_UNLOCK(sma, i);
+ return p;
}
+ SMA_UNLOCK(sma);
+
/* Expunge cache in hope of freeing up memory, but only once */
if (!nuked) {
- sma->expunge(*sma->data, n+fragment);
- nuked = 1;
+ /* nuke is not set if expunge() was skipped internally to get another try */
+ nuked = sma->expunge(*sma->data, n);
goto restart;
}
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;
if (p == NULL) {
@@ -473,178 +366,177 @@ 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
}
-#ifdef APC_MEMPROTECT
-PHP_APCU_API void* apc_sma_protect(apc_sma_t* sma, void* p) {
- unsigned int i = 0;
- size_t offset;
+PHP_APCU_API apc_sma_info_t *apc_sma_info(apc_sma_t* sma, zend_bool limited) {
- if (p == NULL) {
+ if (!sma->initialized) {
return NULL;
}
- if(SMA_RO(sma, sma->last) == NULL) return p;
-
- offset = (size_t)((char *)p - SMA_ADDR(sma, sma->last));
+ 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 = NULL;
- if(p >= (void*)SMA_ADDR(sma, sma->last) && offset < sma->size) {
- return SMA_RO(sma, sma->last) + offset;
+ if (limited) {
+ return info;
}
- 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;
- }
+ if (!SMA_LOCK(sma)) {
+ efree(info);
+ return NULL;
}
- 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;
-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;
- }
+ /* Skip 1st (0-sized) block */
+ cur = BLOCKAT(cur->fnext);
- if(SMA_RO(sma, sma->last) == NULL) return p;
+ /* For each free block */
+ while (cur->fnext != 0) {
+ CHECK_CANARY(cur);
- offset = (size_t)((char *)p - SMA_RO(sma, sma->last));
+ *link = emalloc(sizeof(apc_sma_link_t));
+ (*link)->size = cur->size;
+ (*link)->offset = OFFSET(cur);
+ (*link)->next = NULL;
+ link = &(*link)->next;
- if(p >= (void*)SMA_RO(sma, sma->last) && offset < sma->size) {
- return SMA_ADDR(sma, sma->last) + offset;
+ cur = BLOCKAT(cur->fnext);
}
+ SMA_UNLOCK(sma);
- 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 info;
+}
+
+PHP_APCU_API void apc_sma_free_info(apc_sma_t *sma, apc_sma_info_t *info) {
+ apc_sma_link_t *p = info->list;
+
+ while (p) {
+ apc_sma_link_t *q = p;
+ p = p->next;
+ efree(q);
}
- return NULL;
+ efree(info);
}
-#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;
- int32_t i;
- char *shmaddr;
- block_t *prv;
+PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma) {
+ return SMA_HDR(sma)->avail;
+}
- if (!sma->initialized) {
- return NULL;
- }
+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)));
+}
- info = emalloc(sizeof(apc_sma_info_t));
- info->num_seg = sma->num;
- info->seg_size = sma->size - (ALIGNWORD(sizeof(sma_header_t)) + ALIGNWORD(sizeof(block_t)) + 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);
- info->list = emalloc(info->num_seg * sizeof(apc_sma_link_t *));
- for (i = 0; i < sma->num; i++) {
- info->list[i] = NULL;
+ /* If total size of available memory is too small, we can skip the contiguous-block check */
+ if (smaheader->avail < realsize) {
+ return 0;
}
- if (limited) {
- return info;
+ if (!SMA_LOCK(sma)) {
+ return 0;
}
- /* For each segment */
- for (i = 0; i < sma->num; i++) {
- SMA_LOCK(sma, i);
- shmaddr = SMA_ADDR(sma, i);
- prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
+ block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
- link = &info->list[i];
+ /* Look for a contiguous block of memory */
+ while (cur->fnext) {
+ cur = BLOCKAT(cur->fnext);
- /* For each block in this segment */
- while (BLOCKAT(prv->fnext)->fnext != 0) {
- block_t *cur = BLOCKAT(prv->fnext);
+ if (cur->size >= realsize) {
+ SMA_UNLOCK(sma);
+ return 1;
+ }
+ }
- CHECK_CANARY(cur);
+ SMA_UNLOCK(sma);
- *link = emalloc(sizeof(apc_sma_link_t));
- (*link)->size = cur->size;
- (*link)->offset = prv->fnext;
- (*link)->next = NULL;
- link = &(*link)->next;
+ return 0;
+}
- prv = cur;
- }
- SMA_UNLOCK(sma, i);
+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;
}
- return info;
-}
+ /* empty the free list */
+ first->fnext = sma->size - ALIGNWORD(sizeof(block_t));
+ BLOCKAT(first->fnext)->fprev = OFFSET(first);
-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);
+ /* loop through all blocks */
+ while (cur->size != 0) {
+ /* continue until cur points to a free block */
+ if (!cur->fnext) {
+ cur = NEXT_SBLOCK(cur);
+ continue;
}
- }
- 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;
+ /* if cur is free, nxt must be an allocated block, since we never have two consecutive free blocks */
+ block_t *nxt = NEXT_SBLOCK(cur);
- for (i = 0; i < sma->num; i++) {
- sma_header_t* header = SMA_HDR(sma, i);
- avail_mem += header->avail;
- }
- return avail_mem;
-}
+ /* 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 */
+ link_free_block_at_start(smaheader, cur);
-PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) {
- int32_t i;
+ cur->prev_size = 0;
+ nxt->prev_size = cur->size;
- for (i = 0; i < sma->num; i++) {
- sma_header_t* header = SMA_HDR(sma, i);
- if (header->avail > size) {
- return 1;
+ cur = NEXT_SBLOCK(nxt);
+ 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 */
+ 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;
}
}
- return 0;
-}
-PHP_APCU_API void apc_sma_check_integrity(apc_sma_t* sma)
-{
- /* dummy */
-}
+ smaheader->avail += reclaimed_size;
-/* }}} */
+ SMA_UNLOCK(sma);
+}
/*
* Local variables:
diff --git a/apc_sma.h b/apc_sma.h
index b4891f5e..6bd35a65 100644
--- a/apc_sma.h
+++ b/apc_sma.h
@@ -28,43 +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_segment_t */
-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 */
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 */
};
-/* }}} */
-/* {{{ 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 */
};
-/* }}} */
-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 {
zend_bool initialized; /* flag to indicate this sma has been initialized */
@@ -73,22 +59,19 @@ 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 */
-
- /* segments */
- apc_segment_t *segs; /* segments */
-} apc_sma_t; /* }}} */
+ size_t max_alloc_size; /* max size of memory available for allocation */
+ 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, 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.
@@ -96,32 +79,18 @@ 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);
-
-/*
- * 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);
+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)
*/
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
*/
@@ -138,20 +107,29 @@ 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_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_api_check_integrity will check the integrity of sma
+* apc_sma_check_avail_contiguous returns true if at least size contiguous bytes can be allocated from the sma
*/
-PHP_APCU_API void apc_sma_check_integrity(apc_sma_t* sma); /* }}} */
+PHP_APCU_API zend_bool apc_sma_check_avail_contiguous(apc_sma_t *sma, size_t size);
-/* {{{ 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))
-/* }}} */
+/*
+* 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);
-#endif
+/* 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/appveyor.yml b/appveyor.yml
index 5d414d8b..cd69f7ec 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -7,111 +7,7 @@ branches:
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
+build: off
test_script:
- ps: .appveyor\test.ps1
+ - echo Dummy
diff --git a/composer.json b/composer.json
new file mode 100644
index 00000000..c9e8e80d
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,46 @@
+{
+ "name": "apcu/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!!)"
+ }
+ ]
+ }
+}
diff --git a/config.m4 b/config.m4
index a6e5c991..5e497853 100644
--- a/config.m4
+++ b/config.m4
@@ -1,90 +1,87 @@
-PHP_ARG_ENABLE(apcu, whether to enable APCu support,
-[ --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],
-[
- PHP_APCU_RWLOCKS=$enableval
- AC_MSG_RESULT($enableval)
-],
-[
- PHP_APCU_RWLOCKS=yes
- AC_MSG_RESULT(yes)
-])
-
-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_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_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,
-[ --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)
-
-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],
-[ if test "x$enableval" = "xno"; then
- PHP_APCU_SPINLOCK=no
- else
- PHP_APCU_SPINLOCK=yes
- fi
-])
-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, [
+PHP_ARG_ENABLE([apcu],
+ [whether to enable APCu support],
+ [AS_HELP_STRING([--enable-apcu],
+ [Enable APCu support])])
+
+PHP_ARG_ENABLE([apcu-rwlocks],
+ [if APCu should be allowed to use rwlocks],
+ [AS_HELP_STRING([--disable-apcu-rwlocks],
+ [Disable rwlocks in APCu])],
+ [yes],
+ [no])
+
+PHP_ARG_ENABLE([apcu-debug],
+ [if APCu should be built in debug mode],
+ [AS_HELP_STRING([--enable-apcu-debug],
+ [Enable APCu debugging])],
+ [no],
+ [no])
+
+PHP_ARG_ENABLE([apcu-clear-signal],
+ [if APCu should clear on SIGUSR1],
+ [AS_HELP_STRING([--enable-apcu-clear-signal],
+ [Enable SIGUSR1 clearing handler])],
+ [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])],
+ [yes],
+ [no])
+
+PHP_ARG_ENABLE([apcu-spinlocks],
+ [if APCu should utilize spinlocks before flocks],
+ [AS_HELP_STRING([--enable-apcu-spinlocks],
+ [Use spinlocks before flocks])],
+ [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])
- AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[
+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],
+ [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
-
- 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"
@@ -190,47 +187,21 @@ 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
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,
- [ --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
+ for i in -Wall -Wextra -Wno-clobbered -Wno-unused-parameter; do
AX_CHECK_COMPILE_FLAG([$i], [APCU_CFLAGS="$APCU_CFLAGS $i"])
done
@@ -245,18 +216,19 @@ 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)])
- PHP_NEW_EXTENSION(apcu, $apc_sources, $ext_shared,, \\$(APCU_CFLAGS))
+ 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(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, [ ])
fi
-PHP_ARG_ENABLE(coverage, whether to include code coverage symbols,
-[ --enable-coverage DEVELOPERS ONLY!!], no, no)
-
if test "$PHP_COVERAGE" = "yes"; then
if test "$GCC" != "yes"; then
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");
diff --git a/package.xml b/package.xml
index 358786c7..692dafeb 100644
--- a/package.xml
+++ b/package.xml
@@ -28,9 +28,9 @@
nikic@php.net
yes
- 2022-09-19
+ 2025-12-07
- 5.1.23dev
+ 5.1.28
5.1.18
@@ -39,11 +39,13 @@
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+.
+ - 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.
+ - Fix build against PHP 8.6.
+ - Fix apc.php compatibility with older apcu versions.
@@ -79,6 +81,7 @@
+
@@ -87,17 +90,21 @@
+
+
-
+
+
+
+
+
+
-
-
-
@@ -189,6 +196,128 @@
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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.c b/php_apc.c
index 081b62a5..392b05ac 100644
--- a/php_apc.c
+++ b/php_apc.c
@@ -58,11 +58,10 @@
#include "SAPI.h"
#include "php_apc.h"
-#if HAVE_SIGACTION
+#ifdef HAVE_SIGACTION
#include "apc_signal.h"
#endif
-/* {{{ ZEND_DECLARE_MODULE_GLOBALS(apcu) */
ZEND_DECLARE_MODULE_GLOBALS(apcu)
/* True globals */
@@ -87,26 +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(OnUpdateShmSegments) /* {{{ */
-{
- zend_long shm_segments = ZEND_STRTOL(new_value->val, NULL, 10);
-#if 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) /* {{{ */
+static PHP_INI_MH(OnUpdateShmSize)
{
#if PHP_VERSION_ID >= 80200
zend_long s = zend_ini_parse_quantity_warn(new_value, entry->name);
@@ -129,18 +112,43 @@ static PHP_INI_MH(OnUpdateShmSize) /* {{{ */
return SUCCESS;
}
-/* }}} */
+
+#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_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.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)
-#if APC_MMAP
-STD_PHP_INI_ENTRY("apc.mmap_file_mask", NULL, PHP_INI_SYSTEM, OnUpdateString, mmap_file_mask, 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_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)
@@ -150,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();
@@ -168,7 +173,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
@@ -204,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)
@@ -235,10 +238,12 @@ static PHP_MINIT_FUNCTION(apcu)
if (APCG(enabled)) {
if (!APCG(initialized)) {
-#if 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 */
@@ -247,7 +252,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_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);
@@ -277,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);
@@ -300,7 +303,7 @@ static PHP_MSHUTDOWN_FUNCTION(apcu)
APCG(initialized) = 0;
}
-#if HAVE_SIGACTION
+#ifdef HAVE_SIGACTION
apc_shutdown_signals();
#endif
}
@@ -309,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)
@@ -325,15 +327,14 @@ static PHP_RINIT_FUNCTION(apcu)
apc_cache_serializer(apc_user_cache, APCG(serializer_name));
}
-#if HAVE_SIGACTION
+#ifdef HAVE_SIGACTION
apc_set_signals();
#endif
}
return SUCCESS;
}
-/* }}} */
-/* {{{ proto void apcu_clear_cache() */
+/* proto void apcu_clear_cache() */
PHP_FUNCTION(apcu_clear_cache)
{
if (zend_parse_parameters_none() == FAILURE) {
@@ -343,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;
@@ -360,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;
@@ -372,14 +371,11 @@ 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)
{
- apc_sma_info_t* info;
- zval block_lists;
- int i;
zend_bool limited = 0;
ZEND_PARSE_PARAMETERS_START(0, 1)
@@ -387,15 +383,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,30 +400,29 @@ 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);
}
-/* }}} */
-/* {{{ 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)
@@ -439,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;
@@ -498,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;
@@ -537,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;
@@ -567,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;
@@ -600,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];
@@ -630,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;
@@ -685,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;
@@ -728,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;
@@ -780,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. */
@@ -805,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,
@@ -819,7 +787,6 @@ zend_module_entry apcu_module_entry = {
PHP_APCU_VERSION,
STANDARD_MODULE_PROPERTIES
};
-/* }}} */
#ifdef COMPILE_DL_APCU
ZEND_GET_MODULE(apcu)
@@ -827,7 +794,6 @@ ZEND_GET_MODULE(apcu)
ZEND_TSRMLS_CACHE_DEFINE();
#endif
#endif
-/* }}} */
/*
* Local variables:
diff --git a/php_apc.h b/php_apc.h
index 4456f3f9..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.23-dev"
+#define PHP_APCU_VERSION "5.1.28"
#define PHP_APCU_EXTNAME "apcu"
PHP_APCU_API zend_bool apc_is_enabled(void);
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/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--
--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 a4537eaa..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
@@ -14,22 +14,27 @@ apc.shm_size=1M
--FILE--
= $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;
-while (apcu_exists("dummy")) {
- apcu_store("key" . $i, str_repeat('x', 500));
- $i++;
-}
+// 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_026.phpt b/tests/apc_026.phpt
index 0fcfe9da..947f016e 100644
--- a/tests/apc_026.phpt
+++ b/tests/apc_026.phpt
@@ -1,60 +1,21 @@
--TEST--
-apcu_inc/dec() should not inc/dec soft expired entries based on global TTL setting
---SKIPIF--
-
+Huge allocations which don't fit into shm shouldn't cause cache wipes
--INI--
apc.enabled=1
apc.enable_cli=1
-apc.use_request_time=1
-apc.ttl=2
+apc.shm_size=1M
--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)
+bool(false)
+bool(true)
diff --git a/tests/apc_defrag.phpt b/tests/apc_defrag.phpt
new file mode 100644
index 00000000..533772b5
--- /dev/null
+++ b/tests/apc_defrag.phpt
@@ -0,0 +1,102 @@
+--TEST--
+Test defragmentation
+--SKIPIF--
+
+--INI--
+apc.enabled=1
+apc.enable_cli=1
+apc.use_request_time=1
+apc.shm_size=1M
+--FILE--
+= $entry_size) {
+ $i++;
+ apcu_store(sprintf("ttl3_%010d", $i), $i, 3);
+
+ if (apcu_sma_info(true)['avail_mem'] >= $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
+$reference = "b";
+apcu_store("ttl3_int", 123456789, 3);
+apcu_store("ttl3_string", "abc", 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
+$avail_before_filled = apcu_sma_info(true)['avail_mem'];
+
+// fill cache with alternating ttl=1 + ttl=3 entries
+fill_cache();
+
+// 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, "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
+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--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
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
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--
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
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)
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)
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--
---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
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 @@
-
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--
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--