pkoutsogiannis / ucache
A shared-memory key-value cache for PHP 7.2 to 8.5 on Windows x64 (IIS, FastCGI).
Package info
github.com/pkoutsogiannis/ucache
Language:C
Type:php-ext
Ext name:ext-ucache
pkg:composer/pkoutsogiannis/ucache
Requires
- php: >=7.2 <8.6
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-01 15:38:47 UTC
README
UCache is the user cache of WinCache (Microsoft's Windows Cache Extension for PHP, 2.0.0.8) as a PHP extension of
its own, for PHP 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5 on Windows x64. Extension ucache, file
php_ucache.dll, version 1.1.0 (what each version changed: RELEASE-NOTES.md). License: see
LICENSE (Microsoft's BSD license, with Datasteam's copyright added).
Table of Contents
- Introduction
- Installing/Configuring
- How the Cache Works
- The Scavenger
- UCache Functions
- ucache_add - Adds a variable in the user cache only if it does not already exist in the cache
- ucache_cas - Compares the variable with an old value and assigns a new value to it
- ucache_clear - Deletes the entire content of the user cache
- ucache_dec - Decrements the value associated with the key
- ucache_delete - Deletes variables from the user cache
- ucache_exists - Checks if a variable exists in the user cache
- ucache_get - Gets a variable stored in the user cache
- ucache_inc - Increments the value associated with the key
- ucache_info - Retrieves information about data stored in the user cache
- ucache_meminfo - Retrieves information about user cache memory usage
- ucache_set - Adds a variable in the user cache and overwrites a variable if it already exists in the cache
- Building for Windows
- Migrating from WinCache
Introduction
UCache keeps PHP values in a shared memory segment, so that all the PHP processes of the same IIS application pool, or more generally with the same parent process (for example the php-cgi.exe processes started by one FastCGI host), use the same cache: a value that one request stores can be read by the later requests of every process of the pool, until it expires or is deleted.
UCache started as the user cache of WinCache 2.0.0.8, extracted and renamed, without the file cache, resolve-path
cache, file change notification, session handler, wincache_lock()/wincache_unlock(), PHP function rerouting and
ETW tracing. The functions take the same arguments and return the same values as the wincache_ucache_* functions of
WinCache 2.0.0.8, apart from bug fixes and a few additions, such as the scavenger keys and the offset and limit
parameters of ucache_info() (see Migrating from WinCache). The highlights of each version
(the details are in RELEASE-NOTES.md):
- 1.0.0: WinCache's user cache under new names, for PHP 7.2 only, with three memory-safety fixes.
- 1.0.1: PHP 7.3 to 8.5; a scavenger that runs at the end of requests and can evict when the cache is full (the
ucache.gc.*settings); values stored without holding the cache lock;ucache.prefix; fixes of many bugs kept from WinCache. - 1.1.0: one hash bucket per 512 bytes of the cache instead of one per 32 to 64 KB (
ucache.bucketbytes), so a lookup takes about 1 microsecond however full the cache is; a cache that stays sound when IIS kills a process in the middle of an update; aucache_clear()that no longer holds up the pool; small arrays stored in a third of the memory or less; settings and pool names read as written.
Installing/Configuring
Requirements
The extension is supported on the following configurations:
Windows:
- Windows x64. On a web server, PHP must run through FastCGI (php-cgi.exe), as IIS runs it: the processes of one IIS
application pool share a cache. The CLI and the built-in web server (
php -S) work too.
PHP:
- PHP 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 or 8.5, non-thread-safe (NTS) x64 build. There is no thread-safe (ZTS) build.
Note: Each PHP version needs the DLL built for it (see Building for Windows), and processes of different PHP versions never share a cache. A DLL loads in any NTS x64 PHP of the version it was built for that uses the same compiler (the windows.php.net builds of that version), whatever the patch release: PHP checks the build ID (
PHP Extension Buildinphpinfo(), for exampleAPI20240924,NTS,VS17).
Installation
UCache is not a PECL package: build php_ucache.dll from the sources (see Building for Windows), or take a DLL built for the PHP version in use.
To install and enable the extension, follow these steps:
- Copy the php_ucache.dll file into the PHP extensions folder: the "ext" folder next to php.exe, or the folder set
by
extension_dirin php.ini. For example:C:\php84\ext. - Using a text editor, open the php.ini file, which is usually located in the folder of php.exe. For example:
C:\php84\php.ini. - Add the following line to the php.ini file:
extension=php_ucache.dll. Add theucache.*settings to change, if any (see Runtime Configuration). - Save and close the php.ini file.
- Recycle the IIS application pools for PHP to pick up the configuration changes, or restart the web server.
- To check that the extension has been enabled, create a file called phpinfo.php with PHP code that calls the
phpinfo()function, save it in the root folder of an IIS web site that uses PHP, then open a browser and make a request tohttp://localhost/phpinfo.php. Search within the returned web page for a section calleducache. If the extension is enabled, then thephpinfo()output lists theucache.*settings, and itsUser cacheline saysenabled(unavailablemeans that the cache could not be created: see Startup warnings). From the command line,php -mlistsucache.
Note: Do not forget to remove the phpinfo.php file from the web site's root folder after verifying that the extension has been enabled.
Note: To replace php_ucache.dll, stop the application pool or the web server completely, replace the DLL, and then start it. Old and new PHP processes must not run at the same time. The names of the cache's shared memory and synchronization objects include the PHP and UCache versions: processes of the same UCache version (a rebuilt DLL) would share one cache, although the two builds can store values differently (an old process can crash on arrays that a new one stored), and processes of different versions would each use a cache of their own, so that neither sees what the other stores. An overlapped recycle, the IIS default, starts the new processes while the old ones are still running. For the same reason, do not rename a loaded DLL or switch PHP folders under a running pool, unless
ucache.namesaltchanges in its first 8 characters (the only ones used) at the same time, which gives the new processes a cache of their own.
Note: Under the CLI, the cache is only created when
ucache.enablecli=1, in php.ini or on the command line:php -d ucache.enablecli=1 script.php.
Runtime Configuration
The behaviour of these functions is affected by settings in php.ini.
The following table lists and explains the configuration settings provided by the UCache extension:
| Name | Default | Minimum | Maximum | Changeable | Changelog |
|---|---|---|---|---|---|
ucache.enabled |
"1" | "0" | "1" | PHP_INI_ALL | Available as of UCache 1.0.0 |
ucache.enablecli |
"0" | "0" | "1" | PHP_INI_SYSTEM | Available as of UCache 1.0.0 |
ucache.size |
"8" | "5" | "930" | PHP_INI_SYSTEM | Available as of UCache 1.0.0 |
ucache.bucketbytes |
"512" | "64" | "65536" | PHP_INI_SYSTEM | Available as of UCache 1.1.0 |
ucache.namesalt |
NULL | PHP_INI_SYSTEM | Available as of UCache 1.0.0 | ||
ucache.apppoolid |
NULL | PHP_INI_SYSTEM | Available as of UCache 1.0.0 | ||
ucache.filemapdir |
NULL | PHP_INI_SYSTEM | Available as of UCache 1.0.0 | ||
ucache.debuglevel |
"0" | "0" | "501" | PHP_INI_ALL | Available as of UCache 1.0.0 |
ucache.gc.threshold |
"0" | "0" | "100" | PHP_INI_SYSTEM | Available as of UCache 1.0.1 |
ucache.gc.interval |
"10" | "0" | PHP_INI_SYSTEM | Available as of UCache 1.0.1 | |
ucache.gc.step |
"0" | "0" | PHP_INI_SYSTEM | Available as of UCache 1.0.1 | |
ucache.gc.ttl |
"0" | "0" | PHP_INI_SYSTEM | Available as of UCache 1.0.1 | |
ucache.gc.evict |
"0" | "0" | "100" | PHP_INI_SYSTEM | Available as of UCache 1.0.1 |
ucache.prefix |
"ucache_" | PHP_INI_SYSTEM | Available as of UCache 1.0.1 |
For further details and definitions of the PHP_INI_* modes, see
Where a configuration setting may be set. The
PHP_INI_SYSTEM settings come from php.ini, or from -d on the command line of PHP, when its process starts:
ini_set() returns false for them, and .user.ini files cannot set them. Only ucache.enabled and
ucache.debuglevel can be changed by a script.
Here's a short explanation of the configuration directives.
ucache.enabled bool
Enables or disables the user cache functionality. With 0, all functions except ucache_info() and ucache_meminfo()
return false, and the end of the request does not run the scavenger. Their arguments are still checked first: invalid
arguments throw ArgumentCountError or TypeError even then, or raise a warning on PHP 7 without strict_types. The
cache is still created at startup, so a script can switch it back on with ini_set().
ucache.enablecli bool
Enables or disables the user cache when PHP runs from the command line (CLI). Without it, the cache is not created under the CLI, and the functions fail there.
ucache.size int
Defines the size of the shared memory segment of the user cache, in megabytes: a number of megabytes (64), or a size
with a K, M or G suffix in any case, which a B may follow (64M, 64MB, 64 MB, 1G, 65536K), always in
decimal (064 is 64). K is rounded up to whole megabytes (100000K is 98). A size outside 5..930 is clamped to the
nearest limit, with a startup warning. Digits followed by other text (64MiB) are taken as megabytes, with a startup
warning, and any other value (abc, -1, 0x40) gives the default, 8 MB, with a startup warning; so does an empty
value, without one. ini_get() and phpinfo() show the value as written, ucache_meminfo() the size of the cache
(see Startup warnings).
ucache.bucketbytes int
Defines the size of the hash table of the cache: one hash bucket per this many bytes of ucache.size, 64 to 65536
(the table has the largest prime number of buckets up to that, which ucache_info() reports as hash_bucket_count).
Every key is looked up in the chain of entries of its bucket. Each bucket takes 8 bytes of the cache: with the default,
512, the buckets take 1.6% of it, and the chains of a cache full of small entries (about 200 bytes each) hold 2 to 3
entries. A smaller value shortens the chains at the cost of memory (12.5% of the cache at 64) and of the time of what
goes through the whole table: at 64, a full listing of ucache_info() of a nearly empty 930 MB cache took 7 ms instead
of 1, and a ucache_clear() 11 ms instead of 2. A larger one gives the memory back, but the chains of small entries
grow (about 20 entries at 4096, 300 at 65536): every lookup goes through its chain, and the full pass of a full cache
through all of them, more slowly (71 ms instead of 21 for 128 MB of small entries, at 4096 as at 65536): keep larger
values for a cache of large values (see Tuning). A value outside 64..65536 is clamped to the nearest limit,
and a value that is not a number of bytes (4K) gives the default, each with a startup warning; an empty value gives
the default without one. Only the process that creates the cache uses it: a cache keeps its table for as long as it
lives, as it keeps its size (see Settings per IIS application pool).
ucache.gc.step counts buckets of this table.
ucache.namesalt string
Defines a string added to the names of the shared memory and synchronization objects, to keep apart caches that
would otherwise be shared. Only the first 8 characters are used: ini_get(), phpinfo() and ucache.php show the
whole value, but two salts that differ only after the 8th character give the same cache. An empty value is the same
as not setting it.
ucache.apppoolid string
Defines the name that decides which processes share a cache, instead of the parent process id. If not set, the
APP_POOL_ID environment variable is used when IIS sets it; an empty value (ucache.apppoolid=, or none, off,
no, false or null, which PHP reads as empty) is the same as not setting it. Names that differ only in the case of
the letters A to Z are the same name; processes with different names do not share a cache. Names are compared as
Unicode text, so ucache.apppoolid=X and APP_POOL_ID=X give the same cache: APP_POOL_ID is read as Unicode, and
the setting as UTF-8 when it is valid UTF-8 (a php.ini saved in UTF-8), otherwise as text of the ANSI code page (an
ANSI php.ini, or -d, whose argument PHP gets in that code page, so that characters outside it cannot be given that
way).
ucache.filemapdir string
Defines the folder of a file that backs the shared memory instead of the paging file. Only used for the cache of an
IIS application pool: when ucache.apppoolid or APP_POOL_ID groups the processes and names an application pool,
whose account (IIS APPPOOL\<name>) is given access to the file. The caches of processes grouped by their parent
process stay in the paging file; so do those of other pool ids, but only once their file could be created in the folder
(it is then deleted, since no IIS APPPOOL account of that name can be given access to it). An empty value is the same as
not setting it. If the file cannot be created or opened in the folder (it does not exist, or the account of the process
cannot write to it), the cache is not created, whatever the pool id, with a startup warning that names the setting.
ucache.debuglevel int
Enables a debug trace written with OutputDebugString: 0 (off), 101, 201, 301, 401 or 501 (the most detailed). Other values are ignored.
ucache.gc.threshold int
Defines a free memory percentage, 0 to 100 (negative values act as 0, higher values as 100). When > 0, writes also run scavenger steps while free memory is at or below it, one a second at most, more often while the steps find entries to remove, and the steps at the end of requests run only then. 0: writes do not run steps. See The Scavenger.
ucache.gc.interval int
Defines the seconds between the scavenger steps at the end of requests. 0 turns them off. See The Scavenger.
ucache.gc.step int
Defines the hash buckets that a scavenger step goes through. 0: automatic, at least 16, so that the steps at the end of requests go through the whole table in at most about 5 minutes. See The Scavenger.
ucache.gc.ttl int
Defines, in seconds, how long an entry can go unused. When > 0, the scavenger also removes the entries not used for this long, whatever their ttl. 0: the scavenger removes expired entries only. See The Scavenger.
ucache.gc.evict int
Defines a free memory percentage, 0 to 100 (higher values act as 100). When > 0 and ucache_set() or ucache_add()
finds the cache full, the entries not used for the longest time are evicted until this percentage of the cache is free,
or more if that is not enough for the value. A value larger than the empty cache (counted without the cache's own
overheads, see A full cache) fails at once and evicts nothing. 0: no eviction. See
The Scavenger.
ucache.prefix string
Defines the prefix of the function names, in place of ucache_, for code that already has functions of these names:
with ucache.prefix=my_cache_, the functions are my_cache_get(), my_cache_set(), ... and ucache_get() and the
others do not exist. The prefix may contain letters, digits, underscores and bytes 0x80 to 0xff, at most 64 of them,
and must not start with a digit; empty gives get(), set(), .... When the prefix is not valid, or makes a name that
another extension already uses (date_ would make date_add()), PHP starts with a warning and without any of the
functions. This manual names the functions with the default prefix. ucache.php follows the setting.
Startup warnings
UCache raises a startup warning (E_CORE_WARNING) when it cannot use a setting as written (ucache.size,
ucache.bucketbytes, ucache.prefix) and when it cannot create the cache. PHP writes startup warnings to its error log (the file of
error_log, or else the error output of the process) and, on Windows, to the Application event log, with the source
PHP-<version> (for example PHP-8.4.14) and the event ID 4: PHP 7 always does, PHP 8 when log_errors is on (as in
php.ini-production and php.ini-development) or display_startup_errors is off. Event Viewer says that the description
of the event cannot be found, and shows the warning under "the following information was included with the event". With
display_startup_errors on, PHP also displays it (the CLI prints it). Every PHP process raises the warning again when
it starts, and IIS starts new php-cgi processes regularly: fix the setting rather than ignore the warning. The
ucache.size, ucache.bucketbytes and cache warnings are not raised under the CLI without ucache.enablecli=1, where
no cache is created; the ucache.prefix warning is.
When the cache cannot be created, PHP runs without it: the functions return false (ucache_meminfo() returns null),
and the User cache line of phpinfo() says unavailable. The warning gives the UCache error and, where it applies,
the Windows error of the step that failed, and names the likely cause:
- the file of the cache could not be created or opened in
ucache.filemapdir: the folder does not exist (Windows error 3), or the pool's account cannot write to it, or a file of another account is in the way (Windows error 5); - a process that shares the cache kept its initialization lock for 30 seconds (Windows error 258, a timeout);
- access to the shared memory or locks of the cache was denied (Windows error 5): processes of another account may use
the same
ucache.apppoolid(orAPP_POOL_ID) anducache.namesalt; - the paging file has no room for the cache (Windows error 1455, the system commit limit): a cache in the paging file
takes its whole
ucache.sizefrom the commit limit when it is created.
Settings per IIS application pool
Each IIS application pool runs php-cgi processes of its own, and they share a cache of their own (see
How the Cache Works). To give a pool its own ucache.* settings, give its sites a FastCGI
application of their own. IIS tells FastCGI applications apart by the program and its arguments, and php-cgi takes -d
arguments. In applicationHost.config:
<fastCgi> <application fullPath="C:\php84\php-cgi.exe" arguments="-d ucache.gc.interval=10 -d ucache.gc.step=1000 -d ucache.gc.ttl=84000" /> <application fullPath="C:\php84\php-cgi.exe" arguments="-d ucache.gc.evict=10" /> </fastCgi>
The PHP handler of each site then names its application, as fullPath|arguments, exactly:
<handlers> <remove name="PHP_via_FastCGI" /> <add name="PHP_via_FastCGI" path="*.php" verb="*" modules="FastCgiModule" resourceType="Either" scriptProcessor="C:\php84\php-cgi.exe|-d ucache.gc.interval=10 -d ucache.gc.step=1000 -d ucache.gc.ttl=84000" /> </handlers>
Instead of -d arguments, the environmentVariables of a FastCGI application can set PHP_INI_SCAN_DIR (the
folders, separated by ;, of extra .ini files read after php.ini) or PHPRC (the folder of a php.ini of its own).
The application still needs arguments that differ from those of the other applications of the same php-cgi.exe.
The processes that share a cache must use the same ucache.* settings. Two sites of one pool that use different FastCGI
applications still share the pool's cache: give them different ucache.namesalt values if their settings differ.
php-cgi reads the settings when it starts: after changing them, recycle the pool. After changing ucache.size or
ucache.bucketbytes, stop the pool and, once its php-cgi processes have exited, start it again instead: an overlapped
recycle (the IIS default) starts the new processes while the old ones still run, and the new processes would open the
cache of the old ones and keep its old size and hash table, also after the old ones have exited: a cache keeps the size
and the table it was created with for as long as any process of the pool has it open, which under steady traffic can be
until the pool is stopped (see Installation).
phpinfo() on a site shows the settings its processes use.
Resource Types
This extension has no resource types defined.
Predefined Constants
This extension has no constants defined.
UCache Statistics Script
The sources of UCache include a PHP script, ucache.php (WinCache's wincache.php for UCache), that can be used to
obtain cache information and statistics: the server and cache settings, the memory use, the hit and miss counts, what
the scavenger removed and evicted and when it last ran, the locks found abandoned (total_abandoned_lock_count), the
list of entries (250 per page, with links to the other pages) and the value stored under a key; with authentication on
and a password other than the default, it can also clear the whole cache.
To use ucache.php, copy it into a root folder of a web site that runs with the extension loaded, or into any
subfolder, preferably one that only administrators can reach. To protect the script, open it in any text editor and
replace the values of the USERNAME and PASSWORD constants: both default to ucache, and with its own
authentication the page refuses to work while the password is ucache. If any other IIS authentication is enabled on
the server, then follow the instructions in the comments. Nobody types the password then, but it is still the only
secret behind the Clear All button: set PASSWORD to a long random value (32 hex characters, for example), and change
USERNAME too, because requests that reach the page without IIS authentication can still log in with the two:
Example #1 Authentication configuration for ucache.php
<?php /** * ======================== CONFIGURATION SETTINGS ============================== * If you do not want to use authentication for this page, set USE_AUTHENTICATION to 0. * If you use authentication then replace the default username and password. * With IIS authentication (see below) nobody types them, but PASSWORD is still the * only secret behind the Clear All button: make it a long random value (32 hex * characters, for example), and change USERNAME too, because requests that reach * this page without IIS authentication can still log in with the two. */ define('USE_AUTHENTICATION', 1); define('USERNAME', 'ucache'); define('PASSWORD', 'ucache'); /** * The Basic PHP authentication will work only when IIS is configured to support * Anonymous Authentication' and nothing else. If IIS is configured to support/use * any other kind of authentication like Basic/Negotiate/Digest etc, this will not work. * In that case use the array below to define the names of users in your * domain/network/workgroup which you want to grant access to. */ $user_allowed = array('DOMAIN\user1', 'DOMAIN\user2', 'DOMAIN\user3'); /** * If the array contains string 'all', then all the users authenticated by IIS * will have access to the page. Uncomment the below line and comment above line * to grant access to all users who gets authenticated by IIS. */ /* $user_allowed = array('all'); */ /** ===================== END OF CONFIGURATION SETTINGS ========================== */ ?>
With USE_AUTHENTICATION set to 0 there is no authentication (any other value turns it on), and the page no longer
offers to clear the cache; it still shows values, so anyone who can reach it can read everything in the cache.
The Clear All button only works from the page itself: it sends back a token that the page makes from PASSWORD, from
a random value that it keeps in a SameSite=Strict cookie and from its own path, so the browser has to accept that
cookie. Another site cannot read the token; another host of the same site, which can set the cookie, cannot make the
token without the password; and a copy of the page elsewhere does not accept it, even with the same password. So the
button needs a password other than ucache and other than an empty one, with IIS authentication too, where the page
does not ask for the password: until it is changed, the page shows a note in place of the button. The page also
refuses to clear the cache when the browser says, in its Sec-Fetch-Site or Origin header, that the form comes from
another origin (another site, host or port; behind a reverse proxy, the proxy must pass on the Host header that the
browser sent), it tells the browser and proxies not to keep a copy of it (Cache-Control: no-store), and it refuses
to be shown inside a frame.
Note: Always protect the ucache.php script by using either the built-in authentication or the server's authentication mechanism. Leaving this script unprotected may compromise the security of your web application and web server.
How the Cache Works
Sharing
Processes share a cache when they have the same ucache.apppoolid (default: the APP_POOL_ID environment variable
set by IIS) or, without one, the same parent process id, and the same ucache.namesalt. Processes with different
application pool ids do not share a cache, nor does a process with an application pool id share one with processes
grouped by their parent process. Only processes of the same Windows session share a cache, since the names of its
shared memory and synchronization objects have no Global\ prefix: IIS runs every application pool in session 0, so
a CLI script run in a Remote Desktop session does not see the cache of a pool. There is no private per-process cache.
The cache is not persistent: it starts empty when the first process of a group creates it, and lasts while a process
of the group has it open. A process that starts while the last one is still closing it opens that cache, with its
entries and at its size.
Values
Values are copied into shared memory on write and copied back on read. Scalars, strings, null and arrays (including
nested arrays and references between their elements) are stored as they are; objects are serialized on write and
unserialized on read; resources cannot be stored. $GLOBALS, as a value or as the array of the array forms, is
stored as the global variables it holds, without those that are not set, as serialize() takes them (before PHP 8.1
its entries point to the variables). Before PHP 8.1, array_merge(), array_slice(), array_reverse(),
array_chunk() and the like copy those entries as they are into the arrays they make of $GLOBALS: such an array is
stored the same way, with the values the variables have when ucache_set() is called, but a list of those entries
(array_values($GLOBALS), or array_chunk($GLOBALS, 10) without its keys) is refused: ucache_set() and
ucache_add() return false without a warning, as for a resource inside the value. From PHP 8.1 on, $GLOBALS is a
copy, and these are ordinary arrays.
An entry holds the value as it was when ucache_set() was called: changes that serializing an object (__sleep(),
__serialize()) makes to the rest of the value are not stored, and each object is stored as it is when it is
serialized. An object that occurs more than once in the value's arrays and references is serialized once, but reads
back as separate copies (one that another object of the value also holds is serialized again as part of that object).
A value that cannot be serialized (a Closure, an object of an anonymous class, a Generator, an object whose
__sleep() or __serialize() throws) is not stored: the exception propagates out of ucache_set() or
ucache_add(). A value whose arrays and references are nested more than 10,000 levels deep (each array and each
reference is one level, not counting what objects hold) is not stored either: ucache_set() and ucache_add()
return false without a warning, as for a resource inside the value. On PHP 8.1 and later, so is a value nested too
deeply for the C stack that is left, and ucache_get() of such an entry returns false. This matters in a Fiber
(PHP 8.1 and later), whose stack is 2 MB by default (fiber.stack_size): a few thousand levels can be too many
there. What objects hold is serialized by PHP itself, which checks the stack only from PHP 8.3 on: on 8.1 and 8.2,
inside a Fiber with that stack, an object whose properties nest arrays more than about 1,250 levels deep ends the
process, as serialize() of it does (8.3 and later throw an Error, which propagates out of ucache_set()).
An object that cannot be unserialized when it is read makes the read fail, as it makes unserialize() fail:
ucache_get() returns false with PHP's warning, which says why, and the entry stays for the processes that can read
it. This happens to an enum or an enum case that the reading process does not define (PHP 8.1 and later), to an
object of a class that changed since it was stored (one that implements Serializable now, for example), and, on
PHP 7.4 and later, to an object nested deeper than unserialize_max_depth (4096 levels by default). An object of a
class that is not defined comes back as __PHP_Incomplete_Class, as from unserialize().
Keys
Keys are strings; other scalar keys are converted to strings. Keys are case sensitive. A key ends at its first NUL
byte ("a\0b" and "a\0c" are both the key "a"). ucache_set() and ucache_add() store keys of at most 4096
characters, and reject an empty key only in their single-key form: the array form does not check, so '' can be
stored that way (and then read with ucache_get('')).
Expiry
An entry stored with a ttl can be read for up to ttl + 1 seconds after it was stored, and reads do not extend it.
An expired entry keeps its memory until it is removed: see The Scavenger for the exact rules.
Serialization and the cache lock
Values are copied in without holding the cache lock, and objects are unserialized once it has been released, so
__sleep(), __serialize(), __wakeup(), __unserialize(), Serializable::serialize() and
Serializable::unserialize() can call the UCache functions and do not hold up the other processes. Destructors do not
run under the lock either: the cache functions free PHP values only once it has been released (a ucache_get() that
fails frees what it had copied then), so PHP's garbage collector, which can call destructors when a value is freed,
never runs under it. Nor does a fatal error in a cache function leave the cross-process lock held: the other processes
of the pool go on. On PHP builds that end the process at memory_limit instead of raising the fatal error, such as the
CVE-patched PHP 7.2, a ucache_get() that reaches memory_limit while it copies a value out ends the process while it
holds the cache lock: the next process takes the lock, which the end of its owner released, and counts it in
total_abandoned_lock_count (a copy out changes nothing in the cache, so nothing is undone). UCache serializes and
unserializes each object as a top-level serialize() or unserialize() would, independently of any serialize() or
unserialize() that the caller is in the middle of.
Every call takes the cache lock for its lookup, so processes that call the cache back to back take turns and together
make no more calls than one process alone (on PHP 7.2, 4 or 8 processes calling ucache_get() back to back made about a
third as many calls, together, as one), while at a thousand calls a second the lock is busy about 0.1% of the time.
memory_limit
ucache_set() and ucache_add() serialize every object of the value before they copy anything into the cache. Each
serialization is built in request memory and then moved out of PHP's heap, so only the one being built counts
against PHP's own memory_limit: an object whose serialization alone does not fit still raises PHP's memory_limit
error (which ends the process on PHP builds that exit at memory_limit). The serializations kept until the value is
copied are counted against memory_limit by UCache itself: on every PHP version, if the memory in use plus those
serializations would exceed memory_limit, the value is not stored, and the call returns false without an error (in
the array forms, its key is among the failures).
On PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12 and later, reaching memory_limit inside some internal functions, UCache's among
them, raises the fatal error only when the function returns, and the process is killed if it reaches a hard limit (by
default twice memory_limit) first. ucache_get(), ucache_info(), ucache_set() and ucache_add() therefore watch
memory_limit while they copy: when it is exceeded they stop and fail, and PHP then raises the fatal error. In their
single-key form, ucache_set() and ucache_add() store nothing if memory_limit was exceeded at any time during the
call, even briefly (unless the script had lowered memory_limit below a peak it had already reached). Their array
forms stop at the value during which memory_limit was exceeded: the keys before it are stored, that one and the ones
after it are not. The checks run between allocations, though: one string, or the serialization of one object, too big
to fit under the hard limit still gets the process killed (exit code 137, without a fatal error or shutdown
functions). Older versions raise the fatal error at once.
The full listing of ucache_info() copies every entry into request memory, about 800 bytes for an entry with a short
key: list a large cache a part at a time, with offset and limit (see ucache_info).
The Scavenger
The scavenger removes the entries that have expired and, if the settings ask for it, the entries that have not been used for a while. It works in steps, at the end of requests and on writes, and in a full pass when a value does not fit in the cache. Calls that look up a key also remove the expired entries they come across.
Expiry and use
An entry stored with a ttl > 0 expires once more than ttl whole seconds have passed since it was stored: it can be
read for up to ttl + 1 seconds, and reads do not extend it. An entry stored with ttl 0 never expires. An expired
entry can no longer be read, but it keeps its memory until a call, a step or a full pass removes it.
An entry is used when ucache_get() reads it, when ucache_set() or ucache_add() stores it, and when
ucache_inc(), ucache_dec() or ucache_cas() finds it, whether or not they change it. ucache_exists() and
ucache_info() are not uses. The time since the last use matters only for ucache.gc.ttl and for eviction; it does
not change when an entry expires.
Removal on contact
Every call that looks up a key (ucache_get(), ucache_exists(), ucache_set(), ucache_add(), ucache_delete(),
ucache_inc(), ucache_dec(), ucache_cas(), and each key of their array forms) removes the expired entries it passes
in the key's hash bucket: the key's own expired entry, and possibly expired entries of other keys that share the bucket.
So storing a key again after it expired leaves a single entry, and ucache_delete() of an expired key returns false
(the key is missing) and removes the entry. This removal on contact happens whatever the ucache.gc.* settings are, and
concerns expired entries only: the entries that ucache.gc.ttl targets are left to the scavenger. It also removes the
entries of the bucket that a ucache_clear() has not freed yet. ucache_info() and ucache_meminfo() never remove
anything: the full ucache_info() listing shows the expired entries that nothing has removed yet.
Steps
A scavenger step goes through a number of hash buckets, starting where the previous step stopped, and removes:
- the expired entries;
- when
ucache.gc.ttl> 0, also the entries not used forucache.gc.ttlseconds or more, whatever their ttl (ttl 0 included). This does not make them expire: until a step reaches such an entry, it can still be read, and reading it is a use; - the entries that a
ucache_clear()has not freed yet (see Freeing memory), as many as one of its batches, without counting them.
Steps run:
- At the end of requests: once
ucache.gc.intervalseconds (10 by default) have passed since the cache was created or since the last step, in any process that shares the cache, the first request to end runs one step. The requests that end in between do not take the cache lock.ucache.gc.interval=0turns these steps off, and a request that ends withucache.enabled=0does not run one. - On writes, when
ucache.gc.threshold> 0: while free memory is at or belowucache.gc.thresholdpercent of the cache, aucache_delete(),ucache_inc(),ucache_dec()orucache_cas(), or aucache_set()orucache_add()that gets as far as storing its value (each key of the array forms counts), runs one step once a second has passed since the last step, in any process that shares the cache; a tenth of a second after a step that removed entries; and a hundredth of a second after a step that removed entries while at most a quarter ofucache.gc.thresholdpercent was free. So, however many writes there are, the steps on writes hold the cache lock about 1 ms a second while they find nothing to remove, about 1% of the time while they find entries, and up to about 10% while free memory is that low. The steps at the end of requests also run only while free memory is at or belowucache.gc.threshold. A step skipped because more memory is free still counts as a step for these times and forucache.gc.interval, so the other writes and requests do not look at the free memory either.
A step goes through ucache.gc.step buckets, at most the whole table. The default, 0, takes the larger of 16 and
buckets x ucache.gc.interval / 300, rounded up (buckets / 300 when ucache.gc.interval is 0), so that the steps at
the end of requests go through the whole table in at most about 5 minutes while requests keep coming, and the steps
on writes, a second apart, ucache.gc.interval times faster (in about 5 minutes when ucache.gc.interval is 0, while
writes keep coming). The table has a prime number of buckets, the largest prime up to one bucket per
ucache.bucketbytes bytes of ucache.size (512 by default): 16381 for the default 8 MB (547 buckets a step), 1904621
for 930 MB (63488 buckets a step). A step also stops after about 1 ms of work under the cache lock, and the next step
goes on from there: the clock is read every 256 buckets of 512 bytes (in proportion with other sizes: every 2 buckets of
65536 bytes, every 2048 of 64), and after each bucket where the step removed entries, since each removal beyond the
first 32 frees its memory under the lock (see Freeing memory). So a step holds the cache lock for
about 1 ms at most, but going through a table that holds many entries can take more than 5 minutes.
A full cache
When ucache_set() or ucache_add() runs out of shared memory for a value (in the array forms, for any of the
keys), it does not fail at once, unless the value takes more memory than the empty cache holds, the memory_free of
ucache_meminfo() with no entries (8,253,952 bytes for the default 8 MB): such a call fails at once, before it takes
any shared memory, so nothing is removed or evicted for it and the writes of the other processes do not run out of
memory because of it. Otherwise it takes the cache lock and runs a full pass over the whole table, without the 1 ms
limit, that removes what a step removes. If ucache.gc.evict is > 0 and less than ucache.gc.evict percent of the
cache is then free, it evicts the entries not used for the longest time, whatever their ttl, until that percentage is
free. It aims at twice that percentage instead, or four times and so on, while that percentage of the cache is less
than the value takes and less than that is free, or while the pass removed nothing and that much is free already: the
value did not fit with that much free, unless memory was freed between the failed write and the pass, which then
evicts more than it needed to. Then it tries to store the value once more. With ucache.gc.evict, a value that still
does not fit makes it evict again, until twice as much is free, then four times and so on, up to the whole cache:
free memory in many small pieces may not hold a value that needs one large block, so such a value, in a cache full
of small entries, can empty most of it. If the value still does not fit, the call fails: it returns false, or the
array form lists the key among the failures. A write that finds that another process ran a full pass meanwhile only
tries again. When a full pass removed nothing (with ucache.gc.evict, the cache had no entry left) and the value
still did not fit, the processes that share the cache skip the pass for a while, at least a second and 20 times as
long as the pass took (at most a minute): their writes that run out of memory meanwhile fail at once (so do the
following keys of an array form), and an entry that expires meanwhile is removed by the next pass. While a
ucache_clear() is freeing the entries of the cache, a write first helps it for up to 0.1 second (see
Freeing memory); one that then runs out of memory helps it for about a millisecond at a time and
tries again, instead of running a full pass, up to 16 times; if the value still does not fit, the call fails.
The memory a value takes is counted before anything is copied: its strings, arrays and references and the
serializations of its objects, each once however often it occurs, but not the cache's own overheads (about 40 bytes
for each block of the allocator, and an entry takes at least 2 blocks, an array 4). So a value that is larger than
the empty cache only through these overheads still runs the pass, and can empty the cache before its call fails.
Before PHP 8.1, an array that array_merge() and the like make of $GLOBALS (see Values) is not counted.
Eviction goes by groups of similar idle time (powers of two of seconds), the longest idle first; within a group,
entries go in the order of the table, so the order is only roughly that of the last use. The free memory is read
again once the entries evicted since the last reading could have freed what was missing, or after 64 of them, so a
pass can evict up to 63 small entries more than it needed to. With ucache.gc.evict=0, the default, nothing is
evicted: entries are removed only by their ttl, ucache.gc.ttl, a delete, a replace or a clear.
Freeing memory
Every removal (a delete, a replace by ucache_set(), a removal on contact, a step) unlinks the entries under the
cache lock and frees their memory right after the lock is released, before the call returns, so freeing a big entry
does not hold up the other processes. That is for up to 32 entries per call; the entries a step removes beyond that
are freed at once, under the lock, and so is everything the full pass of a full cache removes, so that the write
waiting for the memory, and the next process that runs out of it, see what is really free (the full pass frees them
32 entries at a time, taking the lock of the allocator once for each batch). If a process is killed while it frees,
for example by an IIS FastCGI timeout, the memory of the entries it had not freed yet (32 at most) stays lost until
every process of the pool has exited, and so does the rest of an entry it was freeing.
A process can be killed at any instruction, also in the middle of an update of what the processes share: a hash
chain and the count of entries, under the cache lock, or the lists of free blocks, the headers of blocks and the counts
of the memory allocator, under its lock. Each of these updates first saves, in a log kept in the shared memory, what
every field it changes held, and empties the log when it is complete. The next process that takes the lock finds the
log of an update that was cut off and puts those fields back, so that the update did not happen: an entry being
removed stays in its chain, an entry being added is left out (its memory is lost), a ucache_clear() did not clear,
a block being allocated stays free, and a block being freed stays in use, lost with the rest of its entry. The other
processes go on with consistent structures. total_abandoned_lock_count in ucache_info() counts the locks that a
process found abandoned (the process that held one ended without releasing it), whether or not it was in the middle of
an update; a log is also put back when the lock was created again while the memory lived on (a process that starts
while the last process of the pool exits), which is not counted.
ucache_clear() clears the cache at once, under the cache lock: from then on no process sees the entries that were in
it (the calls miss them, the listing and total_item_count leave them out), and an entry stored afterwards is kept.
Then it removes them and frees their memory in batches: each batch takes the cache lock for at most 32 entries (fewer if
their values took 256 KB) or 4096 hash buckets of 512 bytes (in proportion with other ucache.bucketbytes: 32 buckets
of 65536 bytes), and frees its entries once the lock is released, taking the lock of the allocator once for the batch.
So the other processes go on reading and writing meanwhile: a read waits for one batch at
most, or a few milliseconds while several processes free entries at once, and a ucache_set() or ucache_add() first
runs batches itself, for about 0.1 second at most, before it stores its value (each key of the array forms does, so that
an array form of many keys can wait until the entries are all freed). A value stored while entries are still being
freed goes in the hole of one of them and stays there, between free blocks that would otherwise be joined, so that
values larger than the gaps between such values would not fit afterwards, however much of the cache is free: the wait
keeps such values few. ucache_clear() returns once the batches are done. A ucache_clear() while another process is
still freeing the entries of an earlier one clears the cache at once in the same way and returns: that process goes on,
through the whole table again. Meanwhile, a write that runs out of memory helps for about a millisecond at a time (see
A full cache), and so does the end of every request while ucache.gc.interval > 0. Measured on PHP 7.2
with a full cache of 512 MB (2.5 million entries of about 200 bytes): the clear took 1.5 to 2.1 seconds; meanwhile a
read in another process waited at most 0.6 ms; a write of 10 bytes, about 0.1 second at most; a write of 1000 bytes,
which needed memory, at most 0.12 second, and such writes failed until the freed memory formed blocks large enough for
them (7 of the 27 made during the clear); and a second ucache_clear() took less than 0.1 ms. With 4 processes storing
small values during the clear of a full cache of 512 MB (1.4 million entries), a read in another process waited about
4 ms at most, and the clear left the memory in about 70 free blocks, where a value of 8 MB fitted. If the process that
frees the entries is killed, the cache stays cleared, and the entries left are freed by those helpers, by the lookups
and steps that pass them, or by the next ucache_clear(), which takes over once that process has done nothing for a
second.
What ucache_info() reports
ucache_info() shows what the scavenger did: last_scavenge_seconds and last_scavenge_count for the last step or
full pass, total_scavenged_count for all the entries that steps and full passes removed, evicted entries included,
and total_evicted_count for the evicted ones. A step that is skipped (see Steps) is not counted, and
neither are removals on contact, deletes, replaces and clears.
Tuning
- To go through the table faster or slower than the automatic step does, set
ucache.gc.step: going through it then takes about buckets /ucache.gc.stepxucache.gc.intervalseconds with the steps at the end of requests, and about buckets /ucache.gc.stepseconds with the steps on writes (a tenth of that while they find entries to remove), more if steps reach the 1 ms limit. A fixed value does not grow with the cache: 1000 buckets every 10 seconds take about 5 hours to go through the 1904621 buckets of a 930 MB cache, and steps on writes of 16 buckets take about 17 minutes for the 16381 buckets of 8 MB and about 18 hours for the 1048573 of 512 MB. - With the defaults, an entry without a ttl stays until it is deleted, replaced or cleared. If keys go out of use
(one per user, per page or per day, for example), set
ucache.gc.ttlabove the longest time between two uses of an entry that should stay, for example 86400 (a day). - If writes fail because the cache is full, give it a larger
ucache.size, or setucache.gc.evict(for example 10) instead of callingucache_clear()from PHP when a write fails: eviction removes the entries not used for the longest time,ucache_clear()removes everything, for every process of the pool. A very low value frees little, so full passes come often, and each one holds the cache lock while it goes through the whole table. Atotal_evicted_countthat keeps growing means that the cache is too small for what is stored in it. A value larger than the empty cache (see A full cache) fails whatever the settings: only a largerucache.sizestores it. ucache.gc.thresholdmakes the scavenger work harder once the cache is that full: with the defaultucache.gc.interval, the steps on writes go through the table ten times faster than the steps at the end of requests, for about 1 ms of cache lock a second, and faster again while they find entries to remove. It only helps where entries expire or go unused (ucache.gc.ttl): otherwise the steps find nothing to remove. It does not keep up with a mass expiry: steps on writes remove some thousands of entries a second there. Measured on PHP 7.2, with 500,000 expired entries in 128 MB anducache.gc.threshold=50: about 4,000 a second at first and 6,000 later, so that free memory was back above 50% after about 40 seconds, with steps holding the cache lock about 1% of the time (in a fuller cache, more than 20,000 a second while at most a quarter of 50% was free). If new values fill the cache first, the full pass of a full cache removes all the expired entries at once, and every process of the pool waits for it: it held the cache lock for about 0.25 seconds for 635,000 expired entries in 128 MB. Steps on writes cannot prevent that pass then: spread the ttls of entries stored together, or give the cache room for them.ucache.gc.interval=0turns off the steps at the end of requests: expired entries are then removed only on contact, by steps on writes (ucache.gc.threshold) and by the full pass of a full cache. It suits a cache where no entry has a ttl anducache.gc.ttlis 0, since steps have nothing to remove there.ucache.bucketbytestrades memory for speed: around the default it moves little memory either way, and larger buckets soon slow down the lookups of small entries. Measured on PHP 7.2, in a cache of 128 MB filled until a write failed: with small entries (an integer, a key of 40 characters), 128 bytes stored 4.8% fewer entries than the default 512 and read them in 1.07 microseconds instead of 1.19 (a missing key 0.89 instead of 1.04), 4096 stored 1.4% more and read them in 2.0 (2.7), and 65536 1.6% more in 16.9 (31.7), with 316 entries per bucket; the full pass of that full cache took 21 ms at 512 and 71 ms at 4096 and 65536. With arrays of 14 strings (about 1.6 KB serialized), reads took 1.68, 1.68, 1.78 and 2.26 microseconds, and with strings of 1 KB 1.25, 1.23, 1.42 and 3.46.
UCache Functions
Argument names are the ones in the extension's arginfo, except $values and $unused in the array forms of
ucache_set() and ucache_add(), which arginfo calls $key and $value. On PHP 8 the optional parameters have
their defaults in arginfo, so named arguments can skip them: ucache_set($values, ttl: 60),
ucache_inc($key, success: $ok), ucache_info(key: $key), ucache_info(limit: 250). Reflection reports one
required parameter for ucache_set() and ucache_add(), as their array form takes only the array.
Arguments are checked before anything else, whatever the state of the cache. Invalid ones (a wrong type, a missing
argument, an argument to ucache_clear() or ucache_meminfo(), which take none) throw ArgumentCountError or
TypeError. On PHP 7 they do so only when the calling file declares strict_types=1: otherwise they raise a warning
and the function returns false (ucache_meminfo(): null). With ucache.enabled=0, every function except
ucache_info() and ucache_meminfo() then returns false.
ucache_add
(UCache >= 1.0.0)
ucache_add - Adds a variable in the user cache only if it does not already exist in the cache
Description
ucache_add(mixed $key, mixed $value = null, int $ttl = 0): bool|array ucache_add(array $values, mixed $unused = null, int $ttl = 0): bool|array
Adds a variable in the user cache, only if this variable does not already exist in the cache. The added variable
remains in the user cache unless its time to live expires or it is deleted by using the ucache_delete() or
ucache_clear() functions.
Parameters
key
Store the variable using this key name. If a variable with the same key is already present, the function fails and
returns false. key is case sensitive (see Keys). To overwrite the value even if key is present, use the
ucache_set() function instead.
value
Value of a variable to store. Value supports all data types except resources, such as file handles. Objects are
serialized (see Values). Called with the key alone, without a value, the function fails; with named
arguments (PHP 8), an omitted value is null and is stored: ucache_add('k', ttl: 60) stores null.
values
Associative array of keys and values, which adds all the pairs in one call.
unused
Not used in the array form. A resource there still makes the call fail, with a warning.
ttl
Time for the variable to live in the cache in seconds. After the value specified in ttl has passed, the stored
variable can no longer be read, and the scavenger frees it (see The Scavenger). The default value
of 0 means that the variable never expires. A negative ttl makes the call fail. A ttl above 4294967295 (about
136 years) counts as 4294967295.
Return Values
If key is a string, the function returns true on success and false on failure: the key exists, the key is blank or
longer than 4096 characters, no value was given, the value is or holds a resource, is nested too deeply or does not
fit in the cache, its serialized objects would pass memory_limit, or ttl is negative.
If key is an array, the function returns an empty array if all the name => value pairs were added, or else an
array of the names that could not be added, the existing ones included, each with the value -1. An element given by
reference is stored as its value.
Errors/Exceptions
Raises an E_WARNING when key is a string that already exists ("function called with a key which already exists";
in the array form, existing keys are listed among the failures without a warning), for a blank key ("key cannot be
blank string"), a negative ttl ("ttl cannot be less than 0") and a resource given as the value ("value cannot be a
resource"). A key longer than 4096 characters, a resource inside an array value, or a value nested too deeply, fails
without a warning.
If serializing an object of the value throws (a Closure, an object of an anonymous class, a Generator, a __sleep()
or __serialize() that throws), the exception propagates and the value is not stored. In the array form, the pairs
before that element are stored, that one and the ones after it are not.
Examples
Example #1 ucache_add() with key as a string
<?php var_dump(ucache_add('green', 1)); var_dump(ucache_add('green', 2)); var_dump(ucache_get('green')); ?>
The above example will output:
bool(true)
Warning: ucache_add(): function called with a key which already exists in C:\inetpub\wwwroot\example.php on line 3
bool(false)
int(1)
Example #2 ucache_add() with key as an array
<?php $array = array('green' => 1, 'blue' => 2); var_dump(ucache_add($array)); var_dump(ucache_add(array('green' => 3, 'red' => 4))); var_dump(ucache_get(array('green', 'blue', 'red'))); ?>
The above example will output:
array(0) {
}
array(1) {
["green"]=>
int(-1)
}
array(3) {
["green"]=>
int(1)
["blue"]=>
int(2)
["red"]=>
int(4)
}
See Also
- ucache_set() - Adds a variable in the user cache and overwrites a variable if it already exists in the cache
- ucache_get() - Gets a variable stored in the user cache
- ucache_delete() - Deletes variables from the user cache
- ucache_clear() - Deletes the entire content of the user cache
- ucache_exists() - Checks if a variable exists in the user cache
- ucache_meminfo() - Retrieves information about user cache memory usage
- ucache_info() - Retrieves information about data stored in the user cache
- __sleep()
ucache_cas
(UCache >= 1.0.0)
ucache_cas - Compares the variable with an old value and assigns a new value to it
Description
ucache_cas(string $key, int $cvalue, int $nvalue): bool
Compares the variable associated with the key with cvalue and, if it matches, assigns nvalue to it.
Parameters
key
The key that is used to store the variable in the cache. key is case sensitive.
cvalue
The value to which the variable pointed by key is compared. It has to be an integer.
nvalue
The value to be assigned to the variable pointed by key if a match is found. It has to be an integer.
Return Values
Returns true on success or false on failure: the key is missing or has expired, the value is not cvalue, or the
stored value is not an integer. The entry keeps its ttl, and the call counts as a use of the entry
(ucache.gc.ttl), whether or not it changes the value.
Errors/Exceptions
Raises an E_WARNING ("function can only be called for key whose value is long") when the stored value is not an
integer.
Examples
Example #1 Using ucache_cas()
<?php ucache_set('counter', 2922); var_dump(ucache_cas('counter', 2922, 1)); var_dump(ucache_get('counter')); var_dump(ucache_cas('counter', 2922, 5)); ?>
The above example will output:
bool(true)
int(1)
bool(false)
See Also
- ucache_inc() - Increments the value associated with the key
- ucache_dec() - Decrements the value associated with the key
ucache_clear
(UCache >= 1.0.0)
ucache_clear - Deletes the entire content of the user cache
Description
ucache_clear(): bool
Clears/deletes all the values stored in the user cache, for every process that shares the cache. The cache is empty
for every process at once; the memory of the entries is then freed a batch at a time while the other processes use
the cache, and the function returns once the entries are removed (other processes that help may free their last
batches just after), or at once if another process is still freeing the entries of an earlier ucache_clear() (see
Freeing memory).
Parameters
This function has no parameters.
Return Values
Returns true on success or false on failure (the cache is switched off or was not created).
Examples
Example #1 Using ucache_clear()
<?php ucache_set('green', 1); ucache_set('blue', 2); var_dump(ucache_clear()); var_dump(ucache_exists('green')); var_dump(ucache_exists('blue')); ?>
The above example will output:
bool(true)
bool(false)
bool(false)
See Also
- ucache_set() - Adds a variable in the user cache and overwrites a variable if it already exists in the cache
- ucache_add() - Adds a variable in the user cache only if it does not already exist in the cache
- ucache_delete() - Deletes variables from the user cache
- ucache_get() - Gets a variable stored in the user cache
- ucache_exists() - Checks if a variable exists in the user cache
- ucache_meminfo() - Retrieves information about user cache memory usage
- ucache_info() - Retrieves information about data stored in the user cache
ucache_dec
(UCache >= 1.0.0)
ucache_dec - Decrements the value associated with the key
Description
ucache_dec(string $key, int $delta = 1, bool &$success = null): int|false
Decrements the value associated with the key by 1 or as specified by delta.
Parameters
key
The key that was used to store the variable in the cache. key is case sensitive.
delta
The value by which the variable is decremented. It has to be an integer.
success
Will be set to true on success and false on failure. It is left as it was when the cache is switched off
(ucache.enabled=0) or was not created.
Return Values
Returns the decremented value on success, or false if the key is missing or has expired, or if the stored value is
not an integer. The value changes in place: the entry keeps its ttl, and the call counts as a use of the entry
(ucache.gc.ttl). The result wraps around at the limits of the integer range.
Errors/Exceptions
Raises an E_WARNING ("function can only be called for key whose value is long") when the stored value is not an
integer. A missing key fails without a warning.
Examples
Example #1 Using ucache_dec()
<?php ucache_set('counter', 1); var_dump(ucache_dec('counter', 2923, $success)); var_dump($success); ?>
The above example will output:
int(-2922)
bool(true)
See Also
- ucache_inc() - Increments the value associated with the key
- ucache_cas() - Compares the variable with an old value and assigns a new value to it
ucache_delete
(UCache >= 1.0.0)
ucache_delete - Deletes variables from the user cache
Description
ucache_delete(mixed $key): bool|array
Deletes the elements in the user cache pointed by key.
Parameters
key
The key that was used to store the variable in the cache. key is case sensitive. key can be an array of keys.
Return Values
If key is a string, returns true on success or false on failure: the key is missing. An expired key is missing:
the call returns false, and removes the expired entry (see Removal on contact).
If key is an array, returns an array of the keys that were deleted (int keys as strings), but false if the last
key of the array was not deleted, whatever happened to the others. The array passed in is not modified.
Examples
Example #1 ucache_delete() with key as a string
<?php ucache_set('green', 1); var_dump(ucache_delete('green')); var_dump(ucache_exists('green')); ?>
The above example will output:
bool(true)
bool(false)
Example #2 ucache_delete() with key as an array
<?php ucache_set(array('green' => 1, 'blue' => 2, 'cyan' => 3)); var_dump(ucache_delete(array('green', 'blue', 'cyan'))); ?>
The above example will output:
array(3) {
[0]=>
string(5) "green"
[1]=>
string(4) "blue"
[2]=>
string(4) "cyan"
}
Example #3 ucache_delete() with an array whose last key is missing
<?php ucache_set(array('green' => 1, 'blue' => 2)); var_dump(ucache_delete(array('green', 'blue', 'red'))); var_dump(ucache_exists('green')); ?>
The above example will output:
bool(false)
bool(false)
See Also
- ucache_set() - Adds a variable in the user cache and overwrites a variable if it already exists in the cache
- ucache_add() - Adds a variable in the user cache only if it does not already exist in the cache
- ucache_get() - Gets a variable stored in the user cache
- ucache_clear() - Deletes the entire content of the user cache
- ucache_exists() - Checks if a variable exists in the user cache
- ucache_meminfo() - Retrieves information about user cache memory usage
- ucache_info() - Retrieves information about data stored in the user cache
ucache_exists
(UCache >= 1.0.0)
ucache_exists - Checks if a variable exists in the user cache
Description
ucache_exists(string $key): bool
Checks if a variable with the key exists in the user cache or not.
Parameters
key
The key that was used to store the variable in the cache. key is case sensitive.
Return Values
Returns true if the variable with the key exists and has not expired, otherwise returns false. The call does not
count as a use of the entry (ucache.gc.ttl), and removes the expired entry of the key (see
Removal on contact).
Examples
Example #1 Using ucache_exists()
<?php ucache_set('green', 1); var_dump(ucache_exists('green')); var_dump(ucache_exists('red')); ?>
The above example will output:
bool(true)
bool(false)
See Also
- ucache_set() - Adds a variable in the user cache and overwrites a variable if it already exists in the cache
- ucache_add() - Adds a variable in the user cache only if it does not already exist in the cache
- ucache_get() - Gets a variable stored in the user cache
- ucache_delete() - Deletes variables from the user cache
- ucache_clear() - Deletes the entire content of the user cache
- ucache_meminfo() - Retrieves information about user cache memory usage
- ucache_info() - Retrieves information about data stored in the user cache
ucache_get
(UCache >= 1.0.0)
ucache_get - Gets a variable stored in the user cache
Description
ucache_get(mixed $key, bool &$success = null): mixed
Gets a variable stored in the user cache.
Parameters
key
The key that was used to store the variable in the cache. key is case sensitive. key can be an array of keys
(strings or ints). In this case the return value will be an array of the values of the keys that were found. If an
object, or an array containing objects, is returned, then the objects will be unserialized. See
__wakeup() for details on unserializing
objects.
success
Will be set to true on success and false on failure. It is left as it was when the cache is switched off
(ucache.enabled=0) or was not created (CLI without ucache.enablecli=1, or a cache that could not be created: see
Startup warnings). If success is a typed property (PHP 7.4 and later), the bool is assigned
under the property's type rules: converted (an int property gets 1 or 0), or a TypeError (an array property, or
an int one under strict_types).
Return Values
If key is a string, the function returns the value of the variable stored with that key, or false if the key is
missing or has expired, or if an object of the value cannot be unserialized (see Values). success is set
to true when the value was found and read without an exception, and to false otherwise: use it to tell a stored false
from a miss.
If key is an array, the function returns an array key => value of the keys that were found and read, keyed the
way PHP keys any array (5 and '5' give the integer key 5, '07' stays a string); if none was found, the array is
empty. An element of key that is a reference (as a foreach by reference leaves them) is read as its value.
success is set to true, unless the call failed.
The call counts as a use of the entry (ucache.gc.ttl), and a read of an expired key removes its entry (see
Removal on contact).
Errors/Exceptions
Raises an E_WARNING ("key array elements can only be string or long") and returns false when an element of a key
array is neither a string nor an int.
If unserializing a stored object throws (__wakeup(), __unserialize(), Serializable::unserialize()), the
exception propagates, and success stays false although the entry exists and is kept; with an array of keys, the
keys after that one are not read. If it fails (see Values), PHP raises its warning (for example "Maximum
depth of 4096 exceeded") and the call returns false; with an array of keys, that key is left out. On reaching
memory_limit, see memory_limit.
Examples
Example #1 ucache_get() with key as a string
<?php ucache_add('color', 'blue'); var_dump(ucache_get('color', $success)); var_dump($success); ?>
The above example will output:
string(4) "blue"
bool(true)
Example #2 ucache_get() with key as an array
<?php $array1 = array('green' => '5', 'Blue' => '6', 'yellow' => '7', 'cyan' => '8', 5 => 'five'); ucache_set($array1); $array2 = array('green', 'Blue', 'yellow', 'cyan', 'red', '5'); var_dump(ucache_get($array2, $success)); var_dump($success); ?>
The above example will output:
array(5) {
["green"]=>
string(1) "5"
["Blue"]=>
string(1) "6"
["yellow"]=>
string(1) "7"
["cyan"]=>
string(1) "8"
[5]=>
string(4) "five"
}
bool(true)
Example #3 Telling a stored false from a miss
<?php ucache_set('flag', false); var_dump(ucache_get('flag', $success), $success); var_dump(ucache_get('missing', $success), $success); ?>
The above example will output:
bool(false)
bool(true)
bool(false)
bool(false)
See Also
- ucache_add() - Adds a variable in the user cache only if it does not already exist in the cache
- ucache_set() - Adds a variable in the user cache and overwrites a variable if it already exists in the cache
- ucache_delete() - Deletes variables from the user cache
- ucache_clear() - Deletes the entire content of the user cache
- ucache_exists() - Checks if a variable exists in the user cache
- ucache_meminfo() - Retrieves information about user cache memory usage
- ucache_info() - Retrieves information about data stored in the user cache
- __wakeup()
ucache_inc
(UCache >= 1.0.0)
ucache_inc - Increments the value associated with the key
Description
ucache_inc(string $key, int $delta = 1, bool &$success = null): int|false
Increments the value associated with the key by 1 or as specified by delta.
Parameters
key
The key that was used to store the variable in the cache. key is case sensitive.
delta
The value by which the variable is incremented. It has to be an integer.
success
Will be set to true on success and false on failure. It is left as it was when the cache is switched off
(ucache.enabled=0) or was not created.
Return Values
Returns the incremented value on success, or false if the key is missing or has expired, or if the stored value is
not an integer. The value changes in place: the entry keeps its ttl, and the call counts as a use of the entry
(ucache.gc.ttl). The result wraps around at the limits of the integer range.
Errors/Exceptions
Raises an E_WARNING ("function can only be called for key whose value is long") when the stored value is not an
integer. A missing key fails without a warning.
Examples
Example #1 Using ucache_inc()
<?php ucache_set('counter', 1); var_dump(ucache_inc('counter', 2921, $success)); var_dump($success); ?>
The above example will output:
int(2922)
bool(true)
See Also
- ucache_dec() - Decrements the value associated with the key
- ucache_cas() - Compares the variable with an old value and assigns a new value to it
ucache_info
(UCache >= 1.0.0)
ucache_info - Retrieves information about data stored in the user cache
Description
ucache_info(bool $summaryonly = false, ?string $key = null, int $offset = 0, int $limit = 0): array|false
Retrieves information about data stored in the user cache.
Parameters
summaryonly
Controls whether the returned array contains information about individual cache entries along with the user cache summary.
key
The key of an entry in the user cache. If specified, the returned array contains information only about that cache
entry, whatever summaryonly is (nothing if the key is missing or has expired). If null or not specified and
summaryonly is false, the returned array contains information about all the entries in the cache, or the part
of them that offset and limit select.
offset
Without key, the number of entries to skip at the start of the listing. The entries are listed in the order of
the cache's hash table, which stays the same while no entry is added or removed: a listing taken a page at a time
while other requests write to the cache may skip or repeat entries. Ignored with key.
limit
Without key, the largest number of entries to list, 0 meaning all of them. The full listing of a large cache can
run past memory_limit: each entry takes several hundred bytes of request memory, plus twice the length of its key
(about 800 MB for a million short keys). Ignored with key.
Return Values
Array of meta data about the user cache, or false on failure (the cache was not created, offset or limit is
negative, or memory_limit was reached while the entries were listed). The array contains:
total_cache_uptime- total time in seconds that the user cache has been activeis_local_cache- always false (the user cache is always the shared one)total_item_count- total number of elements that are currently in the user cache, expired ones includedhash_bucket_count- the hash buckets of the cache (seeucache.bucketbytes): 16381 for the default 8 MBtotal_hit_count- number of times the data has been served from the cachetotal_miss_count- number of times the data has not been found in the cachelast_scavenge_seconds- seconds since the scavenger last ran a step (at the end of a request or on a write) or a full pass (when the cache was full), or null if it has not run since the cache was created. A step that is skipped (see Steps) does not count.last_scavenge_count- the entries that step or pass removedtotal_scavenged_count- the entries that steps and full passes removed since the cache was created, evicted entries includedtotal_evicted_count- the entries evicted from a full cache since the cache was createdtotal_abandoned_lock_count- the times since the cache was created that a process found the cache lock or the lock of the memory allocator abandoned: the process that held it ended without releasing it (killed, for example by an IIS FastCGI timeout, or crashed), and what it had left half updated was put back as it was (see Freeing memory). A process that held both locks counts twice; a process that held none, or a lock that was created again in the meantime, does not count.ucache_entries- an array, indexed from 1 (fromoffset+ 1 withoutkey: the position of the entry in the full listing), that contains the information about the cached entries:key_name- name of the key which is used to store the datavalue_type- type of value stored by the key (long,double,string,bool,null,arrayorobject)value_size- memory taken by the value, in bytesttl_seconds- the ttl the entry was stored with, in seconds, 0 meaning that it never expiresage_seconds- time elapsed from the time data has been added in the cachehitcount- number of times data has been served from the cache
With summaryonly set to true, ucache_entries is empty. total_item_count always counts the whole cache, whatever
offset and limit select. ucache_info() removes nothing, does not count as a use of the entries, and also works
with ucache.enabled=0: its full listing shows the expired entries that nothing has removed yet (see
The Scavenger).
Examples
Example #1 Using ucache_info()
<?php ucache_set('green', 1); ucache_set('blue', array(1, 2), 300); ucache_get('green'); ucache_get('red'); print_r(ucache_info()); ?>
The above example will output:
Array
(
[total_cache_uptime] => 0
[is_local_cache] =>
[total_item_count] => 2
[hash_bucket_count] => 16381
[total_hit_count] => 1
[total_miss_count] => 1
[last_scavenge_seconds] =>
[last_scavenge_count] => 0
[total_scavenged_count] => 0
[total_evicted_count] => 0
[total_abandoned_lock_count] => 0
[ucache_entries] => Array
(
[1] => Array
(
[key_name] => green
[value_type] => long
[value_size] => 16
[ttl_seconds] => 0
[age_seconds] => 0
[hitcount] => 1
)
[2] => Array
(
[key_name] => blue
[value_type] => array
[value_size] => 128
[ttl_seconds] => 300
[age_seconds] => 0
[hitcount] => 0
)
)
)
On PHP 7.2 to 8.1, the value_size of blue is 160: the elements of a packed array take 32 bytes there, 16 since
PHP 8.2.
Example #2 ucache_info() for one key
<?php ucache_set('green', 1); print_r(ucache_info(false, 'green')); ?>
The above example will output:
Array
(
[total_cache_uptime] => 0
[is_local_cache] =>
[total_item_count] => 1
[hash_bucket_count] => 16381
[total_hit_count] => 0
[total_miss_count] => 0
[last_scavenge_seconds] =>
[last_scavenge_count] => 0
[total_scavenged_count] => 0
[total_evicted_count] => 0
[total_abandoned_lock_count] => 0
[ucache_entries] => Array
(
[1] => Array
(
[key_name] => green
[value_type] => long
[value_size] => 16
[ttl_seconds] => 0
[age_seconds] => 0
[hitcount] => 0
)
)
)
Example #3 Listing the entries a page at a time
<?php // 1000 entries per call, so that a large cache does not run past memory_limit $offset = 0; do { $info = ucache_info(false, null, $offset, 1000); if ($info === false) { break; } foreach ($info['ucache_entries'] as $position => $entry) { echo $position, ': ', $entry['key_name'], "\n"; } $offset += 1000; } while ($offset < $info['total_item_count']); ?>
See Also
- ucache_meminfo() - Retrieves information about user cache memory usage
- UCache Statistics Script
ucache_meminfo
(UCache >= 1.0.0)
ucache_meminfo - Retrieves information about user cache memory usage
Description
ucache_meminfo(): array|null
Retrieves information about the memory usage of the user cache, which is one shared memory segment of
ucache.size megabytes.
Parameters
This function has no parameters.
Return Values
Array of meta data about the user cache memory usage, or null if the cache was not created (CLI without
ucache.enablecli=1, or a cache that could not be created: see Startup warnings). It also works
with ucache.enabled=0. The array contains:
memory_total- amount of memory in bytes allocated for the user cachememory_free- amount of free memory in bytes available for the user cachenum_used_blks- number of memory blocks used by the user cachenum_free_blks- number of free memory blocks available for the user cachememory_overhead- amount of memory in bytes taken by the headers of the used and free blocks
Examples
Example #1 Using ucache_meminfo()
<?php print_r(ucache_meminfo()); ?>
The above example will output:
Array
(
[memory_total] => 8388608
[memory_free] => 8253952
[num_used_blks] => 1
[num_free_blks] => 1
[memory_overhead] => 128
)
See Also
- ucache_info() - Retrieves information about data stored in the user cache
- UCache Statistics Script
ucache_set
(UCache >= 1.0.0)
ucache_set - Adds a variable in the user cache and overwrites a variable if it already exists in the cache
Description
ucache_set(mixed $key, mixed $value = null, int $ttl = 0): bool|array ucache_set(array $values, mixed $unused = null, int $ttl = 0): bool|array
Adds a variable in the user cache. Overwrites a variable if it already exists in the cache. The added or updated
variable remains in the user cache unless its time to live expires or it is deleted by using the ucache_delete()
or ucache_clear() functions.
Parameters
key
Store the variable using this key name. If a variable with the same key is already present, the function overwrites
the previous value with the new one. key is case sensitive (see Keys).
value
Value of a variable to store. Value supports all data types except resources, such as file handles. Objects are
serialized (see Values). Called with the key alone, without a value, the function fails; with named
arguments (PHP 8), an omitted value is null and is stored: ucache_set('k', ttl: 60) stores null, like
ucache_set('k', null, 60).
values
Associative array of keys and values, which stores all the pairs in one call.
unused
Not used in the array form. A resource there still makes the call fail, with a warning.
ttl
Time for the variable to live in the cache in seconds. After the value specified in ttl has passed, the stored
variable can no longer be read, and the scavenger frees it (see The Scavenger). The default value
of 0 means that the variable never expires. A negative ttl makes the call fail. A ttl above 4294967295 (about
136 years) counts as 4294967295.
Return Values
If key is a string, the function returns true on success and false on failure: the key is blank or longer than
4096 characters, no value was given, the value is or holds a resource, is nested too deeply or does not fit in the
cache, its serialized objects would pass memory_limit, or ttl is negative.
If key is an array, the function returns an empty array if all the name => value pairs were stored, or else an
array of the names that could not be stored, each with the value -1. An element given by reference is stored as its
value.
Errors/Exceptions
Raises an E_WARNING for a blank key ("key cannot be blank string"), a negative ttl ("ttl cannot be less than 0")
and a resource given as the value ("value cannot be a resource"). A key longer than 4096 characters, a resource
inside an array value, or a value nested too deeply, fails without a warning.
If serializing an object of the value throws (a Closure, an object of an anonymous class, a Generator, a __sleep()
or __serialize() that throws), the exception propagates and the value is not stored. In the array form, the pairs
before that element are stored, that one and the ones after it are not.
Examples
Example #1 ucache_set() with key as a string
<?php var_dump(ucache_set('green', 1)); var_dump(ucache_set('green', 2, 60)); var_dump(ucache_get('green')); ?>
The above example will output:
bool(true)
bool(true)
int(2)
Example #2 ucache_set() with key as an array
<?php $colors = array('green' => 1, 'blue' => array(2, 3), 'red' => fopen('php://memory', 'r')); var_dump(ucache_set($colors, null, 300)); var_dump(ucache_get(array('green', 'blue', 'red'))); ?>
The above example will output:
array(1) {
["red"]=>
int(-1)
}
array(2) {
["green"]=>
int(1)
["blue"]=>
array(2) {
[0]=>
int(2)
[1]=>
int(3)
}
}
See Also
- ucache_add() - Adds a variable in the user cache only if it does not already exist in the cache
- ucache_get() - Gets a variable stored in the user cache
- ucache_delete() - Deletes variables from the user cache
- ucache_clear() - Deletes the entire content of the user cache
- ucache_exists() - Checks if a variable exists in the user cache
- ucache_meminfo() - Retrieves information about user cache memory usage
- ucache_info() - Retrieves information about data stored in the user cache
- __sleep()
Building for Windows
Prerequisites
-
Visual Studio 2022 with MSBuild and the C++ toolset of each PHP version to build for, which the Visual Studio Installer adds as individual components:
- 7.2, 7.3 and 7.4: "MSVC v141 - VS 2017 C++ x64/x86 build tools"
- 8.0, 8.1, 8.2 and 8.3: "MSVC v142 - VS 2019 C++ x64/x86 build tools"
- 8.4 and 8.5: "MSVC v143 - VS 2022 C++ x64/x86 build tools", the default toolset of Visual Studio 2022
Each DLL is linked with the toolset of its PHP binaries because, since PHP 7.4, PHP refuses to load a module linked with a newer linker than its own ("Can't load module ... as it's linked with 14.44, but the core is linked with 14.29"). It compares the tens of the minor version only, so older patch releases, linked with 14.28 (8.0.0) or 14.41 (8.4.0), load the DLL of their version too. In the releases of the table below, the core is linked with 14.16 (v141) in 7.4, 14.29 (v142) in 8.0 to 8.3 and 14.44 (v143) in 8.4 and 8.5; 7.2 and 7.3 do not check.
-
A Windows 10 or 11 SDK.
-
curlandtar, which come with Windows 10 1803 and later, to download and unpack the devel pack.
Compiling and building
build.bat show the usage
build.bat <version> build for PHP 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 or 8.5 and test
build.bat <version> install build and test, then copy the DLL into %PHP_DIR%\ext if the tests passed
There is no thread-safe build: UCache does not support ZTS. The table in :set_php_version at the end of
build.bat gives each version its devel pack, toolset and PHP_DIR:
| Version | Devel pack | Import library | Toolset | PHP_DIR |
|---|---|---|---|---|
| 7.2 | php-devel-pack-7.2.34-nts-Win32-VC15-x64 |
php7.lib |
v141 | c:\php72 |
| 7.3 | php-devel-pack-7.3.33-nts-Win32-VC15-x64 |
php7.lib |
v141 | c:\php73 |
| 7.4 | php-devel-pack-7.4.33-nts-Win32-vc15-x64 |
php7.lib |
v141 | c:\php74 |
| 8.0 | php-devel-pack-8.0.30-nts-Win32-vs16-x64 |
php8.lib |
v142 | c:\php80 |
| 8.1 | php-devel-pack-8.1.34-nts-Win32-vs16-x64 |
php8.lib |
v142 | c:\php81 |
| 8.2 | php-devel-pack-8.2.35-nts-Win32-vs16-x64 |
php8.lib |
v142 | c:\php82 |
| 8.3 | php-devel-pack-8.3.36-nts-Win32-vs16-x64 |
php8.lib |
v142 | c:\php83 |
| 8.4 | php-devel-pack-8.4.27-nts-Win32-vs17-x64 |
php8.lib |
v143 | c:\php84 |
| 8.5 | php-devel-pack-8.5.12-nts-Win32-vs17-x64 |
php8.lib |
v143 | c:\php85 |
build.bat looks for the devel pack in PHP_DEVEL_ROOT (an environment variable, default D:\Dev\php-devel), one
folder per pack. A missing pack is downloaded with curl (the current releases, then the archive), unpacked with
tar and checked (it must have include\main\php_version.h and the import library); the zip is then deleted. If
the download or the unpacking fails, the zip and the partly unpacked folder are deleted and the message says why: a
pack that windows.php.net does not have (HTTP 404), another curl error, or a damaged zip.
It then rewrites php_ucache.props with the pack's include and lib paths and import library, and builds
bin\Release\<version>\php<major><minor>\php_ucache.dll, where <version> is the name of the current git branch (the
branches are named after the versions, 1.1.0 for example), or else the version in precomp.h, and
php<major><minor> is php72, php73, php74, php80, php81, php82, php83, php84 or php85, so that the
builds of different versions do not overwrite each other, with the version's toolset and the newest Windows SDK it finds
(in %ProgramFiles(x86)%\Windows Kits\10, or else in the folder of the registry value KitsRoot10), without debug
information: php_ucache.vcxproj links without /DEBUG and compiles without /FC, so no PDB file is written next to
the DLL and the DLL holds no path of the build machine. The compiler's own PDB goes to the obj folder, which is
deleted, and the link-time code generation files that v141 writes next to the DLL (.iobj, .ipdb) are deleted too. If
the build fails and vswhere finds no Visual Studio with that toolset, build.bat names the component to install. Do not
retarget the project to another toolset, as MSBuild's error MSB8020 suggests: on PHP 7.4 and later, the DLL would not
load.
The DLL is tested before anything is installed (see Verifying the build). With install, the
DLL is copied into %PHP_DIR%\ext only if both test runs passed, and the version's folder is then deleted
(bin\Release\<version>, bin\Release and bin too, once they are empty; the other builds stay). When PHP_DIR has
no php.exe, the test is skipped, and install fails without installing anything. The exit code is 1 on any failure, a
failed or crashed test included, and 0 otherwise. MSBUILD at the top of build.bat is the path of MSBuild.exe. The
paths (PHP_DIR, PHP_DEVEL_ROOT, the folder of the sources) may contain spaces and parentheses.
Without build.bat, point php_ucache.props at the devel pack, or pass the pack on the command line with
"/p:PhpSdkRoot=<pack>\include" "/p:PhpLibName=<pack>\lib\php8.lib" (php7.lib for 7.2 to 7.4), which take
precedence over the file, and build with the toolset of the version:
MSBuild php_ucache.vcxproj /p:Configuration=Release /p:Platform=x64 /p:PlatformToolset=v142
MSBuild php_ucache.vcxproj /p:Configuration=Release /p:Platform=x64
MSBuild php_ucache.vcxproj /p:Configuration=Release /p:Platform=x64 /p:PlatformToolset=v141 /p:WindowsTargetPlatformVersion=10.0.26100.0
The first command is for 8.0 to 8.3, which refuse a DLL linked with the project's default toolset, v143. The second
(v143) is for 8.4 and 8.5, and also works for 7.2 and 7.3, which do not check the linker (7.4 refuses a v143 or v142
DLL). The third builds 7.2 to 7.4 with their own toolset, v141, which does not understand the project's
WindowsTargetPlatformVersion of 10.0 (the newest SDK) and fails with MSB8036: give it the version of an installed
Windows 10/11 SDK, a folder name in %ProgramFiles(x86)%\Windows Kits\10\Include. Building in Visual Studio uses
v143 as well.
In a PHP source tree built with the PHP SDK, config.w32 adds the --enable-ucache configure option (NTS builds
only).
Verifying the build
build.bat runs %PHP_DIR%\php.exe -n -d extension=<dll> -d ucache.enablecli=1 test-build.php <dll>, then the same
with opcache (-d zend_extension=%PHP_DIR%\ext\php_opcache.dll -d opcache.enable_cli=1; only
-d opcache.enable_cli=1 on PHP 8.5, where opcache is built in; skipped if there is no php_opcache.dll).
test-build.php checks the exported functions and settings, exercises every function, and checks the copying of
values into shared memory and back (nested, packed and sparse arrays, references, objects and their serialization
methods, the enums, readonly properties and weak references of the versions that have them), request and shared
memory leaks, argument errors, the scavenger, named arguments (PHP 8), typed $success properties (PHP 7.4 and
later) and deep values in a Fiber (PHP 8.1 and later). It prints one OK/FAIL line per check (SKIP for a section it
cannot run, and a NOTE line in place of a check of times none of whose 3 attempts ran on time, as on a loaded machine)
and exits with 1 if a check failed. It clears the cache it runs against, and needs:
-n(no php.ini), so that it can check the defaults of theucache.*settings; settings given with-dare not checked, so-d ucache.apppoolid=...or-d ucache.namesalt=...can keep it away from another cache;ucache.enablecli=1, without which the CLI has no cache;- a cache that no other process uses while it runs, since it checks hit and miss counts and free memory.
With the path of the DLL under test as its argument, as build.bat passes it, it also checks the DLL file: that it
holds no path of the build machine (of a PDB file or of the sources: a DLL built with debug information fails this
check), and that it imports GetTickCount64() and not GetTickCount(), whose count wraps every 49.7 days, longer than
a test can wait. And it starts other PHP processes that load that DLL, each group on a cache of its own (a unique
ucache.apppoolid or APP_POOL_ID): a holder keeps a cache alive while other processes use it, php -S servers on
127.0.0.1 stand for the FastCGI processes of a pool, which survive the fatal error of a request, and a Windows
PowerShell script holds named objects of a cache. They check what a single process cannot:
- a cache shared by several processes, and which processes share one (
ucache.apppoolid,APP_POOL_ID, the parent process); - the settings read at startup (
ucache.size,ucache.bucketbytes,ucache.prefix), and a cache that cannot be created; - the processes that start while the last process of their pool closes the cache, or after one died while it created the
cache (the PowerShell script holds the named objects of the cache as such processes find them; without
powershell.exe, or where it runs in ConstrainedLanguage mode, these checks are skipped, with aSKIPline); - the release of the cache lock after a fatal error, reads that fail in a Fiber or at memory_limit (no destructor runs
under the lock), and
$GLOBALSin scripts with and without variables at the top level; - the scavenger at the end of requests and on writes;
- a full cache (eviction, and values larger than the empty cache while another process writes), storing in a fragmented cache, and the memory that stored arrays take;
ucache_clear()of a full cache while other processes read and write;- processes killed while they clear, evict, store or replace entries: the cache they leave must be consistent (the
checks of a clear, a full pass and replaces print a
NOTEline when none of their attempts killed the process while it held a lock); - ucache.php, which a
php -Sserver runs: its Clear All form, which a planted cookie, another origin or a copy of the page cannot use, and its authentication, also with IIS authentication (these checks are skipped, with aSKIPline, if ucache.php is not next to test-build.php).
They need a writable temporary folder, free local ports and Windows PowerShell, and every process they start has a
timeout. Without the argument, all the checks with several processes are skipped, with one SKIP line. The second run,
with opcache, goes through the immutable arrays of opcache, which take other paths through the copy code.
Debug builds
Builds compiled with UCACHE_DEBUG (which defines UCACHE_TEST) also have ucache_lasterror(), the last internal
error code of the user cache in this process, and check their internal assertions: a failed one is written to the debug
trace (with ucache.debuglevel 101 or more) and breaks into a debugger that is attached.
To debug the DLL with symbols, set GenerateDebugInformation to true in the Link settings of php_ucache.vcxproj
on your machine and build with MSBuild, as in the commands of Compiling and building
(build.bat deletes the PDB files next to the DLL): the build then writes a PDB file next to the DLL and puts its path
in the DLL, so the check of test-build.php that the DLL holds no path of the build machine fails for that build.
Migrating from WinCache
| WinCache 2.0.0.8 | UCache |
|---|---|
wincache_ucache_get() |
ucache_get() |
wincache_ucache_set() |
ucache_set() |
wincache_ucache_add() |
ucache_add() |
wincache_ucache_delete() |
ucache_delete() |
wincache_ucache_clear() |
ucache_clear() |
wincache_ucache_exists() |
ucache_exists() |
wincache_ucache_info() |
ucache_info() |
wincache_ucache_meminfo() |
ucache_meminfo() |
wincache_ucache_inc() |
ucache_inc() |
wincache_ucache_dec() |
ucache_dec() |
wincache_ucache_cas() |
ucache_cas() |
wincache_ucache_lasterror() (debug builds) |
ucache_lasterror() (debug builds) |
wincache.ucenabled |
ucache.enabled |
wincache.ucachesize |
ucache.size |
wincache.enablecli |
ucache.enablecli |
wincache.namesalt |
ucache.namesalt |
wincache.apppoolid |
ucache.apppoolid |
wincache.filemapdir |
ucache.filemapdir |
wincache.debuglevel |
ucache.debuglevel |
wincache.php (statistics page) |
ucache.php (see UCache Statistics Script) |
Not carried over: the file and resolve-path caches (wincache.fcenabled, fcachesize, maxfilesize, filecount,
chkinterval, ttlmax, ignorelist, fcenabledfilter, fcndetect, wincache_fcache_*(), wincache_rplist_*(),
wincache_fcnotify_*(), wincache_refresh_if_changed()), function rerouting (wincache.reroute_enabled), the session
handler (session.save_handler = wincache, wincache.scachesize, wincache_scache_*()), wincache_lock() /
wincache_unlock(), the internal self tests of debug builds (wincache_runtests()), and the debugging setting
wincache.localheap, with which every value went to the process heap and could never be read back.
UCache does not see data stored by WinCache; it starts with an empty cache.
Code that defines ucache_* functions of its own (a wrapper around wincache_ucache_*(), for example) can keep
them: set ucache.prefix (see Runtime Configuration) to a prefix of their own.
Differences from WinCache 2.0.0.8
Several of these change what existing code sees.
- UCache supports PHP 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5, each with a DLL of its own (see Building for Windows), and on PHP 8 named arguments work. WinCache 2.0.0.8 supported PHP 7.0, 7.1 and 7.2 only (its sources do not compile for PHP 7.3 and later); UCache does not support 7.0 and 7.1.
- The scavenger works differently (see The Scavenger). Its steps also run at the end of requests,
sized to the cache, and apply the same expiry rule as reads (the time since the entry was stored); calls that look
up a key remove the expired entries they pass; and a
ucache_set()orucache_add()that finds the cache full first runs a full pass and, withucache.gc.evict, evicts the entries not used for the longest time, unless the value takes more than the empty cache holds, which fails at once (see A full cache). WinCache 2.0.0.8 ran a step of 16 hash buckets only inside writes (set, add, delete, inc, dec, cas), at most every 10 seconds, so a cache that was only read was never scavenged; it removed an entry once its ttl had passed since it was last read or stored, up to a second before it expired or up to another ttl after; calls left expired entries in place, so storing an expired key again left two entries, and deleting one left it in the cache; and a full cache made writes fail. Theucache.gc.*settings and the scavenger keys ofucache_info()are new. - Integer keys in the array forms of
ucache_get(),ucache_set()anducache_add()use their full decimal string. WinCache 2.0.0.8 cut them to 32 bits, so4294967296was stored as0andPHP_INT_MAXas-1. ucache_get()with an array of keys returns numeric keys as integer keys. WinCache 2.0.0.8 returned them as string keys that neither$result[5]nor$result['5']could read.ucache_delete()with an array of keys leaves the caller's array as it is. WinCache 2.0.0.8 turned its integer elements into strings in place, which could crash a FastCGI worker running opcache.ucache_get()anducache_delete()read the elements of a key array that are references (as aforeachby reference leaves them) as their values. WinCache 2.0.0.8 raised its "key array elements can only be string or long" warning and returned false.- Deleting, replacing or scavenging an entry, and storing a value, take the same time however many entries and free blocks the cache holds: free blocks are kept in lists by size, and a block is split only when the rest can hold the smallest block the cache asks for (16 bytes). Deleting every second one of 100,000 entries takes about 0.1 second, and with 125,000 free blocks too small for them, 2000 sets of a 1000-byte string take about 0.01 second. Only a value whose list holds many free blocks a little too small for it, while the lists of larger sizes are empty, goes through its list (about 0.2 ms with 50,000 such blocks). WinCache 2.0.0.8 walked the blocks that follow a freed block in shared memory to find the next free one, and kept its free blocks in one list in address order, from which it took the first one large enough, so a delete took time in proportion to the number of entries and a write in proportion to the free blocks before the one it took: deleting every second one of 10,000 entries took about 0.08 second, of 100,000 entries about 8 seconds, with every process of the pool waiting for the allocator meanwhile. It also split a block when 4 bytes or more were left, leaving free blocks that nothing could use, which every search then went past.
- A stored array takes the memory it holds: its tables and strings are counted before they are copied and go in one block of that size, each string once however often it occurs. A row of 14 short strings takes about 2.1 KB with the entry, an array of 100 such rows about 100 KB, and deleting, replacing, clearing or expiring an entry frees a few blocks, after the cache lock has been released (see Freeing memory). WinCache 2.0.0.8 put the parts of an array in chunks of 512 bytes, 1 KB or 4 KB, by the size of the part, which a small table left mostly empty, and it freed a large entry in time quadratic in its size, while the cache lock was held: for an entry of 100,000 small arrays it took 14-21 s, and every other process waited.
ucache_clear()holds the cache lock only for a moment: the cache is empty for every process at once, and its entries are then freed in batches of at most 32, the lock released between them, so the other processes go on reading and writing (with a full cache of 512 MB, a read in another process waited at most 0.6 ms during a clear of 1.5 to 2.1 seconds (about 4 ms with 4 processes writing meanwhile), and a write about 0.1 second at most, while it helped free the entries); aucache_clear()while the entries of an earlier one are being freed returns at once; and a process killed during the clear leaves the cache cleared (see Freeing memory). WinCache 2.0.0.8 freed every entry under the cache lock, so every other process of the pool waited until the clear ended, and a process killed during the clear left the entries it had not reached in the cache.- Lookups stay fast as the cache fills: the hash table has one bucket per 512 bytes of
ucache.size(16381 for the default 8 MB, 262139 for 128 MB), so its chains hold 2 to 3 entries on average even in a cache full of the smallest entries, and the whole key is hashed;ucache.bucketbytessets other sizes, anducache_info()reports the number (hash_bucket_count). WinCache 2.0.0.8 had one bucket per 32 to 64 KB (197 for 8 MB, 3203 for 128 MB), which no setting changed, and put every key longer than 260 characters into the same bucket. With 500,000 entries of 40-character keys in 128 MB, its chains held about 156 entries, and a read compared its key with theirs one by one: it took about 9 microseconds, 17 for a missing key, with keys that differ from their first characters, and about 10 and 20 with keys that share their first 33, against about 1 with UCache's table. 5,000 keys of 300 characters in one chain made a read take about 13 microseconds and a miss 26, against about 1.3 when they are spread. - A
ttlis taken as given up to 4294967295 seconds, and as 4294967295 above. WinCache 2.0.0.8 cut it to 32 bits: 2147483648 to 4294967295 were refused as negative ttls, 4294967296 meant no expiry and 4294967297 one second. - UCache measures times with the 64-bit millisecond tick count of Windows, which does not wrap: an entry expires on
time whatever its
ttl, andtotal_cache_uptimeandage_secondsgo on counting after 49.7 days. WinCache 2.0.0.8 used the 32-bit tick count, which wraps every 49.7 days: an entry with attlof 4294967 seconds (about 49.7 days) or more never expired, an expired entry that its scavenger had not removed could be read again forttlseconds every 49.7 days, andtotal_cache_uptimeandage_secondsstarted again from 0 every 49.7 days. $GLOBALScan be stored, as a value or with the array forms, and so can the arrays thatarray_merge(),array_slice()and the like make of it, except a list of its entries (see Values). On PHP 7,$GLOBALSholds the variables that the code at the top level of the script uses through slots, which those functions copy as they are, and which WinCache 2.0.0.8 did not follow: storing$GLOBALS, or such an array, as a value failed when the script had such variables, and the array forms failed for each of them and forGLOBALSitself.ucache.sizetakes a number of megabytes, always in decimal (064is 64), or a size with aK,MorGsuffix (64Mis 64 MB). A size outside 5..930 MB is clamped with a startup warning; digits followed by other text are taken as megabytes, and any other value gives the default 8 MB, each with a startup warning (see Runtime Configuration). WinCache 2.0.0.8 readwincache.ucachesizeas PHP reads any integer setting: it applied the suffix to the megabytes (64Masked for 67108864 MB, which became 930 MB) and read064as octal (52 MB) and64MBas 64; it clamped without a warning; and it stored the setting as an 8-byte value into a 4-byte field, so sizes were read as 32-bit unsigned numbers (a negative size meant 930 MB,4Gmeant 5 MB).- A cache that cannot be created at startup (a
ucache.filemapdirthat does not exist, shared objects of another account, an initialization lock held for 30 seconds) raises a startup warning that names the likely cause, which PHP writes to its error log and to the Windows event log, and theUser cacheline ofphpinfo()saysunavailable(see Startup warnings). WinCache 2.0.0.8 only wrote an ETW event (provider PHP-Wincache) that no event log shows, its functions returned false without a warning, and itsphpinfo()had no user cache line. - Processes of different application pools do not share a cache: the names of the cache's shared memory and
synchronization objects hold a 64-bit hash of the application pool id (
ucache.apppoolid, orAPP_POOL_ID, read as Unicode), A-Z taken as a-z. WinCache 2.0.0.8 reduced the id to one of 899,999 numbers, so different pools could share one cache (site907andsite1499did, as did pool names that differ only in characters outside the ANSI code page, which it read in that code page): about one chance in 180 among 100 pools, 43% among 1,000. UCache takes an emptyucache.apppoolid(ucache.apppoolid=, ornone,off,no,falseornull),ucache.filemapdirorucache.namesaltas not set, and backs with a file inucache.filemapdironly the cache of an application pool. WinCache 2.0.0.8 took an emptywincache.apppoolidas an id of its own, which every process with it shared, instead of usingAPP_POOL_IDor the parent process; put the file of an emptywincache.filemapdirin the root of the current drive; added an emptywincache.namesaltto the names, which kept those processes apart from the ones without the setting; and backed with a file the caches of processes whose parent process id was above 99999. - A process that starts while the last process of its pool is closing the cache, or after a process died while it
created the cache, gets a working cache. Under the init mutex of the cache, it initializes the shared memory if the
memory is new or lacks the mark that its initialization completed (written last, so a process that dies before then
leaves the memory unmarked), and otherwise uses it as it is, at the size it has; the table of the caches of the pool
is decided the same way. WinCache 2.0.0.8 went by which of its init mutexes already existed, which such a process
could get wrong: its first lookup of a key could then end it with a division by zero, as it could in every process
that opened the cache while it ran; or it initialized the memory allocator again under the entries of the cache,
after which entries could be lost and calls could crash (with a larger
wincache.ucachesizethan the processes that were ending, it wrote past the end of their memory); or it ran without a cache, every function returning false, as did every process of the pool that started while it ran. - Reading an array with
ucache_get()does not leak request memory. WinCache 2.0.0.8 leaked about 1.2 KB per read, until the end of the request. - A reference that only the fetched value holds has reference count 1, as in any PHP array where no variable shares
it:
var_dump()shows no&,serialize()writes it as a plain value, and a copy of the fetched array does not share it (writing through the copy does not change the original). WinCache 2.0.0.8 counted extra references, as if another variable still shared it. - An object that refers to itself through a reference (
$o->self = &$o) reads back as a value, asunserialize()returns it. WinCache 2.0.0.8 returned it as a reference shared with that property: a copy of the array holding it shared that reference, and writing to the element also changed the object's property. - On PHP 7.2, an array read back has PHP's recursion protection, also when it was stored from an array that opcache
made immutable (a literal, a constant):
var_dump(),print_r(),var_export(),json_encode(),count()and==detect a recursion built in it. WinCache 2.0.0.8 kept the flags of the stored array, without that protection, and these functions overflowed the C stack and ended the process on such a recursion. ucache_get()returns false, andsuccessis false, when an object of the value cannot be unserialized (see Values), asunserialize()returns false; the array form leaves that key out. WinCache 2.0.0.8 returned null in the object's place and setsuccessto true.- Shared memory is not lost by values given by reference to the array forms of
ucache_set()anducache_add(), byucache_set()calls that fail, or by a fatal error duringucache_set()(raised in__sleep(), or by max_execution_time or memory_limit). In WinCache 2.0.0.8 that memory belonged to no entry, so neither a delete nor a clear could give it back, and it stayed lost until every process of the pool had exited. - A value whose serialization throws (a Closure, an anonymous class, a Generator, a throwing
__sleep()) is not stored, and the array form stops at that element: the elements after it are not stored. WinCache 2.0.0.8 let the exception propagate too, but stored a broken entry (wincache_ucache_exists()true, the value null or cut short). - A value whose arrays and references are nested more than 10,000 levels deep is not stored:
ucache_set()anducache_add()return false (see Values). On PHP 8.1 and later, neither is a value nested too deeply for the C stack that is left (in a Fiber, for example), and reading one returns false. On very deep values, WinCache 2.0.0.8 overflowed the C stack and crashed the process, and the shared memory of the half-copied entry was lost for every process of the pool. - In the array forms of
ucache_set()anducache_add(), a value given by reference is stored as its value. WinCache 2.0.0.8 stored it as a reference, which came back sharing data with the other such values and whichwincache_ucache_inc()refused. $success(ucache_get(),ucache_inc(),ucache_dec()): the value that the variable held before is released; WinCache 2.0.0.8 leaked a string or an array there. On PHP 7.4 and later, a typed property gets the bool under its type rules: converted, or aTypeError.- Arguments are checked before the state of the cache: with
ucache.enabled=0, or a cache that was not created, invalid arguments throwArgumentCountErrororTypeError, or raise a warning on PHP 7 withoutstrict_types. WinCache 2.0.0.8 returned false silently. ucache_clear()anducache_meminfo()called with an argument throwArgumentCountError, or raise a warning on PHP 7 withoutstrict_types. In WinCache 2.0.0.8,wincache_ucache_clear(1)returned false silently, andwincache_ucache_meminfo(1)ignored the argument.- Reflection shows one required parameter for
ucache_set()anducache_add(), two for WinCache 2.0.0.8. - The prefix of the function names is a setting,
ucache.prefix. WinCache 2.0.0.8's names were fixed (wincache_ucache_*). ini_get('ucache.namesalt'),phpinfo()anducache.phpshow the whole value of the setting, although only its first 8 characters are used. WinCache 2.0.0.8 cut the setting itself to those 8 characters.- Objects are unserialized after the cache lock has been released, and values are copied in without holding it, so
__sleep(),__serialize(),__wakeup(),__unserialize(),Serializable::serialize()andSerializable::unserialize()do not run under the lock, and readers do not wait while a value is copied in. WinCache 2.0.0.8 did both under the lock. The cost: with several processes writing small values at once, aggregateucache_set()throughput is lower than with WinCache 2.0.0.8 (about 1.3 to 1.7 times lower for[1, 2, 3]with 2 to 6 writers on PHP 7.2), because concurrent copies take turns on the allocator's lock for each allocation. The parts of an array take one allocation, so for nested arrays it is higher (about 1.2 to 1.9 times higher with 2 to 6 writers). A single writer is faster. - The UCache functions work inside
Serializable::serialize()andSerializable::unserialize(): UCache serializes and unserializes each object independently of the caller'sserialize()orunserialize()(see Serialization and the cache lock). WinCache 2.0.0.8 joined PHP's serializer context there, which corrupted both the nested value and the enclosing object. Inside__sleep()and__wakeup(), a value holding aSerializableobject that refers to itself recursed until memory_limit (when stored) or lost the self-reference (when read). - A fatal error during a cache call (for example memory_limit while
ucache_get()copies a big value) does not leave the cross-process lock held. In WinCache 2.0.0.8 it did, which blocked every other process of the pool until the failing one exited. - A process that ends while it holds the cache lock or the lock of the memory allocator (killed by IIS at a FastCGI
timeout, for example, or crashed) leaves the shared memory consistent: the next process to take the lock undoes the
update that the process left half done, so that only the memory it was working on is lost (see
Freeing memory), and
total_abandoned_lock_countinucache_info()counts such locks. WinCache 2.0.0.8 only wrote an ETW event for the abandoned lock and went on with the half-done update, in every process of the pool until all of them had exited: depending on where the process ended, the list of free blocks could give the same memory out twice or crash the next process that used it, every later write could fail, or a hash chain could keep pointing to an entry after its memory was freed. - Changes that
__sleep()or__serialize()make to a value whileucache_set()stores it are not stored: the entry holds the value as it was whenucache_set()was called, or the call fails. WinCache 2.0.0.8 could store an entry made of freed or unrelated data, which crashed or broke every process that read it. __sleep()and__serialize()are called once per distinct object that the value holds in its arrays and references; WinCache 2.0.0.8 called__sleep()once per occurrence. An object that another object of the value holds is still serialized again as part of that object.__wakeup()and__unserialize()still run once per occurrence.ucache_set()anducache_add()serialize every object of a value before they copy anything into the cache, and keep the serializations until the value is copied; WinCache 2.0.0.8 serialized one object at a time. UCache counts them against memory_limit, so close to the limit a value with many objects may not be stored: the call then returns false, without an error (see memory_limit).value_size(ucache_info()) differs slightly from WinCache 2.0.0.8: sparse hash arrays keep a larger hash part, and an object that occurs more than once is stored once.ucache_info()takesoffsetandlimit, which list a part of the entries without copying the others, and a nullkeymeans no key (see ucache_info). WinCache 2.0.0.8'swincache_ucache_info()always copied every entry into the array it returned, so the listing of a cache of many entries ran past memory_limit, and it took a nullkeyas the empty key, listing no entry.ucache.phpescapes everything it prints and works on PHP 8 (the hit/miss chart ofwincache.phpcalledeach(), which PHP 8 removed). Its Clear All button posts a form with a token that the page makes fromPASSWORD, and the page offers it only whenPASSWORDis neitherucachenor empty, with IIS authentication too; anyUSE_AUTHENTICATIONother than 0 turns authentication on (see UCache Statistics Script).wincache.phpcleared the cache with a link (?page=4&clc=1) that another site could make a logged-in browser follow, offered it with IIS authentication whatever the password, and with aUSE_AUTHENTICATIONother than 0 and 1 asked for no authentication but offered it all the same.ucache.phplists the entries 250 per page and copies only those;wincache.phpcopied every entry even to show the first 250 (and showed all of them on one page with its Show all entries link), so on a cache of many entries it failed with the memory_limit error.