'VirtualRESTServiceClient' => __DIR__ . '/includes/libs/virtualrest/VirtualRESTServiceClient.php',
'WANCacheReapUpdate' => __DIR__ . '/includes/deferred/WANCacheReapUpdate.php',
'WANObjectCache' => __DIR__ . '/includes/libs/objectcache/WANObjectCache.php',
- 'WANObjectCacheReaper' => __DIR__ . '/includes/libs/objectcache/WANObjectCacheReaper.php',
+ 'WANObjectCacheReaper' => __DIR__ . '/includes/libs/objectcache/wancache/WANObjectCacheReaper.php',
'WantedCategoriesPage' => __DIR__ . '/includes/specials/SpecialWantedcategories.php',
'WantedFilesPage' => __DIR__ . '/includes/specials/SpecialWantedfiles.php',
'WantedPagesPage' => __DIR__ . '/includes/specials/SpecialWantedpages.php',
'WikitextContent' => __DIR__ . '/includes/content/WikitextContent.php',
'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php',
'WikitextLogFormatter' => __DIR__ . '/includes/logging/WikitextLogFormatter.php',
- 'WinCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/WinCacheBagOStuff.php',
+ 'WinCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/wancache/WinCacheBagOStuff.php',
'WithoutInterwikiPage' => __DIR__ . '/includes/specials/SpecialWithoutinterwiki.php',
'WordLevelDiff' => __DIR__ . '/includes/diff/WordLevelDiff.php',
'WrapOldPasswords' => __DIR__ . '/maintenance/wrapOldPasswords.php',
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use Wikimedia\ScopedCallback;
-
-/**
- * Class for scanning through chronological, log-structured data or change logs
- * and locally purging cache keys related to entities that appear in this data.
- *
- * This is useful for repairing cache when purges are missed by using a reliable
- * stream, such as Kafka or a replicated MySQL table. Purge loss between datacenters
- * is expected to be more common than within them.
- *
- * @since 1.28
- */
-class WANObjectCacheReaper implements LoggerAwareInterface {
- /** @var WANObjectCache */
- protected $cache;
- /** @var BagOStuff */
- protected $store;
- /** @var callable */
- protected $logChunkCallback;
- /** @var callable */
- protected $keyListCallback;
- /** @var LoggerInterface */
- protected $logger;
-
- /** @var string */
- protected $channel;
- /** @var int */
- protected $initialStartWindow;
-
- /**
- * @param WANObjectCache $cache Cache to reap bad keys from
- * @param BagOStuff $store Cache to store positions use for locking
- * @param callable $logCallback Callback taking arguments:
- * - The starting position as a UNIX timestamp
- * - The starting unique ID used for breaking timestamp collisions or null
- * - The ending position as a UNIX timestamp
- * - The maximum number of results to return
- * It returns a list of maps of (key: cache key, pos: UNIX timestamp, id: unique ID)
- * for each key affected, with the corrosponding event timestamp/ID information.
- * The events should be in ascending order, by (timestamp,id).
- * @param callable $keyCallback Callback taking arguments:
- * - The WANObjectCache instance
- * - An object from the event log
- * It should return a list of WAN cache keys.
- * The callback must fully duck-type test the object, since can be any model class.
- * @param array $params Additional options:
- * - channel: the name of the update event stream.
- * - initialStartWindow: seconds back in time to start if the position is lost.
- * Default: 1 hour.
- * - logger: an SPL monolog instance [optional]
- */
- public function __construct(
- WANObjectCache $cache,
- BagOStuff $store,
- callable $logCallback,
- callable $keyCallback,
- array $params
- ) {
- $this->cache = $cache;
- $this->store = $store;
-
- $this->logChunkCallback = $logCallback;
- $this->keyListCallback = $keyCallback;
- if ( isset( $params['channel'] ) ) {
- $this->channel = $params['channel'];
- } else {
- throw new UnexpectedValueException( "No channel specified." );
- }
-
- $this->initialStartWindow = $params['initialStartWindow'] ?? 3600;
- $this->logger = $params['logger'] ?? new NullLogger();
- }
-
- public function setLogger( LoggerInterface $logger ) {
- $this->logger = $logger;
- }
-
- /**
- * Check and reap stale keys based on a chunk of events
- *
- * @param int $n Number of events
- * @return int Number of keys checked
- */
- final public function invoke( $n = 100 ) {
- $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
- $scopeLock = $this->store->getScopedLock( "$posKey:busy", 0 );
- if ( !$scopeLock ) {
- return 0;
- }
-
- $now = time();
- $status = $this->store->get( $posKey );
- if ( !$status ) {
- $status = [ 'pos' => $now - $this->initialStartWindow, 'id' => null ];
- }
-
- // Get events for entities who's keys tombstones/hold-off should have expired by now
- $events = call_user_func_array(
- $this->logChunkCallback,
- [ $status['pos'], $status['id'], $now - WANObjectCache::HOLDOFF_TTL - 1, $n ]
- );
-
- $event = null;
- $keyEvents = [];
- foreach ( $events as $event ) {
- $keys = call_user_func_array(
- $this->keyListCallback,
- [ $this->cache, $event['item'] ]
- );
- foreach ( $keys as $key ) {
- unset( $keyEvents[$key] ); // use only the latest per key
- $keyEvents[$key] = [
- 'pos' => $event['pos'],
- 'id' => $event['id']
- ];
- }
- }
-
- $purgeCount = 0;
- $lastOkEvent = null;
- foreach ( $keyEvents as $key => $keyEvent ) {
- if ( !$this->cache->reap( $key, $keyEvent['pos'] ) ) {
- break;
- }
- ++$purgeCount;
- $lastOkEvent = $event;
- }
-
- if ( $lastOkEvent ) {
- $ok = $this->store->merge(
- $posKey,
- function ( $bag, $key, $curValue ) use ( $lastOkEvent ) {
- if ( !$curValue ) {
- // Use new position
- } else {
- $curCoord = [ $curValue['pos'], $curValue['id'] ];
- $newCoord = [ $lastOkEvent['pos'], $lastOkEvent['id'] ];
- if ( $newCoord < $curCoord ) {
- // Keep prior position instead of rolling it back
- return $curValue;
- }
- }
-
- return [
- 'pos' => $lastOkEvent['pos'],
- 'id' => $lastOkEvent['id'],
- 'ctime' => $curValue ? $curValue['ctime'] : date( 'c' )
- ];
- },
- IExpiringStore::TTL_INDEFINITE
- );
-
- $pos = $lastOkEvent['pos'];
- $id = $lastOkEvent['id'];
- if ( $ok ) {
- $this->logger->info( "Updated cache reap position ($pos, $id)." );
- } else {
- $this->logger->error( "Could not update cache reap position ($pos, $id)." );
- }
- }
-
- ScopedCallback::consume( $scopeLock );
-
- return $purgeCount;
- }
-
- /**
- * @return array|bool Returns (pos, id) map or false if not set
- */
- public function getState() {
- $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
-
- return $this->store->get( $posKey );
- }
-}
+++ /dev/null
-<?php
-/**
- * Object caching using WinCache.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-/**
- * Wrapper for WinCache object caching functions; identical interface
- * to the APC wrapper
- *
- * @ingroup Cache
- */
-class WinCacheBagOStuff extends MediumSpecificBagOStuff {
- protected function doGet( $key, $flags = 0, &$casToken = null ) {
- $casToken = null;
-
- $blob = wincache_ucache_get( $key );
- if ( !is_string( $blob ) && !is_int( $blob ) ) {
- return false;
- }
-
- $value = $this->unserialize( $blob );
- if ( $value !== false ) {
- $casToken = (string)$blob; // don't bother hashing this
- }
-
- return $value;
- }
-
- protected function cas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
- if ( !wincache_lock( $key ) ) { // optimize with FIFO lock
- return false;
- }
-
- $curCasToken = null; // passed by reference
- $this->doGet( $key, self::READ_LATEST, $curCasToken );
- if ( $casToken === $curCasToken ) {
- $success = $this->set( $key, $value, $exptime, $flags );
- } else {
- $this->logger->info(
- __METHOD__ . ' failed due to race condition for {key}.',
- [ 'key' => $key ]
- );
-
- $success = false; // mismatched or failed
- }
-
- wincache_unlock( $key );
-
- return $success;
- }
-
- protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
- $result = wincache_ucache_set( $key, $this->serialize( $value ), $exptime );
-
- // false positive, wincache_ucache_set returns an empty array
- // in some circumstances.
- // @phan-suppress-next-line PhanTypeComparisonToArray
- return ( $result === [] || $result === true );
- }
-
- public function add( $key, $value, $exptime = 0, $flags = 0 ) {
- if ( wincache_ucache_exists( $key ) ) {
- return false; // avoid warnings
- }
-
- $result = wincache_ucache_add( $key, $this->serialize( $value ), $exptime );
-
- // false positive, wincache_ucache_add returns an empty array
- // in some circumstances.
- // @phan-suppress-next-line PhanTypeComparisonToArray
- return ( $result === [] || $result === true );
- }
-
- protected function doDelete( $key, $flags = 0 ) {
- wincache_ucache_delete( $key );
-
- return true;
- }
-
- /**
- * Construct a cache key.
- *
- * @since 1.27
- * @param string $keyspace
- * @param array $args
- * @return string
- */
- public function makeKeyInternal( $keyspace, $args ) {
- // WinCache keys have a maximum length of 150 characters. From that,
- // subtract the number of characters we need for the keyspace and for
- // the separator character needed for each argument. To handle some
- // custom prefixes used by thing like WANObjectCache, limit to 125.
- // NOTE: Same as in memcached, except the max key length there is 255.
- $charsLeft = 125 - strlen( $keyspace ) - count( $args );
-
- $args = array_map(
- function ( $arg ) use ( &$charsLeft ) {
- // 33 = 32 characters for the MD5 + 1 for the '#' prefix.
- if ( $charsLeft > 33 && strlen( $arg ) > $charsLeft ) {
- $arg = '#' . md5( $arg );
- }
-
- $charsLeft -= strlen( $arg );
- return $arg;
- },
- $args
- );
-
- if ( $charsLeft < 0 ) {
- return $keyspace . ':BagOStuff-long-key:##' . md5( implode( ':', $args ) );
- }
-
- return $keyspace . ':' . implode( ':', $args );
- }
-
- /**
- * Increase stored value of $key by $value while preserving its original TTL
- * @param string $key Key to increase
- * @param int $value Value to add to $key (Default 1)
- * @return int|bool New value or false on failure
- */
- public function incr( $key, $value = 1 ) {
- if ( !wincache_lock( $key ) ) { // optimize with FIFO lock
- return false;
- }
-
- $n = $this->doGet( $key );
- if ( $this->isInteger( $n ) ) {
- $n = max( $n + (int)$value, 0 );
- $oldTTL = wincache_ucache_info( false, $key )["ucache_entries"][1]["ttl_seconds"];
- $this->set( $key, $n, $oldTTL );
- } else {
- $n = false;
- }
-
- wincache_unlock( $key );
-
- return $n;
- }
-}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Wikimedia\ScopedCallback;
+
+/**
+ * Class for scanning through chronological, log-structured data or change logs
+ * and locally purging cache keys related to entities that appear in this data.
+ *
+ * This is useful for repairing cache when purges are missed by using a reliable
+ * stream, such as Kafka or a replicated MySQL table. Purge loss between datacenters
+ * is expected to be more common than within them.
+ *
+ * @since 1.28
+ */
+class WANObjectCacheReaper implements LoggerAwareInterface {
+ /** @var WANObjectCache */
+ protected $cache;
+ /** @var BagOStuff */
+ protected $store;
+ /** @var callable */
+ protected $logChunkCallback;
+ /** @var callable */
+ protected $keyListCallback;
+ /** @var LoggerInterface */
+ protected $logger;
+
+ /** @var string */
+ protected $channel;
+ /** @var int */
+ protected $initialStartWindow;
+
+ /**
+ * @param WANObjectCache $cache Cache to reap bad keys from
+ * @param BagOStuff $store Cache to store positions use for locking
+ * @param callable $logCallback Callback taking arguments:
+ * - The starting position as a UNIX timestamp
+ * - The starting unique ID used for breaking timestamp collisions or null
+ * - The ending position as a UNIX timestamp
+ * - The maximum number of results to return
+ * It returns a list of maps of (key: cache key, pos: UNIX timestamp, id: unique ID)
+ * for each key affected, with the corrosponding event timestamp/ID information.
+ * The events should be in ascending order, by (timestamp,id).
+ * @param callable $keyCallback Callback taking arguments:
+ * - The WANObjectCache instance
+ * - An object from the event log
+ * It should return a list of WAN cache keys.
+ * The callback must fully duck-type test the object, since can be any model class.
+ * @param array $params Additional options:
+ * - channel: the name of the update event stream.
+ * - initialStartWindow: seconds back in time to start if the position is lost.
+ * Default: 1 hour.
+ * - logger: an SPL monolog instance [optional]
+ */
+ public function __construct(
+ WANObjectCache $cache,
+ BagOStuff $store,
+ callable $logCallback,
+ callable $keyCallback,
+ array $params
+ ) {
+ $this->cache = $cache;
+ $this->store = $store;
+
+ $this->logChunkCallback = $logCallback;
+ $this->keyListCallback = $keyCallback;
+ if ( isset( $params['channel'] ) ) {
+ $this->channel = $params['channel'];
+ } else {
+ throw new UnexpectedValueException( "No channel specified." );
+ }
+
+ $this->initialStartWindow = $params['initialStartWindow'] ?? 3600;
+ $this->logger = $params['logger'] ?? new NullLogger();
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Check and reap stale keys based on a chunk of events
+ *
+ * @param int $n Number of events
+ * @return int Number of keys checked
+ */
+ final public function invoke( $n = 100 ) {
+ $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
+ $scopeLock = $this->store->getScopedLock( "$posKey:busy", 0 );
+ if ( !$scopeLock ) {
+ return 0;
+ }
+
+ $now = time();
+ $status = $this->store->get( $posKey );
+ if ( !$status ) {
+ $status = [ 'pos' => $now - $this->initialStartWindow, 'id' => null ];
+ }
+
+ // Get events for entities who's keys tombstones/hold-off should have expired by now
+ $events = call_user_func_array(
+ $this->logChunkCallback,
+ [ $status['pos'], $status['id'], $now - WANObjectCache::HOLDOFF_TTL - 1, $n ]
+ );
+
+ $event = null;
+ $keyEvents = [];
+ foreach ( $events as $event ) {
+ $keys = call_user_func_array(
+ $this->keyListCallback,
+ [ $this->cache, $event['item'] ]
+ );
+ foreach ( $keys as $key ) {
+ unset( $keyEvents[$key] ); // use only the latest per key
+ $keyEvents[$key] = [
+ 'pos' => $event['pos'],
+ 'id' => $event['id']
+ ];
+ }
+ }
+
+ $purgeCount = 0;
+ $lastOkEvent = null;
+ foreach ( $keyEvents as $key => $keyEvent ) {
+ if ( !$this->cache->reap( $key, $keyEvent['pos'] ) ) {
+ break;
+ }
+ ++$purgeCount;
+ $lastOkEvent = $event;
+ }
+
+ if ( $lastOkEvent ) {
+ $ok = $this->store->merge(
+ $posKey,
+ function ( $bag, $key, $curValue ) use ( $lastOkEvent ) {
+ if ( !$curValue ) {
+ // Use new position
+ } else {
+ $curCoord = [ $curValue['pos'], $curValue['id'] ];
+ $newCoord = [ $lastOkEvent['pos'], $lastOkEvent['id'] ];
+ if ( $newCoord < $curCoord ) {
+ // Keep prior position instead of rolling it back
+ return $curValue;
+ }
+ }
+
+ return [
+ 'pos' => $lastOkEvent['pos'],
+ 'id' => $lastOkEvent['id'],
+ 'ctime' => $curValue ? $curValue['ctime'] : date( 'c' )
+ ];
+ },
+ IExpiringStore::TTL_INDEFINITE
+ );
+
+ $pos = $lastOkEvent['pos'];
+ $id = $lastOkEvent['id'];
+ if ( $ok ) {
+ $this->logger->info( "Updated cache reap position ($pos, $id)." );
+ } else {
+ $this->logger->error( "Could not update cache reap position ($pos, $id)." );
+ }
+ }
+
+ ScopedCallback::consume( $scopeLock );
+
+ return $purgeCount;
+ }
+
+ /**
+ * @return array|bool Returns (pos, id) map or false if not set
+ */
+ public function getState() {
+ $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
+
+ return $this->store->get( $posKey );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Object caching using WinCache.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+/**
+ * Wrapper for WinCache object caching functions; identical interface
+ * to the APC wrapper
+ *
+ * @ingroup Cache
+ */
+class WinCacheBagOStuff extends MediumSpecificBagOStuff {
+ protected function doGet( $key, $flags = 0, &$casToken = null ) {
+ $casToken = null;
+
+ $blob = wincache_ucache_get( $key );
+ if ( !is_string( $blob ) && !is_int( $blob ) ) {
+ return false;
+ }
+
+ $value = $this->unserialize( $blob );
+ if ( $value !== false ) {
+ $casToken = (string)$blob; // don't bother hashing this
+ }
+
+ return $value;
+ }
+
+ protected function cas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
+ if ( !wincache_lock( $key ) ) { // optimize with FIFO lock
+ return false;
+ }
+
+ $curCasToken = null; // passed by reference
+ $this->doGet( $key, self::READ_LATEST, $curCasToken );
+ if ( $casToken === $curCasToken ) {
+ $success = $this->set( $key, $value, $exptime, $flags );
+ } else {
+ $this->logger->info(
+ __METHOD__ . ' failed due to race condition for {key}.',
+ [ 'key' => $key ]
+ );
+
+ $success = false; // mismatched or failed
+ }
+
+ wincache_unlock( $key );
+
+ return $success;
+ }
+
+ protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
+ $result = wincache_ucache_set( $key, $this->serialize( $value ), $exptime );
+
+ // false positive, wincache_ucache_set returns an empty array
+ // in some circumstances.
+ // @phan-suppress-next-line PhanTypeComparisonToArray
+ return ( $result === [] || $result === true );
+ }
+
+ public function add( $key, $value, $exptime = 0, $flags = 0 ) {
+ if ( wincache_ucache_exists( $key ) ) {
+ return false; // avoid warnings
+ }
+
+ $result = wincache_ucache_add( $key, $this->serialize( $value ), $exptime );
+
+ // false positive, wincache_ucache_add returns an empty array
+ // in some circumstances.
+ // @phan-suppress-next-line PhanTypeComparisonToArray
+ return ( $result === [] || $result === true );
+ }
+
+ protected function doDelete( $key, $flags = 0 ) {
+ wincache_ucache_delete( $key );
+
+ return true;
+ }
+
+ /**
+ * Construct a cache key.
+ *
+ * @since 1.27
+ * @param string $keyspace
+ * @param array $args
+ * @return string
+ */
+ public function makeKeyInternal( $keyspace, $args ) {
+ // WinCache keys have a maximum length of 150 characters. From that,
+ // subtract the number of characters we need for the keyspace and for
+ // the separator character needed for each argument. To handle some
+ // custom prefixes used by thing like WANObjectCache, limit to 125.
+ // NOTE: Same as in memcached, except the max key length there is 255.
+ $charsLeft = 125 - strlen( $keyspace ) - count( $args );
+
+ $args = array_map(
+ function ( $arg ) use ( &$charsLeft ) {
+ // 33 = 32 characters for the MD5 + 1 for the '#' prefix.
+ if ( $charsLeft > 33 && strlen( $arg ) > $charsLeft ) {
+ $arg = '#' . md5( $arg );
+ }
+
+ $charsLeft -= strlen( $arg );
+ return $arg;
+ },
+ $args
+ );
+
+ if ( $charsLeft < 0 ) {
+ return $keyspace . ':BagOStuff-long-key:##' . md5( implode( ':', $args ) );
+ }
+
+ return $keyspace . ':' . implode( ':', $args );
+ }
+
+ /**
+ * Increase stored value of $key by $value while preserving its original TTL
+ * @param string $key Key to increase
+ * @param int $value Value to add to $key (Default 1)
+ * @return int|bool New value or false on failure
+ */
+ public function incr( $key, $value = 1 ) {
+ if ( !wincache_lock( $key ) ) { // optimize with FIFO lock
+ return false;
+ }
+
+ $n = $this->doGet( $key );
+ if ( $this->isInteger( $n ) ) {
+ $n = max( $n + (int)$value, 0 );
+ $oldTTL = wincache_ucache_info( false, $key )["ucache_entries"][1]["ttl_seconds"];
+ $this->set( $key, $n, $oldTTL );
+ } else {
+ $n = false;
+ }
+
+ wincache_unlock( $key );
+
+ return $n;
+ }
+}