diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml
index 065d8018..aa0999f4 100644
--- a/.github/workflows/config.yml
+++ b/.github/workflows/config.yml
@@ -4,9 +4,11 @@ jobs:
ubuntu:
strategy:
matrix:
- version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"]
+ version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]
runs-on: ubuntu-latest
steps:
+ - name: Set the hugepages parameter
+ run: sudo sh -c "echo 1 > /proc/sys/vm/nr_hugepages"
- name: Checkout apcu
uses: actions/checkout@v4
- name: Setup PHP
@@ -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,28 +35,16 @@ jobs:
shell: cmd
strategy:
matrix:
- version: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"]
+ version: ["8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]
arch: [x64]
ts: [nts, ts]
- os: [windows-2019, windows-2022]
- exclude:
- - { os: windows-2019, version: "8.4" }
- - { os: windows-2019, version: "8.3" }
- - { os: windows-2019, version: "8.2" }
- - { os: windows-2019, version: "8.1" }
- - { os: windows-2019, version: "8.0" }
- - { os: windows-2022, version: "7.4" }
- - { os: windows-2022, version: "7.3" }
- - { os: windows-2022, version: "7.2" }
- - { os: windows-2022, version: "7.1" }
- - { os: windows-2022, version: "7.0" }
- runs-on: ${{matrix.os}}
+ runs-on: windows-2022
steps:
- name: Checkout apcu
uses: actions/checkout@v4
- name: Setup PHP
id: setup-php
- uses: php/setup-php-sdk@v0.10
+ uses: php/setup-php-sdk@v0.11
with:
version: ${{matrix.version}}
arch: ${{matrix.arch}}
diff --git a/TECHNOTES.txt b/TECHNOTES.txt
index e7cf642b..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,303 +24,205 @@ form of a quick-start guide to start hacking on APCu.
4. Debugging Hints
- apachectl stop
- gdb /usr/bin/httpd
- break ??
- run -X
+ apachectl stop
+ gdb /usr/bin/httpd
+ break ??
+ run -X
Grab the .gdbinit from the PHP source tree and have a look at the macros.
5. The basics of APCu
- APCu has three main component parts:
- 1) shared memory allocator
- 2) pooling
- 3) user land cache
+ The caching functionality of APCu is provided by a modified version of the APC source code.
+ Many tweaks have been applied. There's probably some of my blood in it, if you look real close... (krakjoe)
-5.1) APCu SMA
-
- It is a pretty standard memory allocator, now supporting third party extensions.
+ APCu has the following main component parts:
+ 1) shared memory allocator / SMA (apc_sma.c)
+ 2) user land cache (apc_cache.c)
+ 3) persistence representation (apc_persist.c)
- apc_sma_malloc and apc_sma_free behave to the caller just like malloc and free,
- they are generated from macros in apc_sma.h
+5.1 SMA
- Note: apc_sma.h is formatted and designed such that the SMA APCu
- uses can be used by third parties in their own extensions without
- interfering with, or consuming the resources of APCu itself
+ The SMA (shared memory allocator) is a pretty standard memory allocator. It provides
+ apc_sma_malloc() and apc_sma_free() which behave just like malloc() and free().
- apc_sma is a structure of type apc_sma_t, it is statically allocated at runtime,
- appropriate handlers are generated and set, and the structure made ready for initialization.
+ The SMA is designed such that the SMA can be used by third parties in their own extensions
+ without interfering with or consuming the resources of APCu itself.
- MINIT then initializes apc_sma with apc_sma_api_init().
- APCu SMA then takes care of mmaping the shared memory.
- ( which you can obtain in any compilation unit with apc_sma_api_extern(apc_sma) )
+ MINIT calls apc_sma_init(), which takes care of mapping and initializing the shared memory segment.
+ It initializes the smaheader (sma_header_t) at the beginning of the shared memory. The smaheader
+ serves as a place to store, among other things, statistical information and the lock for the SMA.
- At this point, we have a completely useless 32MB chunk of memory at our disposal, before
- it can be used, a sma_header_t is initialized at the beginning of the region of
- mmapp'ed memory.
-
- The smaheader serves as a place to store, among other things, statistical information and a lock.
-
- Immediately after the smaheader come three blocks. The first and last block are 0-sized
- and simplify the handling of the linked list. The block between the 0-sized blocks
- contains the remaining size of the shared memory which is available for allocation.
+ Immediately after the smaheader it initializes three blocks. The first and last block are 0-sized
+ and simplify the handling of the linked list of free blocks and sequential traversal of blocks.
+ The block between the 0-sized blocks contains the remaining amount of shared memory available
+ for allocation.
- At this point, the shared memory looks like this:
+ At this point, the shared memory looks like this:
- +--------+--------+-----------------------------------+
- | header | 0-size | free-block | 0-size |
- +--------+--------+-----------------------------------+
+ +-----------+--------+-----------------------------------+
+ | smaheader | 0-size | free-block | 0-size |
+ +-----------+--------+-----------------------------------+
- The blocks are just a simple offset-based doubly linked list (so no pointers):
+ These three blocks (type block_t) form the initial doubly linked list of free blocks.
+ Since the whole SMA is implemented relocatable (independent of the starting address
+ of the shared memory segment), this list is offset-based (so no pointers).
- typedef struct block_t block_t;
- struct block_t {
- size_t size; /* size of this block */
- size_t prev_size; /* size of sequentially previous block, 0 if prev is allocated */
- size_t fnext; /* offset in segment of next free block */
- size_t fprev; /* offset in segment of prev free block */
-#ifdef APC_SMA_CANARIES
- size_t canary; /* canary to check for memory overwrites */
-#endif
- };
+ The macros BLOCKAT and OFFSET are used to simplify the handling of the offset-based blocks:
- The BLOCKAT macro turns an offset into an actual address for you:
+ - The BLOCKAT macro turns an offset into an actual process-local address/pointer:
#define BLOCKAT(offset) ((block_t*)((char *)smaheader + offset))
- where smaheader = sma->shaddrs[0]
-
- And the OFFSET macro goes the other way:
+ - The OFFSET macro goes the other way:
#define OFFSET(block) ((int)(((char*)block) - (char*)smaheader))
- Allocating a block (`sma_allocate`) walks through the doubly linked list of blocks until it
- finds one that is >= to the requested size. The first call to allocate will hit the second block.
- We then chop up that block so it looks like this:
+ Both macros assume the presence of the variable "smaheader" that points to the beginning
+ of the shared memory segment.
+
+ To allocate a block via apc_sma_malloc(), we walk through the doubly linked list of blocks until we
+ find one that is >= to the requested size (see find_block()). The first call to find_block() will hit
+ the second block. To get a block of the requested size, we then chop up that block so it looks like this:
- +--------+--------+-------+------------------+--------+
- | header | 0-size | block | free-block | 0-size |
- +--------+--------+-------+------------------+--------+
+ +-----------+--------+-------+------------------+--------+
+ | smaheader | 0-size | block | free-block | 0-size |
+ +-----------+--------+-------+------------------+--------+
Then we unlink that block from the doubly linked list so it won't show up
- as an available block on the next allocate. So we actually have:
+ as an available block on the next allocation. So we actually have:
- +--------+--------+ +------------------+--------+
- | header | 0-size |<----->| free-block | 0-size |
- +--------+--------+ +------------------+--------+
+ +-----------+--------+ +------------------+--------+
+ | smaheader | 0-size |<----->| free-block | 0-size |
+ +-----------+--------+ +------------------+--------+
And smaheader->avail along with block->size of the remaining large
free block are updated accordingly. The arrow there represents the
link which now points to a block with an offset further along in
the segment.
- When the block is freed the steps are basically just reversed.
+ When the block is freed, the steps are basically just reversed.
The block is put back and then the deallocate code looks at the block before and after to see
- if the block immediately before and after are free and if so the blocks are combined. So you never
- have 2 free blocks next to each other, apart from the 0-sized dummy blocks.
+ if the blocks immediately before or after are free, and if so, the blocks are combined.
+ So we never have 2 free blocks next to each other, apart from the 0-sized dummy blocks.
This mostly prevents fragmentation.
Block start pointers are aligned to the system's word boundary (usually 8 bytes) with the `ALIGNWORD` macro.
-5.2) APCu Cache
-
- The caching functionality of APCu is provided by a modified version of the APC source code
-
- Some simple tweaks have been applied:
- Locking is written to use the best kind of locking available, and emulate it where it is not to simplify logic.
- Extension of the SMA to support multiple instances, such that additional caches using APCu do not
- increase contention of the main APCu cache.
- The possibility to control more finely what happens when resources become low for APCu.
- An exposed, coherent, and documented API and example included in the distribution.
-
- There's probably some of my blood in it, if you look real close ...
-
- The remainder of the document goes on to explain in some detail the cache itself, functionally unchanged by APCu
-
-6. Next up is apc_cache.c which implements the cache logic.
-
- Having initialized a suitable allocator, MINIT must call apc_cache_create, using the allocator provided
- APCu will create a cache. The parameters to apc_cache_create for APCu are defined by various INI settings.
- API users can provide the same options from anywhere ( their globals for example ).
-
- The cache is stored in/described by this struct allocated locally:
-
- /* {{{ struct definition: apc_cache_t */
- typedef struct _apc_cache_t {
- apc_cache_header_t* header; /* cache header (stored in SHM) */
- uintptr_t* slots; /* array of cache slots (stored in SHM) */
- apc_sma_t* sma; /* shared memory allocator */
- apc_serializer_t* serializer; /* serializer */
- size_t nslots; /* number of slots in cache */
- zend_long gc_ttl; /* maximum time on GC list for a entry */
- zend_long ttl; /* if slot is needed and entry's access time is older than this ttl, remove it */
- zend_long smart; /* smart parameter for gc */
- zend_bool defend; /* defense parameter for runtime */
- } apc_cache_t; /* }}} */
-
- Whenever you see functions that take a 'cache' argument, this is what they
- take.
-
- At the beginning of the cache we have a header. The header looks like this:
-
- /* {{{ struct definition: apc_cache_header_t
- Any values that must be shared among processes should go in here. */
- typedef struct _apc_cache_header_t {
- apc_lock_t lock; /* header lock */
- zend_long nhits; /* hit count */
- zend_long nmisses; /* miss count */
- zend_long ninserts; /* insert count */
- zend_long nexpunges; /* expunge count */
- zend_long nentries; /* entry count */
- zend_long mem_size; /* used */
- time_t stime; /* start time */
- apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */
- uintptr_t gc; /* offset in shm to the first entry of gc list */
- } apc_cache_header_t; /* }}} */
-
- Since this is at the start of the shared memory segment, these values are accessible
- across all processes / threads and hence access to them has to be locked.
-
- After the header we have an array of slots. The number of slots is user-defined
- through the apc.entries_hint ini hint. Each slot is described by:
-
- /* {{{ struct definition: apc_cache_slot_t */
- typedef struct apc_cache_slot_t apc_cache_slot_t;
- struct apc_cache_slot_t {
- apc_cache_key_t key; /* slot key */
- apc_cache_entry_t* value; /* slot value */
- apc_cache_slot_t* next; /* next slot in linked list */
- zend_ulong nhits; /* number of hits to this slot */
- time_t ctime; /* time slot was initialized */
- time_t dtime; /* time slot was removed from cache */
- time_t atime; /* time slot was last accessed */
- };
- /* }}} */
-
- The apc_cache_slot_t *next there is a linked list to other slots that happened to hash to the
- same array position.
-
- apc_cache_store_internal() shows what happens on a new cache insert.
+5.2 Cache
- /* calculate hash and entry */
- apc_cache_hash_slot(cache, key, &h, &s);
+ Next up is apc_cache.c which implements the cache logic.
+
+ Having initialized the shared memory allocator (SMA), MINIT calls apc_cache_create() to initialize
+ the cache. The parameters to apc_cache_create() for APCu are mostly defined by various INI settings.
+
+ The function apc_cache_create() allocates and returns a struct of the type apc_cache_t, which describes
+ the created cache. Whenever you see functions that take a 'cache' argument, this is what they take.
+
+ In addition, apc_cache_create() allocates and initializes shared memory for the cache header
+ (apc_cache_header_t) and the hash slots of the hash table. Since the header and the hash slots are
+ in the shared memory, these values are accessible across all processes / threads and hence access
+ to it has to be locked. For this purpose, the header contains a read / write lock that allows access
+ to either multiple parallel readers or only one writer.
+
+ The number of hash slots is computed / user-defined through the apc.entries_hint ini value.
+ Therefore, the size of the hash slot array in shared memory can vary.
+
+ Each hash slot consists of a doubly linked list of entries. Because the cache layer is implemented
+ relocatable (independent of the shared memory's starting address), these doubly linked lists
+ use offsets relative to the starting address of the cache header instead of pointers. For this reason,
+ the array of hash slots is just an array of uintptr_t values, each containing an offset to the
+ first entry of the doubly linked list which belongs to this slot (a value of 0 means the slot is empty).
+
+ The macros ENTRYAT and ENTRYOF are used to convert between offsets and pointers to cache entries.
+ Both expect the presence of cache->header that points to the cache header in the shared memory segment:
- entry = &cache->slots[s];
+ #define ENTRYAT(offset) ((apc_cache_entry_t *)((uintptr_t)cache->header + (uintptr_t)offset))
+ #define ENTRYOF(entry) (((uintptr_t)entry) - (uintptr_t)cache->header)
- cache->slots is our array of slots in the segment.
+ In Addition, the functions apc_cache_wlocked_link_entry() and apc_cache_wlocked_unlink_entry()
+ help inserting or removing entries into or from doubly linked lists. Using them, you don't have to
+ worry about offsets when adding or removing entries from lists.
- So, on an insert we find the array position in the slots array by hashing the key provided.
- If there are currently no other slots there, we just stick the created `apc_cache_entry_t` into the array.
+ apc_cache_wlocked_insert() shows what happens when a cache entry is inserted:
- while (*entry) {
- /* process expired entries and check for entry with matching key */
+ /* calculate hash and entry */
+ apc_cache_hash_slot(cache, key, &h, &s);
+
+ uintptr_t *entry_offset = &cache->slots[s];
+
+ while (*entry_offset) {
+ apc_cache_entry_t *entry = ENTRYAT(*entry_offset);
+ /* process expired entries and check for entry with matching key */
}
+
/* link in new entry */
- new_entry->next = *entry;
- *entry = new_entry;
-
- If there are other slots already at this position we walk the link list to get to
- the end.
-
- While walking the linked list we also check to see if the cache has a TTL defined.
- If while walking the linked list we see a slot that has expired, we remove it
- since we are right there looking at it. This is the only place we remove stale
- entries unless the shared memory segment fills up and we force a full expunge via
- apc_cache_expunge(). apc_cache_expunge() walks all slots attempting deletion, how
- deletion occurs depends on runtime parameters, see INSTALL for runtime parameter
- configuration details.
-
- apc_cache_rlocked_find() simply hashes and returns the entry if it is there. If it is there
- but older than the mtime in the entry we are looking for, we return indicating we didn't find it.
-
- API users are advised to use apc_cache_fetch over find for simplicity, this ensures
- correct operation, fetch sets up the call to find and takes care of copying and releasing
- the entry from the cache to a zval* provided.
-
- Next we need to understand what an actual cache entry looks like. Have a look at
- apc_cache.h for the structs. Here is the definition of apc_cache_key_t:
-
- /* {{{ struct definition: apc_cache_key_t */
- typedef struct _apc_cache_key_t {
- const char *str; /* pointer to constant string key */
- zend_uint len; /* length of data at str */
- zend_ulong h; /* pre-computed hash of key */
- time_t mtime; /* the mtime of this cached entry */
- apc_cache_owner_t owner; /* the context that created this key */
- } apc_cache_key_t; /* }}} */
-
- To create a apc_cache_key_t structure, call apc_cache_make_key(), see apc_cache.h
-
- Ok, on to the actual cache entry, here is the definition of apc_cache_entry_t:
-
- /* {{{ struct definition: apc_cache_entry_t */
- typedef struct apc_cache_entry_t apc_cache_entry_t;
- struct apc_cache_entry_t {
- zval val; /* the zval copied at store time */
- uintptr_t next; /* offset in shm of next entry in linked list */
- zend_long ttl; /* the ttl on this specific entry */
- zend_long ref_count; /* the reference count of this entry */
- zend_long nhits; /* number of hits to this entry */
- 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 */
- zend_string key; /* entry key (MUST BE THE LAST FIELD OF THE STRUCT!) */
- };
- /* }}} */
-
- To create an apc_cache_entry_t, call apc_cache_make_entry(), see apc_cache.h
-
- Any of the structures taken by apc_cache_* functions have their equivalent apc_cache_make_*
-
- If an insertion of an entry should fail, it falls to the caller of insert to free
- the pooled resources used to create the entry.
-
-7. Serializers
-
- The way data is serialized and unserialized can be found in apc_persist.c.
- Saving to shared memory (persist) is done using `apc_persist_context_t`.
- Both the entry key and the entry's zval get persisted into shared memory in a continuous block
- for the single entry.
-
- typedef struct _apc_persist_context_t {
- /* Serializer to use */
- apc_serializer_t *serializer;
- /* Computed size of the needed SMA allocation */
- size_t size;
- /* Whether or not we may have to memoize refcounted addresses */
- zend_bool memoization_needed;
- /* Whether to serialize the top-level value */
- zend_bool use_serialization;
- /* Serialized object/array string, in case there can only be one */
- unsigned char *serialized_str;
- size_t serialized_str_len;
- /* Whole SMA allocation */
- char *alloc;
- /* Current position in allocation */
- char *alloc_cur;
- /* HashTable storing refcounteds for which the size has already been counted. */
- HashTable already_counted;
- /* HashTable storing already allocated refcounteds. Pointers to refcounteds are stored. */
- HashTable already_allocated;
- } apc_persist_context_t;
+ apc_cache_wlocked_link_entry(cache, entry_offset, new_entry);
+
+ During insertion, we get the array position in the hash slot array (cache->slots) by hashing
+ the key. If there are currently no other entries in the slot, we just link the new entry
+ (apc_cache_entry_t) into the slot, which results in a doubly linked list with one entry.
+
+ If there are other entries in the slot, we walk to the end of the linked list. As we traverse
+ the linked list, we also remove expired entries since we are right there looking at them and
+ we already have a write-lock. This is the only place where we remove stale entries unless the
+ shared memory segment is full and we try to cleanup the cache via apc_cache_default_expunge().
+
+ apc_cache_rlocked_find() simply tries to return an entry by searching for the key (hash lookup +
+ traversing the linked list). If an entry with the same key exists but its TTL has expired,
+ we return that it was not found.
+
+ Developers are encouraged to use apc_cache_fetch() instead of find for simplicity. This ensures
+ correct operation, because fetch sets up the read lock, the call to find, and takes care of
+ copying and releasing the entry from the cache to a provided zval*.
+
+ The function apc_cache_default_expunge() removes all expired entries and defragments the shared
+ memory to get as much free contiguous memory as possible. If the removal of expired entries and
+ defragmentation doesn't free enough contiguous memory, a full cache wipe is performed
+ via apc_cache_wlocked_real_expunge().
+
+5.3 Persistence
+
+ Before an entry can be inserted into a linked list, it must be created (persisted) in the
+ shared memory segment. This is done with apc_persist() in apc_persist.c. The following steps
+ are required to persist an entry:
+ - The amount of memory needed to store the entry is calculated by apc_persist_calc().
+ - The shared memory to persist the entry is allocated by apc_sma_malloc().
+ - The entry is persisted in the allocated shared memory by apc_persist_create_entry().
+
+ The persistence representation of an entry is implemented independent of its position
+ in the shared memory segment, which enables us to move around entries during defragmentation
+ and to make the entire cache relocatable. This is achieved by converting all entry's pointers
+ to offsets relative to the entry's starting address during persistence.
+
+ The function apc_unpersist() creates a process-local copy of the persisted entry's value (zval),
+ which can be passed to the php runtime after an entry has been found. During apc_unpersist(),
+ all offsets relative to the entry's starting address must be converted back to process-local
+ addresses / pointers before data access. It is important to note that this conversion must
+ occur on the stack or in process-local memory and that the shared memory representation of the
+ entry must not be modified, as this would break the persistence representation.
+
+ In some cases, the value (zval) of an entry is persisted by using a serializer. Whether a
+ serializer is used depends primarily on the data type of the value (e.g., arrays or objects).
+ For simple types as null/bool/int/float/string, serializers are unnecessary and not used.
The ini setting `apc.serializer` can be used to customize the `apc_serializer_t *serializer`.
- This affects which serializer is used for PHP objects or arrays.
- (for a top level null/bool/int/float/string, serializers are unnecessary and not used)
-
- - `apc.serializer=php` (default) will use serialize() and unserialize() for serializing arrays/
- This has lower memory usage than `apc.serializer=default` for most use cases
- - `apc.serializer=default` is used for arrays that don't contain objects, and will store the array structure in shared memory
- in a form that allows deduplicating values as well as faster unserialization of small arrays, at the cost of generally having higher memory usage.
-
- For arrays that contain objects, it falls back to php's native serialize()/unserialize()
- - APCu can be configured to use third party serializers if they are compiled with support for apcu.
- For example, `apc.serializer=igbinary` (https://github.com/igbinary/igbinary) can be used for generally faster unserialization and lower memory usage than apc.serializer=php
- (requires that igbinary be configured and compiled after APCu is installed)
+ This affects which serializer is used (Default: apc.serializer=php).
-If you made it to the end of this, you should have a pretty good idea of where things are in
-the code. There is much more reading to do in headers ... good luck ...
+ APCu can be configured to use third-party serializers if they are compiled with support for apcu.
+ For example, `apc.serializer=igbinary` (https://github.com/igbinary/igbinary) can be used for
+ generally faster unserialization and lower memory usage than apc.serializer=php
+ (requires that igbinary is configured and compiled after APCu is installed)
+
+6. Relocatable design
+
+ All layers of APCu are implemented in a relocatable manner. The goal of the relocatable design
+ is to allow independent processes to attach to the same shared memory segment in the future, even
+ if they use different starting addresses for the shared memory segment. This is achieved by using
+ offset-based addressing instead of pointers in all layers of APCu. Therefore, you will not find
+ any pointers in the entire shared memory segment. So, do not store pointers (absolute addresses)
+ in the shared memory segment, as doing so will likely break the relocatable design!
+
+If you made it to the end of this, you should have a pretty good idea of where things are in
+the code. There is much more reading to do in headers ... good luck ...
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 dddb4544..046f2286 100644
--- a/apc.php
+++ b/apc.php
@@ -766,6 +766,8 @@ function block_sort($array1, $array2)
$insert_rate_user = sprintf("%.2f", $cache['num_inserts'] ? (($cache['num_inserts'])/$elapsed) : 0);
$apcversion = phpversion('apcu');
$phpversion = phpversion();
+ $cleanups = $cache['cleanups'] ?? '-';
+ $defragmentations = $cache['defragmentations'] ?? '-';
$number_vars = $cache['num_entries'];
$size_vars = bsize($cache['mem_size']);
$num_hits_and_misses = $cache['num_hits'] + $cache['num_misses'];
@@ -804,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']} |
diff --git a/apc_cache.c b/apc_cache.c
index 48bb863d..1ff4305e 100644
--- a/apc_cache.c
+++ b/apc_cache.c
@@ -60,7 +60,7 @@ apc_cache_entry_t *apc_persist(
apc_sma_t *sma, apc_serializer_t *serializer, zend_string *key, const zval *val);
zend_bool apc_unpersist(zval *dst, const apc_cache_entry_t *entry, apc_serializer_t *serializer);
-/* {{{ make_prime */
+/* make_prime */
static int const primes[] = {
257, /* 256 */
521, /* 512 */
@@ -120,19 +120,17 @@ static int make_prime(int n)
}
return *(k-1);
}
-/* }}} */
static inline void free_entry(apc_cache_t *cache, apc_cache_entry_t *entry) {
apc_sma_free(cache->sma, entry);
}
-/* {{{ apc_cache_hash_slot
- Note: These calculations can and should be done outside of a lock */
+/* These calculations can and should be done outside of a lock */
static inline void apc_cache_hash_slot(
apc_cache_t* cache, zend_string *key, zend_ulong* hash, size_t* slot) {
*hash = ZSTR_HASH(key);
*slot = *hash % cache->nslots;
-} /* }}} */
+}
static inline zend_bool apc_entry_key_equals(const apc_cache_entry_t *entry, zend_string *key, zend_ulong hash) {
return ZSTR_H(&entry->key) == hash
@@ -140,18 +138,18 @@ static inline zend_bool apc_entry_key_equals(const apc_cache_entry_t *entry, zen
&& memcmp(ZSTR_VAL(&entry->key), ZSTR_VAL(key), ZSTR_LEN(key)) == 0;
}
-/* An entry is hard expired if the creation time if older than the per-entry TTL.
- * Hard expired entries must be treated indentially to non-existent entries. */
+/* An entry is hard expired if the creation time is older than the per-entry TTL.
+ * Hard expired entries must be treated identically to non-existent entries. */
static zend_bool apc_cache_entry_hard_expired(apc_cache_entry_t *entry, time_t t) {
return entry->ttl && (time_t) (entry->ctime + entry->ttl) < t;
}
-/* An entry is soft expired if no per-entry TTL is set, a global cache TTL is set,
+/* An entry is soft expired if a global cache TTL is set,
* and the access time of the entry is older than the global TTL. Soft expired entries
* are accessible by lookup operation, but may be removed from the cache at any time. */
static zend_bool apc_cache_entry_soft_expired(
apc_cache_t *cache, apc_cache_entry_t *entry, time_t t) {
- return !entry->ttl && cache->ttl && (time_t) (entry->atime + cache->ttl) < t;
+ return cache->ttl && (time_t) (entry->atime + cache->ttl) < t;
}
static zend_bool apc_cache_entry_expired(
@@ -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, uintptr_t *entry_offset)
-{
- apc_cache_entry_t *dead = ENTRYAT(*entry_offset);
+/* 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;
+ }
- /* unlink entry from list */
- *entry_offset = dead->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--;
/* free entry if there are no references */
- if (dead->ref_count <= 0) {
- free_entry(cache, dead);
+ if (entry->ref_count <= 0) {
+ free_entry(cache, entry);
} else {
/* add to gc if there are still refs */
- dead->dtime = time(0);
- dead->next = cache->header->gc;
- cache->header->gc = ENTRYOF(dead);
+ entry->dtime = time(0);
+ apc_cache_wlocked_link_entry(cache, &cache->header->gc, entry);
}
}
-/* }}} */
-/* {{{ 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
@@ -220,13 +251,12 @@ static void apc_cache_wlocked_gc(apc_cache_t* cache)
}
/* set next and free current entry */
- *entry_offset = entry->next;
+ apc_cache_wlocked_unlink_entry(cache, entry);
free_entry(cache, entry);
}
}
-/* }}} */
-/* {{{ php serializer */
+/* php serializer */
PHP_APCU_API int APC_SERIALIZER_NAME(php) (APC_SERIALIZER_ARGS)
{
smart_str strbuf = {0};
@@ -254,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;
@@ -276,9 +306,8 @@ PHP_APCU_API int APC_UNSERIALIZER_NAME(php) (APC_UNSERIALIZER_ARGS)
return 0;
}
return 1;
-} /* }}} */
+}
-/* {{{ apc_cache_create */
PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* serializer, zend_long size_hint, zend_long gc_ttl, zend_long ttl, zend_long smart, zend_bool defend) {
apc_cache_t* cache;
zend_long cache_size;
@@ -294,7 +323,7 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser
cache_size = sizeof(apc_cache_header_t) + nslots * sizeof(uintptr_t);
/* allocate shm */
- cache->header = apc_sma_malloc(sma, cache_size);
+ cache->header = apc_sma_malloc(sma, cache_size, NULL);
if (!cache->header) {
zend_error_noreturn(E_CORE_ERROR, "Unable to allocate " ZEND_LONG_FMT " bytes of shared memory for cache structures. Either apc.shm_size is too small or apc.entries_hint too large", cache_size);
@@ -308,6 +337,8 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser
cache->header->nhits = 0;
cache->header->nmisses = 0;
cache->header->nentries = 0;
+ cache->header->ncleanups = 0;
+ cache->header->ndefragmentations = 0;
cache->header->nexpunges = 0;
cache->header->gc = 0;
cache->header->stime = time(NULL);
@@ -326,7 +357,7 @@ PHP_APCU_API apc_cache_t* apc_cache_create(apc_sma_t* sma, apc_serializer_t* ser
CREATE_LOCK(&cache->header->lock);
return cache;
-} /* }}} */
+}
static inline zend_bool apc_cache_wlocked_insert(
apc_cache_t *cache, apc_cache_entry_t *new_entry, zend_bool exclusive) {
@@ -356,7 +387,7 @@ static inline zend_bool apc_cache_wlocked_insert(
return 0;
}
- apc_cache_wlocked_remove_entry(cache, entry_offset);
+ apc_cache_wlocked_remove_entry(cache, entry);
break;
}
@@ -365,7 +396,7 @@ static inline zend_bool apc_cache_wlocked_insert(
* entries, so we don't always have to skip past a bunch of stale entries.
*/
if (apc_cache_entry_expired(cache, entry, t)) {
- apc_cache_wlocked_remove_entry(cache, entry_offset);
+ apc_cache_wlocked_remove_entry(cache, entry);
continue;
}
@@ -374,8 +405,7 @@ static inline zend_bool apc_cache_wlocked_insert(
}
/* link in new entry */
- new_entry->next = *entry_offset;
- *entry_offset = ENTRYOF(new_entry);
+ apc_cache_wlocked_link_entry(cache, entry_offset, new_entry);
cache->header->mem_size += new_entry->mem_size;
cache->header->nentries++;
@@ -388,7 +418,7 @@ static void apc_cache_set_entry_values(apc_cache_entry_t *entry, const int32_t t
{
entry->ttl = ttl;
entry->next = 0;
- entry->ref_count = 0;
+ entry->prev = 0;
entry->nhits = 0;
entry->ctime = t;
entry->mtime = t;
@@ -421,6 +451,9 @@ static inline zend_bool apc_cache_store_internal(
return 0;
}
+ /* release entry, because the ref_count of a new entry is initialized to 1 during allocation */
+ apc_cache_entry_release(cache, entry);
+
return 1;
}
@@ -499,7 +532,6 @@ static inline apc_cache_entry_t *apc_cache_rlocked_find_incref(
return entry;
}
-/* {{{ apc_cache_store */
PHP_APCU_API zend_bool apc_cache_store(
apc_cache_t* cache, zend_string *key, const zval *val,
const int32_t ttl, const zend_bool exclusive) {
@@ -534,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;
@@ -617,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;
}
@@ -627,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
@@ -661,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,
@@ -683,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;
@@ -696,7 +726,7 @@ static void apc_cache_wlocked_real_expunge(apc_cache_t* cache) {
for (i = 0; i < cache->nslots; i++) {
uintptr_t *entry_offset = &cache->slots[i];
while (*entry_offset) {
- apc_cache_wlocked_remove_entry(cache, entry_offset);
+ apc_cache_wlocked_remove_entry(cache, ENTRYAT(*entry_offset));
}
}
@@ -711,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) {
@@ -729,33 +758,39 @@ 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 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;
}
- /* 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;
+ }
/* smart > 1 increases the probability of a full cache wipe,
* so expunge() is called less often when memory is low. */
@@ -768,7 +803,7 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size)
apc_cache_entry_t *entry = ENTRYAT(*entry_offset);
if (apc_cache_entry_expired(cache, entry, t)) {
- apc_cache_wlocked_remove_entry(cache, entry_offset);
+ apc_cache_wlocked_remove_entry(cache, entry);
continue;
}
@@ -777,20 +812,39 @@ PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size)
}
}
- /* if the cache now has space, then reset last key */
- if (apc_sma_get_avail_size(cache->sma, size)) {
- /* wipe lastkey */
- memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t));
- } else {
- /* with not enough space left in cache, we are forced to expunge */
+ /* gc */
+ apc_cache_wlocked_gc(cache);
+
+ /* if all free blocks together do not provide enough memory, we immediately perform a real expunge */
+ if (!apc_sma_check_avail(cache->sma, size)) {
apc_cache_wlocked_real_expunge(cache);
+ goto end_lbl;
}
+ /* 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;
+ }
+
+ /* wipe lastkey */
+ memset(&cache->header->lastkey, 0, sizeof(apc_cache_slam_key_t));
+
+end_lbl:
+ /* Increment cache cleanup statistics (removal of expired entries).
+ * This should be done late to detect stacking of default expunge operations. */
+ cache->header->ncleanups++;
+
apc_cache_wunlock(cache);
+ return 1;
}
-/* }}} */
-/* {{{ 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;
@@ -818,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;
@@ -833,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)
@@ -888,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)
@@ -938,9 +987,7 @@ PHP_APCU_API zend_bool apc_cache_atomic_update_long(
return 0;
}
-/* }}} */
-/* {{{ apc_cache_delete */
PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key)
{
zend_ulong h;
@@ -965,7 +1012,7 @@ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key)
/* check for a match by hash and identifier */
if (apc_entry_key_equals(entry, key, h)) {
/* executing removal */
- apc_cache_wlocked_remove_entry(cache, entry_offset);
+ apc_cache_wlocked_remove_entry(cache, entry);
apc_cache_wunlock(cache);
return 1;
@@ -977,15 +1024,12 @@ PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t *cache, zend_string *key)
apc_cache_wunlock(cache);
return 0;
}
-/* }}} */
-/* {{{ apc_cache_entry_fetch_zval */
PHP_APCU_API zend_bool apc_cache_entry_fetch_zval(
apc_cache_t *cache, apc_cache_entry_t *entry, zval *dst)
{
return apc_unpersist(dst, entry, cache->serializer);
}
-/* }}} */
static inline void array_add_long(zval *array, zend_string *key, zend_long lval) {
zval zv;
@@ -999,7 +1043,6 @@ static inline void array_add_double(zval *array, zend_string *key, double dval)
zend_hash_add_new(Z_ARRVAL_P(array), key, &zv);
}
-/* {{{ apc_cache_link_info */
static zval apc_cache_link_info(apc_cache_t *cache, apc_cache_entry_t *p)
{
zval link, zv;
@@ -1019,9 +1062,7 @@ static zval apc_cache_link_info(apc_cache_t *cache, apc_cache_entry_t *p)
return link;
}
-/* }}} */
-/* {{{ apc_cache_info */
PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool limited)
{
zval list;
@@ -1047,7 +1088,9 @@ PHP_APCU_API zend_bool apc_cache_info(zval *info, apc_cache_t *cache, zend_bool
add_assoc_double(info, "num_misses", (double) cache->header->nmisses);
add_assoc_double(info, "num_inserts", (double) cache->header->ninserts);
add_assoc_long(info, "num_entries", cache->header->nentries);
- add_assoc_double(info, "expunges", (double) cache->header->nexpunges);
+ add_assoc_long(info, "cleanups", cache->header->ncleanups);
+ add_assoc_long(info, "defragmentations", cache->header->ndefragmentations);
+ add_assoc_long(info, "expunges", cache->header->nexpunges);
add_assoc_long(info, "start_time", cache->header->stime);
array_add_double(info, apc_str_mem_size, (double) cache->header->mem_size);
@@ -1102,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;
@@ -1150,7 +1190,6 @@ PHP_APCU_API void apc_cache_stat(apc_cache_t *cache, zend_string *key, zval *sta
} php_apc_end_try();
}
-/* {{{ apc_cache_defense */
PHP_APCU_API zend_bool apc_cache_defense(apc_cache_t *cache, zend_string *key, time_t t)
{
/* only continue if slam defense is enabled */
@@ -1190,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) {
@@ -1239,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 e800854f..90aebb22 100644
--- a/apc_cache.h
+++ b/apc_cache.h
@@ -46,11 +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 {
- zval val; /* the zval copied at store time */
- uintptr_t next; /* offset in shm of next entry in linked list */
+ uintptr_t next; /* offset to next entry (MUST BE THE 1st FIELD OF THE STRUCT!) */
+ uintptr_t prev; /* offset to previous entry / head-pointer of the linked list */
zend_long ttl; /* the ttl on this specific entry */
zend_long ref_count; /* the reference count of this entry */
zend_long nhits; /* number of hits to this entry */
@@ -59,26 +58,26 @@ 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 */
apc_cache_slam_key_t lastkey; /* last key inserted (not necessarily without error) */
uintptr_t gc; /* offset in shm to the first entry of gc list */
-} apc_cache_header_t; /* }}} */
+} apc_cache_header_t;
-/* {{{ struct definition: apc_cache_t */
typedef struct _apc_cache_t {
apc_cache_header_t* header; /* cache header (stored in SHM) */
uintptr_t* slots; /* array of cache slots (stored in SHM) */
@@ -89,13 +88,11 @@ typedef struct _apc_cache_t {
zend_long ttl; /* if slot is needed and entry's access time is older than this ttl, remove it */
zend_long smart; /* smart parameter for gc */
zend_bool defend; /* defense parameter for runtime */
-} apc_cache_t; /* }}} */
+} apc_cache_t;
-/* {{{ typedef: apc_cache_updater_t */
-typedef zend_bool (*apc_cache_updater_t)(apc_cache_t*, apc_cache_entry_t*, void* data); /* }}} */
+typedef zend_bool (*apc_cache_updater_t)(apc_cache_t*, apc_cache_entry_t*, void* data);
-/* {{{ typedef: apc_cache_atomic_updater_t */
-typedef zend_bool (*apc_cache_atomic_updater_t)(apc_cache_t*, zend_long*, void* data); /* }}} */
+typedef zend_bool (*apc_cache_atomic_updater_t)(apc_cache_t*, zend_long*, void* data);
/*
* apc_cache_create creates the shared memory cache.
@@ -126,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
*/
@@ -149,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,7 +166,6 @@ PHP_APCU_API zend_bool apc_cache_atomic_update_long(
/*
* apc_cache_fetch fetches an entry from the cache directly into dst
- *
*/
PHP_APCU_API zend_bool apc_cache_fetch(apc_cache_t* cache, zend_string *key, time_t t, zval *dst);
@@ -182,7 +180,8 @@ PHP_APCU_API zend_bool apc_cache_exists(apc_cache_t* cache, zend_string *key, ti
*/
PHP_APCU_API zend_bool apc_cache_delete(apc_cache_t* cache, zend_string *key);
-/* apc_cache_fetch_zval copies a cache entry value to be usable at runtime.
+/*
+ * apc_cache_fetch_zval copies a cache entry value to be usable at runtime.
*/
PHP_APCU_API zend_bool apc_cache_entry_fetch_zval(
apc_cache_t *cache, apc_cache_entry_t *entry, zval *dst);
@@ -199,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
@@ -220,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);
@@ -240,7 +238,11 @@ PHP_APCU_API void apc_cache_serializer(apc_cache_t* cache, const char* name);
* Note: beware of locking (copy it exactly), setting states is also important
*/
-/* {{{ apc_cache_default_expunge
+/*
+* apc_cache_default_expunge() is executed by the sma layer when there is not enough
+* free shared memory to satisfy an allocation request. It attempts to free memory
+* (e.g., by removing entries) so that the allocation request can be satisfied.
+*
* Where smart is not set:
* 1) Perform cleanup of stale entries
* 2) If available memory is less than the size requested, run full expunge
@@ -251,7 +253,7 @@ PHP_APCU_API void apc_cache_serializer(apc_cache_t* cache, const char* name);
*
* The TTL of an entry takes precedence over the TTL of a cache
*/
-PHP_APCU_API void apc_cache_default_expunge(apc_cache_t* cache, size_t size);
+PHP_APCU_API zend_bool apc_cache_default_expunge(apc_cache_t* cache, size_t size);
/*
* apc_cache_entry: generate and create or fetch an entry
diff --git a/apc_globals.h b/apc_globals.h
index 0eb1da1f..27100bd4 100644
--- a/apc_globals.h
+++ b/apc_globals.h
@@ -44,7 +44,8 @@ ZEND_BEGIN_MODULE_GLOBALS(apcu)
zend_long smart; /* smart value */
#ifdef APC_MMAP
- char *mmap_file_mask; /* mktemp-style file-mask to pass to mmap */
+ char *mmap_file_mask; /* mktemp-style file-mask to pass to mmap */
+ zend_long mmap_hugepage_size; /* hugepage size flag to pass to mmap (0: none)*/
#endif
/* module variables */
diff --git a/apc_iterator.c b/apc_iterator.c
index 9181085c..535a9df7 100644
--- a/apc_iterator.c
+++ b/apc_iterator.c
@@ -45,7 +45,6 @@ zend_class_entry* apc_iterator_get_ce(void) {
return; \
}
-/* {{{ apc_iterator_item */
static apc_iterator_item_t* apc_iterator_item_ctor(
apc_iterator_t *iterator, apc_cache_entry_t *entry) {
zval zv;
@@ -108,17 +107,13 @@ static apc_iterator_item_t* apc_iterator_item_ctor(
return item;
}
-/* }}} */
-/* {{{ apc_iterator_item_dtor */
static void apc_iterator_item_dtor(apc_iterator_item_t *item) {
zend_string_release(item->key);
zval_ptr_dtor(&item->value);
efree(item);
}
-/* }}} */
-/* {{{ acp_iterator_free */
static void apc_iterator_free(zend_object *object) {
apc_iterator_t *iterator = apc_iterator_fetch_from(object);
@@ -148,9 +143,7 @@ static void apc_iterator_free(zend_object *object) {
zend_object_std_dtor(object);
}
-/* }}} */
-/* {{{ apc_iterator_create */
zend_object* apc_iterator_create(zend_class_entry *ce) {
apc_iterator_t *iterator =
(apc_iterator_t*) emalloc(sizeof(apc_iterator_t) + zend_object_properties_size(ce));
@@ -166,11 +159,8 @@ zend_object* apc_iterator_create(zend_class_entry *ce) {
return &iterator->obj;
}
-/* }}} */
-/* {{{ apc_iterator_search_match
- * Verify if the key matches our search parameters
- */
+/* Verifies if the key matches our search parameters */
static int apc_iterator_search_match(apc_iterator_t *iterator, apc_cache_entry_t *entry) {
int rval = 1;
@@ -194,9 +184,7 @@ static int apc_iterator_search_match(apc_iterator_t *iterator, apc_cache_entry_t
return rval;
}
-/* }}} */
-/* {{{ apc_iterator_check_expiry */
static int apc_iterator_check_expiry(apc_cache_t* cache, apc_cache_entry_t *entry, time_t t)
{
if (entry->ttl) {
@@ -207,9 +195,7 @@ static int apc_iterator_check_expiry(apc_cache_t* cache, apc_cache_entry_t *entr
return 1;
}
-/* }}} */
-/* {{{ apc_iterator_fetch_active */
static size_t apc_iterator_fetch_active(apc_iterator_t *iterator) {
apc_cache_t *cache = apc_user_cache;
size_t count = 0;
@@ -249,9 +235,7 @@ static size_t apc_iterator_fetch_active(apc_iterator_t *iterator) {
return count;
}
-/* }}} */
-/* {{{ apc_iterator_fetch_deleted */
static size_t apc_iterator_fetch_deleted(apc_iterator_t *iterator) {
apc_cache_t *cache = apc_user_cache;
size_t count = 0;
@@ -287,9 +271,7 @@ static size_t apc_iterator_fetch_deleted(apc_iterator_t *iterator) {
return count;
}
-/* }}} */
-/* {{{ apc_iterator_totals */
static void apc_iterator_totals(apc_iterator_t *iterator) {
apc_cache_t *cache = apc_user_cache;
time_t t = apc_time();
@@ -320,7 +302,6 @@ static void apc_iterator_totals(apc_iterator_t *iterator) {
apc_cache_runlock(cache);
} php_apc_end_try();
}
-/* }}} */
void apc_iterator_obj_init(apc_iterator_t *iterator, zval *search, zend_long format, size_t chunk_size, zend_long list)
{
@@ -506,7 +487,6 @@ PHP_METHOD(APCUIterator, getTotalHits) {
RETURN_LONG(iterator->hits);
}
-/* }}} */
PHP_METHOD(APCUIterator, getTotalSize) {
apc_iterator_t *iterator = apc_iterator_fetch(getThis());
@@ -540,7 +520,6 @@ PHP_METHOD(APCUIterator, getTotalCount) {
RETURN_LONG(iterator->count);
}
-/* {{{ apc_iterator_init */
int apc_iterator_init(int module_number) {
zend_class_entry ce;
@@ -573,13 +552,11 @@ int apc_iterator_init(int module_number) {
return SUCCESS;
}
-/* }}} */
int apc_iterator_shutdown(int module_number) {
return SUCCESS;
}
-/* {{{ apc_iterator_delete */
int apc_iterator_delete(zval *zobj) {
apc_iterator_t *iterator;
zend_class_entry *ce = Z_OBJCE_P(zobj);
@@ -606,8 +583,6 @@ int apc_iterator_delete(zval *zobj) {
return 1;
}
-/* }}} */
-
/*
* Local variables:
diff --git a/apc_iterator.h b/apc_iterator.h
index 9feda387..d38a5a35 100644
--- a/apc_iterator.h
+++ b/apc_iterator.h
@@ -46,7 +46,6 @@
#define APC_ITER_NONE 0
#define APC_ITER_ALL (0xffffffffL)
-/* {{{ apc_iterator_t */
typedef struct _apc_iterator_t {
short int initialized; /* sanity check in case __construct failed */
zend_long format; /* format bitmask of the return values ie: key, value, info */
@@ -69,17 +68,14 @@ typedef struct _apc_iterator_t {
zend_long count; /* count total */
zend_object obj;
} apc_iterator_t;
-/* }}} */
#define apc_iterator_fetch_from(o) ((apc_iterator_t*)((char*)o - XtOffsetOf(apc_iterator_t, obj)))
#define apc_iterator_fetch(z) apc_iterator_fetch_from(Z_OBJ_P(z))
-/* {{{ apc_iterator_item */
typedef struct _apc_iterator_item_t {
zend_string *key;
zval value;
} apc_iterator_item_t;
-/* }}} */
PHP_APCU_API void apc_iterator_obj_init(
apc_iterator_t *iterator,
diff --git a/apc_lock.h b/apc_lock.h
index 7288cb9f..a808814c 100644
--- a/apc_lock.h
+++ b/apc_lock.h
@@ -22,10 +22,10 @@
/*
APCu works most efficiently where there is access to native read/write locks
If the current system has native rwlocks present they will be used, if they are
- not present, APCu will emulate their behavior with standard mutex.
+ not present, APCu will emulate their behavior with standard mutex.
While APCu is emulating read/write locks, reads and writes are exclusive,
- additionally the write lock prefers readers, as is the default behaviour of
- the majority of Posix rwlock implementations
+ additionally the write lock prefers readers, as is the default behavior of
+ the majority of Posix rwlock implementations
*/
#ifdef HAVE_CONFIG_H
@@ -65,13 +65,14 @@ typedef apc_windows_cs_rwlock_t apc_lock_t;
# define APC_LOCK_SHARED
#endif
-/* {{{ functions */
/*
- The following functions should be called once per process:
- apc_lock_init initializes attributes suitable for all locks
- apc_lock_cleanup destroys those attributes
- This saves us from having to create and destroy attributes for
- every lock we use at runtime */
+ The following functions should be called once per process:
+ - apc_lock_init initializes attributes suitable for all locks
+ - apc_lock_cleanup destroys those attributes
+
+ This saves us from having to create and destroy attributes for
+ every lock we use at runtime
+ */
PHP_APCU_API zend_bool apc_lock_init(void);
PHP_APCU_API void apc_lock_cleanup(void);
/*
@@ -82,16 +83,15 @@ PHP_APCU_API zend_bool apc_lock_rlock(apc_lock_t *lock);
PHP_APCU_API zend_bool apc_lock_wlock(apc_lock_t *lock);
PHP_APCU_API zend_bool apc_lock_runlock(apc_lock_t *lock);
PHP_APCU_API zend_bool apc_lock_wunlock(apc_lock_t *lock);
-PHP_APCU_API void apc_lock_destroy(apc_lock_t *lock); /* }}} */
+PHP_APCU_API void apc_lock_destroy(apc_lock_t *lock);
-/* {{{ generic locking macros */
+/* generic locking macros */
#define CREATE_LOCK(lock) apc_lock_create(lock)
#define DESTROY_LOCK(lock) apc_lock_destroy(lock)
#define WLOCK(lock) apc_lock_wlock(lock)
#define WUNLOCK(lock) { apc_lock_wunlock(lock); HANDLE_UNBLOCK_INTERRUPTIONS(); }
#define RLOCK(lock) apc_lock_rlock(lock)
#define RUNLOCK(lock) { apc_lock_runlock(lock); HANDLE_UNBLOCK_INTERRUPTIONS(); }
-/* }}} */
/* atomic operations */
#ifdef PHP_WIN32
diff --git a/apc_mmap.c b/apc_mmap.c
index ad739818..225de401 100644
--- a/apc_mmap.c
+++ b/apc_mmap.c
@@ -51,7 +51,36 @@
# define MAP_ANON MAP_ANONYMOUS
#endif
-void *apc_mmap(char *file_mask, size_t size)
+static int apc_mmap_hugepage_flags(size_t size, zend_long hugepage_size)
+{
+ if (!hugepage_size) return 0; // not use hugepages
+
+#if defined(MAP_HUGETLB) && defined(MAP_HUGE_MASK) && defined(MAP_HUGE_SHIFT)
+ if (size % hugepage_size) {
+ zend_error_noreturn(E_CORE_ERROR, "apc.shm_size must be a multiple of apc.mmap_hugepage_size");
+ }
+
+ zend_long page_size = hugepage_size;
+ int log2_page_size = -1;
+
+ // calculate log2 of hugepage size
+ while (page_size) {
+ page_size >>= 1;
+ log2_page_size++;
+ }
+
+ if (!log2_page_size || (log2_page_size & MAP_HUGE_MASK) != log2_page_size) {
+ // maybe hugepage size is too large or small
+ zend_error_noreturn(E_CORE_ERROR, "Invalid hugepage size: %ld", hugepage_size);
+ }
+
+ return MAP_HUGETLB | ((unsigned int)log2_page_size << MAP_HUGE_SHIFT);
+#else
+ zend_error_noreturn(E_CORE_ERROR, "This system does not support hugepages");
+#endif
+}
+
+void *apc_mmap(char *file_mask, size_t size, zend_long hugepage_size)
{
void *shmaddr;
int fd = -1;
@@ -84,15 +113,22 @@ void *apc_mmap(char *file_mask, size_t size)
unlink(file_mask);
}
+ flags |= apc_mmap_hugepage_flags(size, hugepage_size);
shmaddr = (void *)mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0);
if ((long)shmaddr == -1) {
- zend_error_noreturn(E_CORE_ERROR, "apc_mmap: Failed to mmap %zu bytes. Is your apc.shm_size too large?", size);
+ if (hugepage_size) {
+ zend_error_noreturn(E_CORE_ERROR, "apc_mmap: Failed to mmap %zu bytes with hugepage size %ld. apc.shm_size may be too large, apc.mmap_hugepage_size may be invalid, or the system lacks sufficient reserved hugepages.", size, hugepage_size);
+ } else {
+ zend_error_noreturn(E_CORE_ERROR, "apc_mmap: Failed to mmap %zu bytes. apc.shm_size may be too large.", size);
+ }
}
#ifdef MADV_HUGEPAGE
/* enable transparent huge pages to reduce TLB misses (Linux only) */
- madvise(shmaddr, size, MADV_HUGEPAGE);
+ if (!hugepage_size) {
+ madvise(shmaddr, size, MADV_HUGEPAGE);
+ }
#endif
if (fd != -1) close(fd);
diff --git a/apc_mmap.h b/apc_mmap.h
index 142a6e55..ab5aaec7 100644
--- a/apc_mmap.h
+++ b/apc_mmap.h
@@ -35,7 +35,7 @@
/* Wrapper functions for shared memory mapped files */
#ifdef APC_MMAP
-void *apc_mmap(char *file_mask, size_t size);
+void *apc_mmap(char *file_mask, size_t size, zend_long hugepage_size);
void apc_unmap(void *shmaddr, size_t size);
#endif
diff --git a/apc_persist.c b/apc_persist.c
index 8f74e347..e322b8d2 100644
--- a/apc_persist.c
+++ b/apc_persist.c
@@ -333,7 +333,8 @@ static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_I
static zend_array *apc_persist_copy_ht(apc_persist_context_t *ctxt, const HashTable *orig_ht) {
#if PHP_VERSION_ID >= 70300
if (orig_ht->nNumOfElements == 0) {
- return (HashTable *)&zend_empty_array;
+ /* To indicate using zend_empty_array during unpersist, we point to the entry's starting address. */
+ return (HashTable *)ctxt->alloc;
}
#endif
HashTable *ht = COPY(orig_ht, sizeof(HashTable));
@@ -474,6 +475,13 @@ static apc_cache_entry_t *apc_persist_create_entry(
return entry;
}
+static void apc_persist_sma_init_entry(apc_cache_entry_t *entry) {
+ /* The ref_count must be initialized during allocation. This ensures that the entry
+ * is not moved by defragmentation before all persistence operations are completed
+ * and the entry is stored in the hash table. */
+ entry->ref_count = 1;
+}
+
apc_cache_entry_t *apc_persist(
apc_sma_t *sma, apc_serializer_t *serializer, zend_string *key, const zval *val) {
apc_persist_context_t ctxt;
@@ -515,7 +523,7 @@ apc_cache_entry_t *apc_persist(
}
}
- ctxt.alloc = ctxt.alloc_cur = apc_sma_malloc(sma, ctxt.size);
+ ctxt.alloc = ctxt.alloc_cur = apc_sma_malloc(sma, ctxt.size, (apc_sma_malloc_init_f)apc_persist_sma_init_entry);
if (!ctxt.alloc) {
apc_persist_destroy_context(&ctxt);
return NULL;
@@ -708,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;
}
diff --git a/apc_signal.c b/apc_signal.c
index bb47f77f..bb12026e 100644
--- a/apc_signal.c
+++ b/apc_signal.c
@@ -52,9 +52,7 @@ static void apc_clear_cache(int signo, siginfo_t *siginfo, void *context);
extern apc_cache_t* apc_user_cache;
-/* {{{ apc_core_unmap
- * Coredump signal handler, detached from shm and calls previously installed handlers
- */
+/* Coredump signal handler, detached from shm and calls previously installed handlers */
static void apc_core_unmap(int signo, siginfo_t *siginfo, void *context)
{
if (apc_user_cache) {
@@ -67,11 +65,11 @@ static void apc_core_unmap(int signo, siginfo_t *siginfo, void *context)
#else
raise(signo);
#endif
-} /* }}} */
+}
#if defined(SIGUSR1) && defined(APC_CLEAR_SIGNAL)
-/* {{{ apc_reload_cache */
+/* Clears the cache on SIGUSR1 */
static void apc_clear_cache(int signo, siginfo_t *siginfo, void *context) {
if (apc_user_cache) {
apc_cache_clear(apc_user_cache);
@@ -84,12 +82,10 @@ static void apc_clear_cache(int signo, siginfo_t *siginfo, void *context) {
#else
raise(signo);
#endif
-} /* }}} */
+}
#endif
-/* {{{ apc_rehandle_signal
- * Call the previously registered handler for a signal
- */
+/* Call the previously registered handler for a signal */
static void apc_rehandle_signal(int signo, siginfo_t *siginfo, void *context)
{
int i;
@@ -106,12 +102,9 @@ static void apc_rehandle_signal(int signo, siginfo_t *siginfo, void *context)
}
}
-} /* }}} */
+}
-/* {{{ apc_register_signal
- * Set a handler for a previously installed signal and save so we can
- * callback when handled
- */
+/* Set a handler for a previously installed signal and save so we can callback when handled */
static int apc_register_signal(int signo, void (*handler)(int, siginfo_t*, void*))
{
struct sigaction sa;
@@ -150,10 +143,9 @@ static int apc_register_signal(int signo, void (*handler)(int, siginfo_t*, void*
return SUCCESS;
}
return FAILURE;
-} /* }}} */
+}
-/* {{{ apc_set_signals
- * Install our signal handlers */
+/* Install our signal handlers */
void apc_set_signals()
{
if (apc_signal_info.installed == 0) {
@@ -196,10 +188,9 @@ void apc_set_signals()
#endif
}
}
-} /* }}} */
+}
-/* {{{ apc_set_signals
- * cleanup signals for shutdown */
+/* Cleanup signals for shutdown */
void apc_shutdown_signals()
{
int i=0;
@@ -211,7 +202,6 @@ void apc_shutdown_signals()
apc_signal_info.installed = 0; /* just in case */
}
}
-/* }}} */
#endif /* HAVE_SIGACTION */
diff --git a/apc_sma.c b/apc_sma.c
index 3559c545..1d31256e 100644
--- a/apc_sma.c
+++ b/apc_sma.c
@@ -80,7 +80,7 @@ struct block_t {
/* macros for getting the next or previous sequential block */
#define NEXT_SBLOCK(block) ((block_t*)((char*)block + block->size))
-#define PREV_SBLOCK(block) (block->prev_size ? ((block_t*)((char*)block - block->prev_size)) : NULL)
+#define PREV_SBLOCK(block) ((block_t*)((char*)block - block->prev_size))
/* Canary macros for setting, checking and resetting memory canaries */
#ifdef APC_SMA_CANARIES
@@ -98,14 +98,34 @@ struct block_t {
/* How many extra blocks to check for a better fit */
#define BEST_FIT_LIMIT 3
+static inline void link_free_block_at_start(sma_header_t *smaheader, block_t *cur) {
+ block_t *dst = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
+
+ /* insert cur as first block in the free list */
+ cur->fnext = dst->fnext;
+ cur->fprev = OFFSET(dst);
+ dst->fnext = OFFSET(cur);
+ BLOCKAT(cur->fnext)->fprev = dst->fnext;
+}
+
+static inline void unlink_free_block(sma_header_t *smaheader, block_t *cur) {
+ BLOCKAT(cur->fprev)->fnext = cur->fnext;
+ BLOCKAT(cur->fnext)->fprev = cur->fprev;
+}
+
static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) {
- block_t *cur, *prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
+ block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
block_t *found = NULL;
uint32_t i;
- CHECK_CANARY(prv);
+ CHECK_CANARY(cur);
+
+ /* First, ensure that at least realsize free bytes are available, even if they are not contiguous. */
+ if (smaheader->avail < realsize) {
+ return NULL;
+ }
- while (prv->fnext) {
- cur = BLOCKAT(prv->fnext);
+ while (cur->fnext) {
+ cur = BLOCKAT(cur->fnext);
CHECK_CANARY(cur);
/* Found a suitable block */
@@ -113,42 +133,30 @@ static inline block_t *find_block(sma_header_t *smaheader, size_t realsize) {
found = cur;
break;
}
-
- prv = cur;
}
if (found) {
/* Try to find a smaller block that also fits */
- prv = cur;
- for (i = 0; i < BEST_FIT_LIMIT && prv->fnext; i++) {
- cur = BLOCKAT(prv->fnext);
+ for (i = 0; i < BEST_FIT_LIMIT && cur->fnext; i++) {
+ cur = BLOCKAT(cur->fnext);
CHECK_CANARY(cur);
if (cur->size >= realsize && cur->size < found->size) {
found = cur;
}
-
- prv = cur;
}
}
return found;
}
-/* {{{ sma_allocate: tries to allocate at least size bytes of shared memory */
+/* sma_allocate: tries to allocate at least size bytes of shared memory */
static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size)
{
- block_t* prv; /* block prior to working block */
block_t* cur; /* working block in list */
size_t realsize; /* actual size of block needed, including block header */
- size_t block_header_size = ALIGNWORD(sizeof(block_t));
- realsize = ALIGNWORD(size + block_header_size);
-
- /* First, ensure that the segment contains at least realsize free bytes, even if they are not contiguous. */
- if (smaheader->avail < realsize) {
- return SIZE_MAX;
- }
+ realsize = ALIGNWORD(size + ALIGNWORD(sizeof(block_t)));
cur = find_block(smaheader, realsize);
if (!cur) {
@@ -156,11 +164,11 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size)
return SIZE_MAX;
}
+ /* remove cur from the list of free blocks */
+ 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 - unlink it */
- prv = BLOCKAT(cur->fprev);
- prv->fnext = cur->fnext;
- BLOCKAT(cur->fnext)->fprev = OFFSET(prv);
+ /* cur is big enough for realsize, but too small to split */
NEXT_SBLOCK(cur)->prev_size = 0; /* block is alloc'd */
} else {
/* cur is too big; split it into two smaller blocks */
@@ -175,25 +183,25 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *smaheader, size_t size)
NEXT_SBLOCK(nxt)->prev_size = nxt->size; /* adjust size */
SET_CANARY(nxt);
- /* replace cur with next in free list */
- nxt->fnext = cur->fnext;
- nxt->fprev = cur->fprev;
- BLOCKAT(nxt->fnext)->fprev = OFFSET(nxt);
- BLOCKAT(nxt->fprev)->fnext = OFFSET(nxt);
+ /* put the remaining block (nxt) back into the free list */
+ link_free_block_at_start(smaheader, nxt);
}
+ /* mark cur as allocated */
cur->fnext = 0;
+ /* store used space to be able to reclaim unused space during defragmentation */
+ cur->fprev = realsize;
+
/* update the segment header */
smaheader->avail -= cur->size;
SET_CANARY(cur);
- return OFFSET(cur) + block_header_size;
+ return OFFSET(cur) + ALIGNWORD(sizeof(block_t));
}
-/* }}} */
-/* {{{ sma_deallocate: deallocates the block at the given offset */
+/* sma_deallocate: deallocates the block at the given offset */
static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset)
{
block_t* cur; /* the new block to insert */
@@ -212,12 +220,12 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset)
size = cur->size;
if (cur->prev_size != 0) {
- /* remove prv from list */
+ /* remove prv from the list of free blocks */
prv = PREV_SBLOCK(cur);
- BLOCKAT(prv->fnext)->fprev = prv->fprev;
- BLOCKAT(prv->fprev)->fnext = prv->fnext;
+ unlink_free_block(smaheader, prv);
+
/* cur and prv share an edge, combine them */
- prv->size +=cur->size;
+ prv->size += cur->size;
RESET_CANARY(cur);
cur = prv;
@@ -226,30 +234,26 @@ static APC_HOTSPOT size_t sma_deallocate(sma_header_t *smaheader, size_t offset)
nxt = NEXT_SBLOCK(cur);
if (nxt->fnext != 0) {
assert(NEXT_SBLOCK(NEXT_SBLOCK(cur))->prev_size == nxt->size);
+ /* remove nxt from the list of free blocks */
+ unlink_free_block(smaheader, nxt);
+
/* cur and nxt shared an edge, combine them */
- BLOCKAT(nxt->fnext)->fprev = nxt->fprev;
- BLOCKAT(nxt->fprev)->fnext = nxt->fnext;
cur->size += nxt->size;
CHECK_CANARY(nxt);
RESET_CANARY(nxt);
}
+ /* mark in the sequentially next block that the previous block is free */
NEXT_SBLOCK(cur)->prev_size = cur->size;
- /* insert new block after prv */
- prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
- cur->fnext = prv->fnext;
- prv->fnext = OFFSET(cur);
- cur->fprev = OFFSET(prv);
- BLOCKAT(cur->fnext)->fprev = OFFSET(cur);
+ /* insert cur into the free list */
+ link_free_block_at_start(smaheader, cur);
return size;
}
-/* }}} */
-/* {{{ APC SMA API */
-PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, size_t size, size_t min_alloc_size, char *mask) {
+PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f expunge, size_t size, size_t min_alloc_size, char *mask, zend_long hugepage_size) {
if (sma->initialized) {
return;
}
@@ -260,7 +264,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex
sma->size = ALIGNWORD(size > 0 ? size : SMA_DEFAULT_SEGSIZE);
#ifdef APC_MMAP
- sma->shmaddr = apc_mmap(mask, sma->size);
+ sma->shmaddr = apc_mmap(mask, sma->size, hugepage_size);
#else
sma->shmaddr = apc_shm_attach(sma->size);
#endif
@@ -269,6 +273,7 @@ PHP_APCU_API void apc_sma_init(apc_sma_t* sma, void** data, apc_sma_expunge_f ex
SMA_CREATE_LOCK(&smaheader->sma_lock);
smaheader->min_block_size = min_alloc_size > 0 ? ALIGNWORD(min_alloc_size + ALIGNWORD(sizeof(block_t))) : MINBLOCKSIZE;
smaheader->avail = sma->size - ALIGNWORD(sizeof(sma_header_t)) - ALIGNWORD(sizeof(block_t)) - ALIGNWORD(sizeof(block_t));
+ sma->max_alloc_size = smaheader->avail - ALIGNWORD(sizeof(block_t));
block_t *first = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
first->size = 0;
@@ -307,33 +312,45 @@ PHP_APCU_API void apc_sma_detach(apc_sma_t* sma) {
#endif
}
-PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n) {
+PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t n, apc_sma_malloc_init_f init_callback) {
size_t off;
zend_bool nuked = 0;
restart:
assert(sma->initialized);
+ /* Prevent cache wipes caused by huge allocations that don't fit into shm */
+ if (n > sma->max_alloc_size) {
+ return NULL;
+ }
+
if (!SMA_LOCK(sma)) {
return NULL;
}
off = sma_allocate(SMA_HDR(sma), n);
- SMA_UNLOCK(sma);
-
if (off != SIZE_MAX) {
void *p = (void *)(SMA_ADDR(sma) + off);
+
+ if (init_callback) {
+ /* Perform initializations that must be done before releasing the lock */
+ init_callback(p);
+ }
+
+ SMA_UNLOCK(sma);
#ifdef VALGRIND_MALLOCLIKE_BLOCK
VALGRIND_MALLOCLIKE_BLOCK(p, n, 0, 0);
#endif
return p;
}
+ SMA_UNLOCK(sma);
+
/* Expunge cache in hope of freeing up memory, but only once */
if (!nuked) {
- sma->expunge(*sma->data, n);
- nuked = 1;
+ /* nuke is not set if expunge() was skipped internally to get another try */
+ nuked = sma->expunge(*sma->data, n);
goto restart;
}
@@ -380,24 +397,29 @@ PHP_APCU_API apc_sma_info_t *apc_sma_info(apc_sma_t* sma, zend_bool limited) {
return info;
}
- SMA_LOCK(sma);
+ if (!SMA_LOCK(sma)) {
+ efree(info);
+ return NULL;
+ }
+
sma_header_t *smaheader = SMA_HDR(sma);
- block_t *prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
+ block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
apc_sma_link_t **link = &info->list;
- /* For each free block */
- while (BLOCKAT(prv->fnext)->fnext != 0) {
- block_t *cur = BLOCKAT(prv->fnext);
+ /* Skip 1st (0-sized) block */
+ cur = BLOCKAT(cur->fnext);
+ /* For each free block */
+ while (cur->fnext != 0) {
CHECK_CANARY(cur);
*link = emalloc(sizeof(apc_sma_link_t));
(*link)->size = cur->size;
- (*link)->offset = prv->fnext;
+ (*link)->offset = OFFSET(cur);
(*link)->next = NULL;
link = &(*link)->next;
- prv = cur;
+ cur = BLOCKAT(cur->fnext);
}
SMA_UNLOCK(sma);
@@ -420,7 +442,11 @@ PHP_APCU_API size_t apc_sma_get_avail_mem(apc_sma_t* sma) {
return SMA_HDR(sma)->avail;
}
-PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) {
+PHP_APCU_API zend_bool apc_sma_check_avail(apc_sma_t *sma, size_t size) {
+ return SMA_HDR(sma)->avail >= ALIGNWORD(size + ALIGNWORD(sizeof(block_t)));
+}
+
+PHP_APCU_API zend_bool apc_sma_check_avail_contiguous(apc_sma_t *sma, size_t size) {
size_t realsize = ALIGNWORD(size + ALIGNWORD(sizeof(block_t)));
sma_header_t *smaheader = SMA_HDR(sma);
@@ -429,7 +455,10 @@ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) {
return 0;
}
- SMA_LOCK(sma);
+ if (!SMA_LOCK(sma)) {
+ return 0;
+ }
+
block_t *cur = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
/* Look for a contiguous block of memory */
@@ -447,12 +476,67 @@ PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size) {
return 0;
}
-PHP_APCU_API void apc_sma_check_integrity(apc_sma_t* sma)
-{
- /* dummy */
-}
+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;
+ }
-/* }}} */
+ /* empty the free list */
+ first->fnext = sma->size - ALIGNWORD(sizeof(block_t));
+ BLOCKAT(first->fnext)->fprev = OFFSET(first);
+
+ /* loop through all blocks */
+ while (cur->size != 0) {
+ /* continue until cur points to a free block */
+ if (!cur->fnext) {
+ cur = NEXT_SBLOCK(cur);
+ continue;
+ }
+
+ /* if cur is free, nxt must be an allocated block, since we never have two consecutive free blocks */
+ block_t *nxt = NEXT_SBLOCK(cur);
+
+ /* if nxt is the last block, or if nxt can't be moved, cur can't be combined with other free blocks */
+ if (nxt->size == 0 || !move(data, (char *)nxt + ALIGNWORD(sizeof(block_t)), (char *)cur + ALIGNWORD(sizeof(block_t)))) {
+ /* insert cur into the free list */
+ link_free_block_at_start(smaheader, cur);
+
+ cur->prev_size = 0;
+ nxt->prev_size = cur->size;
+
+ 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;
+ }
+ }
+
+ smaheader->avail += reclaimed_size;
+
+ SMA_UNLOCK(sma);
+}
/*
* Local variables:
diff --git a/apc_sma.h b/apc_sma.h
index 472a8c72..6bd35a65 100644
--- a/apc_sma.h
+++ b/apc_sma.h
@@ -28,33 +28,29 @@
#ifndef APC_SMA_H
#define APC_SMA_H
-/* {{{ SMA API
- APC SMA API provides support for shared memory allocators to external libraries ( and to APC )
- Skip to the bottom macros for error free usage of the SMA API
+/*
+ * SMA API
+ * APC SMA API provides support for shared memory allocators to external libraries ( and to APC )
+ * Skip to the bottom macros for error free usage of the SMA API
*/
#include "apc.h"
-/* {{{ struct definition: apc_sma_link_t */
typedef struct apc_sma_link_t apc_sma_link_t;
struct apc_sma_link_t {
zend_long size; /* size of this free block */
zend_long offset; /* offset in segment of this block */
apc_sma_link_t* next; /* link to next free block */
};
-/* }}} */
-/* {{{ struct definition: apc_sma_info_t */
typedef struct apc_sma_info_t apc_sma_info_t;
struct apc_sma_info_t {
size_t seg_size; /* segment size */
apc_sma_link_t* list; /* list of free blocks */
};
-/* }}} */
-typedef 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 */
@@ -64,8 +60,9 @@ typedef struct _apc_sma_t {
/* info */
size_t size; /* segment size */
+ size_t max_alloc_size; /* max size of memory available for allocation */
void *shmaddr; /* address of shm segment */
-} apc_sma_t; /* }}} */
+} apc_sma_t;
/*
* apc_sma_init will initialize a shared memory allocator with the given size of shared memory
@@ -74,7 +71,7 @@ typedef struct _apc_sma_t {
*/
PHP_APCU_API void apc_sma_init(
apc_sma_t* sma, void** data, apc_sma_expunge_f expunge,
- size_t size, size_t min_alloc_size, char *mask);
+ size_t size, size_t min_alloc_size, char *mask, zend_long hugepage_size);
/*
* apc_sma_detach will detach from shared memory and cleanup local allocations.
@@ -82,9 +79,12 @@ PHP_APCU_API void apc_sma_init(
PHP_APCU_API void apc_sma_detach(apc_sma_t* sma);
/*
-* apc_smap_api_malloc will allocate a block from the sma of the given size
+* apc_smap_api_malloc will allocate a block from the sma of the given size.
+* The init_callack() can be used to perform initializations that must be completed
+* before the lock of the sma layer is released.
*/
-PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t size);
+typedef void (*apc_sma_malloc_init_f)(void *pointer);
+PHP_APCU_API void* apc_sma_malloc(apc_sma_t* sma, size_t size, apc_sma_malloc_init_f init_callback);
/*
* apc_sma_api_free will free p (which should be a pointer to a block allocated from sma)
@@ -107,18 +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 contiguous bytes are available to the sma
+* apc_sma_check_avail returns true if at least size bytes are available across all free blocks
+*/
+PHP_APCU_API zend_bool apc_sma_check_avail(apc_sma_t *sma, size_t size);
+
+/*
+* apc_sma_check_avail_contiguous returns true if at least size contiguous bytes can be allocated from the sma
*/
-PHP_APCU_API zend_bool apc_sma_get_avail_size(apc_sma_t* sma, size_t size);
+PHP_APCU_API zend_bool apc_sma_check_avail_contiguous(apc_sma_t *sma, size_t size);
/*
-* apc_sma_api_check_integrity will check the integrity of sma
+* 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.
*/
-PHP_APCU_API void apc_sma_check_integrity(apc_sma_t* sma); /* }}} */
+typedef zend_bool (*apc_sma_move_f)(void *data, void *pointer_old, void *pointer_new);
+PHP_APCU_API void apc_sma_defrag(apc_sma_t *sma, void *data, apc_sma_move_f move);
-/* {{{ ALIGNWORD: pad up x, aligned to the system's word boundary */
+/* ALIGNWORD: pad up x, aligned to the system's word boundary */
#define ALIGNWORD(x) ZEND_MM_ALIGNED_SIZE(x)
-/* }}} */
#endif
-
diff --git a/apc_stack.c b/apc_stack.c
index 22d81b9f..57823e33 100644
--- a/apc_stack.c
+++ b/apc_stack.c
@@ -93,7 +93,6 @@ int apc_stack_size(apc_stack_t* stack)
return stack->size;
}
-
/*
* Local variables:
* tab-width: 4
diff --git a/package.xml b/package.xml
index f9c90514..692dafeb 100644
--- a/package.xml
+++ b/package.xml
@@ -28,9 +28,9 @@
nikic@php.net
yes
- 2024-09-21
+ 2025-12-07
- 5.1.25-dev
+ 5.1.28
5.1.18
@@ -39,7 +39,13 @@
PHP License
-- TBD
+ - 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.
@@ -75,6 +81,7 @@
+
@@ -83,17 +90,21 @@
+
+
-
+
+
+
+
+
+
-
-
-
@@ -185,6 +196,89 @@
+
+ 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
diff --git a/php_apc.c b/php_apc.c
index 3aff354a..392b05ac 100644
--- a/php_apc.c
+++ b/php_apc.c
@@ -62,7 +62,6 @@
#include "apc_signal.h"
#endif
-/* {{{ ZEND_DECLARE_MODULE_GLOBALS(apcu) */
ZEND_DECLARE_MODULE_GLOBALS(apcu)
/* True globals */
@@ -87,11 +86,10 @@ static void php_apc_init_globals(zend_apcu_globals* apcu_globals)
apcu_globals->serializer_name = NULL;
apcu_globals->entry_level = 0;
}
-/* }}} */
-/* {{{ PHP_INI */
+/* PHP_INI */
-static PHP_INI_MH(OnUpdateShmSize) /* {{{ */
+static PHP_INI_MH(OnUpdateShmSize)
{
#if PHP_VERSION_ID >= 80200
zend_long s = zend_ini_parse_quantity_warn(new_value, entry->name);
@@ -114,7 +112,32 @@ 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)
@@ -124,7 +147,8 @@ STD_PHP_INI_ENTRY("apc.gc_ttl", "3600", PHP_INI_SYSTEM, OnUpdateLong,
STD_PHP_INI_ENTRY("apc.ttl", "0", PHP_INI_SYSTEM, OnUpdateLong, ttl, zend_apcu_globals, apcu_globals)
STD_PHP_INI_ENTRY("apc.smart", "0", PHP_INI_SYSTEM, OnUpdateLong, smart, zend_apcu_globals, apcu_globals)
#ifdef APC_MMAP
-STD_PHP_INI_ENTRY("apc.mmap_file_mask", NULL, PHP_INI_SYSTEM, OnUpdateString, mmap_file_mask, zend_apcu_globals, apcu_globals)
+STD_PHP_INI_ENTRY("apc.mmap_file_mask", NULL, PHP_INI_SYSTEM, OnUpdateString, mmap_file_mask, zend_apcu_globals, apcu_globals)
+STD_PHP_INI_ENTRY("apc.mmap_hugepage_size", "0", PHP_INI_SYSTEM, OnUpdateMmapHugepageSize, mmap_hugepage_size, zend_apcu_globals, apcu_globals)
#endif
STD_PHP_INI_BOOLEAN("apc.enable_cli", "0", PHP_INI_SYSTEM, OnUpdateBool, enable_cli, zend_apcu_globals, apcu_globals)
STD_PHP_INI_BOOLEAN("apc.slam_defense", "0", PHP_INI_SYSTEM, OnUpdateBool, slam_defense, zend_apcu_globals, apcu_globals)
@@ -134,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();
@@ -188,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)
@@ -219,10 +238,12 @@ static PHP_MINIT_FUNCTION(apcu)
if (APCG(enabled)) {
if (!APCG(initialized)) {
-#ifdef APC_MMAP
- char *mmap_file_mask = APCG(mmap_file_mask);
-#else
char *mmap_file_mask = NULL;
+ zend_long mmap_hugepage_size = 0;
+
+#ifdef APC_MMAP
+ mmap_file_mask = APCG(mmap_file_mask);
+ mmap_hugepage_size = APCG(mmap_hugepage_size);
#endif
/* ensure this runs only once */
@@ -231,7 +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_size), APC_ENTRY_SIZE(0), mmap_file_mask);
+ APCG(shm_size), APC_ENTRY_SIZE(0), mmap_file_mask, mmap_hugepage_size);
REGISTER_LONG_CONSTANT(APC_SERIALIZER_CONSTANT, (zend_long)&_apc_register_serializer, CONST_PERSISTENT | CONST_CS);
@@ -261,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);
@@ -293,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)
@@ -315,9 +333,8 @@ static PHP_RINIT_FUNCTION(apcu)
}
return SUCCESS;
}
-/* }}} */
-/* {{{ proto void apcu_clear_cache() */
+/* proto void apcu_clear_cache() */
PHP_FUNCTION(apcu_clear_cache)
{
if (zend_parse_parameters_none() == FAILURE) {
@@ -327,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;
@@ -344,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;
@@ -356,9 +371,9 @@ PHP_FUNCTION(apcu_key_info)
ZEND_PARSE_PARAMETERS_END();
apc_cache_stat(apc_user_cache, key, return_value);
-} /* }}} */
+}
-/* {{{ proto array apcu_sma_info([bool limited]) */
+/* proto array apcu_sma_info([bool limited]) */
PHP_FUNCTION(apcu_sma_info)
{
zend_bool limited = 0;
@@ -407,9 +422,7 @@ PHP_FUNCTION(apcu_sma_info)
add_assoc_zval(return_value, "block_lists", &block_lists);
apc_sma_free_info(&apc_sma, info);
}
-/* }}} */
-/* {{{ php_apc_update */
zend_bool php_apc_update(
zend_string *key, apc_cache_atomic_updater_t updater, void *data,
zend_bool insert_if_not_found, time_t ttl)
@@ -421,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;
@@ -480,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;
@@ -519,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;
@@ -549,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;
@@ -582,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];
@@ -612,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;
@@ -667,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;
@@ -710,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;
@@ -762,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. */
@@ -787,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,
@@ -801,7 +787,6 @@ zend_module_entry apcu_module_entry = {
PHP_APCU_VERSION,
STANDARD_MODULE_PROPERTIES
};
-/* }}} */
#ifdef COMPILE_DL_APCU
ZEND_GET_MODULE(apcu)
@@ -809,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 3ca816fb..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.25-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/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--
= $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
new file mode 100644
index 00000000..947f016e
--- /dev/null
+++ b/tests/apc_026.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Huge allocations which don't fit into shm shouldn't cause cache wipes
+--INI--
+apc.enabled=1
+apc.enable_cli=1
+apc.shm_size=1M
+--FILE--
+
+--EXPECT--
+bool(false)
+bool(true)
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_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/bug63224.phpt b/tests/bug63224.phpt
deleted file mode 100644
index 92ea3c90..00000000
--- a/tests/bug63224.phpt
+++ /dev/null
@@ -1,310 +0,0 @@
---TEST--
-APC: Bug #63224 error in __sleep whit reference to other classes
---SKIPIF--
-
---CONFLICTS--
-server
---FILE--
-b->f();
- return array('b');
- }
-}
-
-
-class B{
- const A_CONSTANT = 1;
- public \$var;
-
- public function f(){
- \$this->var = self::A_CONSTANT;
- }
-}
-
-
-if(isset(\$_SESSION['lalala'])){
- echo "";
- \$a = \$_SESSION['lalala'];
- print_r(\$a);
-} else {
- echo "no session yet, first run\n";
-}
-
-// another file
-// class A and B use autoload
-\$b = new B();
-\$a = new A();
-\$a->b = \$b;
-
-\$_SESSION['lalala'] = \$a;
-session_write_close();
-FL;
-
-$args = array(
- 'apc.enabled=1',
- 'apc.cache_by_default=1',
- 'apc.enable_cli=1',
- 'session.gc_probability=0',
-);
-
-server_start($file, $args);
-
-$sid = md5(uniqid("call me maybe", true));
-for ($i = 0; $i < 10; $i++) {
- $send = "GET / HTTP/1.1\n" .
- "Host: " . PHP_CLI_SERVER_HOSTNAME . "\n" .
- "Cookie: PHPSESSID=$sid;" .
- "\r\n\r\n";
- for ($j = 0; $j < $num_servers; $j++) {
- run_test(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT+$j, $send);
- }
-}
-echo 'done';
---EXPECT--
-no session yet, first run
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-A Object
-(
- [b] => B Object
- (
- [var] => 1
- )
-
-)
-done
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 @@
-