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. -[![Build Status](https://travis-ci.org/krakjoe/apcu.svg?branch=master)](https://travis-ci.org/krakjoe/apcu) -[![Build status](https://ci.appveyor.com/api/projects/status/om63glh4g24gi1p9/branch/master?svg=true)](https://ci.appveyor.com/project/krakjoe/apcu/branch/master) +[![Build status: GitHub](https://github.com/krakjoe/apcu/actions/workflows/config.yml/badge.svg)](https://github.com/krakjoe/apcu/actions/workflows/config.yml) +[![Build status: AppVeyor](https://ci.appveyor.com/api/projects/status/om63glh4g24gi1p9/branch/master?svg=true)](https://ci.appveyor.com/project/krakjoe/apcu/branch/master) Documentation ============ 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) '', '', '', - '  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--