Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
APCUBagOStuff
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 6
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 doGet
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 doSet
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 doAdd
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 doDelete
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 doIncrWithInit
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6namespace Wikimedia\ObjectCache;
7
8/**
9 * Store data in the local server memory via APCu (php-apcu)
10 *
11 * Past issues of note:
12 * - Memory corruption when `apc.serializer=default` in INI:
13 *   https://phabricator.wikimedia.org/T120267
14 * - We used to recommend `apc.serializer=php` as non-default setting, and if not set,
15 *   applied serialize() manually to workaround bugs and to create values we can use
16 *   as CAS tokens. Upstream defaults to serializer=php since php-apcu 5.1.15 (2018).
17 *   https://gerrit.wikimedia.org/r/671634
18 *
19 * @see https://www.php.net/apcu
20 * @ingroup Cache
21 */
22class APCUBagOStuff extends MediumSpecificBagOStuff {
23    /**
24     * @var string String to append to each APC key. This may be changed
25     *  whenever the handling of values is changed, to prevent existing code
26     *  from encountering older values which it cannot handle.
27     */
28    private const KEY_SUFFIX = ':5';
29
30    /** @var int Max attempts for implicit CAS operations */
31    private static $CAS_MAX_ATTEMPTS = 100;
32
33    public function __construct( array $params = [] ) {
34        // No use in segmenting values
35        $params['segmentationSize'] = INF;
36        parent::__construct( $params );
37        // Versions of apcu < 5.1.19 use apc.use_request_time=1 by default, causing new keys
38        // to be assigned timestamps based on the start of the PHP request/script. The longer
39        // the request has been running, the more likely that newly stored keys will instantly
40        // be seen as expired by other requests. Disable apc.use_request_time.
41        ini_set( 'apc.use_request_time', '0' );
42
43        if ( PHP_SAPI === 'cli' ) {
44            $this->attrMap[self::ATTR_DURABILITY] = ini_get( 'apc.enable_cli' )
45                ? self::QOS_DURABILITY_SCRIPT
46                : self::QOS_DURABILITY_NONE;
47        } else {
48            $this->attrMap[self::ATTR_DURABILITY] = self::QOS_DURABILITY_SERVICE;
49        }
50    }
51
52    /** @inheritDoc */
53    protected function doGet( $key, $flags = 0, &$casToken = null ) {
54        $getToken = ( $casToken === self::PASS_BY_REF );
55        $casToken = null;
56
57        $value = apcu_fetch( $key . self::KEY_SUFFIX );
58        if ( $getToken && $value !== false ) {
59            // Note that if the driver handles serialization then this uses the PHP value
60            // as the token. This might require inspection or re-serialization in doCas().
61            $casToken = $value;
62        }
63
64        return $value;
65    }
66
67    /** @inheritDoc */
68    protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
69        $ttl = $this->getExpirationAsTTL( $exptime );
70
71        return apcu_store( $key . self::KEY_SUFFIX, $value, $ttl );
72    }
73
74    /** @inheritDoc */
75    protected function doAdd( $key, $value, $exptime = 0, $flags = 0 ) {
76        if ( apcu_exists( $key . self::KEY_SUFFIX ) ) {
77            // Avoid global write locks for high contention keys
78            return false;
79        }
80
81        $ttl = $this->getExpirationAsTTL( $exptime );
82
83        return apcu_add( $key . self::KEY_SUFFIX, $value, $ttl );
84    }
85
86    /** @inheritDoc */
87    protected function doDelete( $key, $flags = 0 ) {
88        apcu_delete( $key . self::KEY_SUFFIX );
89
90        return true;
91    }
92
93    /** @inheritDoc */
94    protected function doIncrWithInit( $key, $exptime, $step, $init, $flags ) {
95        // Use apcu 5.1.12 $ttl argument if apcu_inc() will initialize to $init:
96        // https://www.php.net/manual/en/function.apcu-inc.php
97        if ( $step === $init ) {
98            /** @noinspection PhpMethodParametersCountMismatchInspection */
99            $ttl = $this->getExpirationAsTTL( $exptime );
100            $result = apcu_inc( $key . self::KEY_SUFFIX, $step, $success, $ttl );
101        } else {
102            $result = false;
103            for ( $i = 0; $i < self::$CAS_MAX_ATTEMPTS; ++$i ) {
104                $oldCount = apcu_fetch( $key . self::KEY_SUFFIX );
105                if ( $oldCount === false ) {
106                    $count = $init;
107                    $ttl = $this->getExpirationAsTTL( $exptime );
108                    if ( apcu_add( $key . self::KEY_SUFFIX, $count, $ttl ) ) {
109                        $result = $count;
110                        break;
111                    }
112                } elseif ( is_int( $oldCount ) ) {
113                    $count = $oldCount + $step;
114                    if ( apcu_cas( $key . self::KEY_SUFFIX, $oldCount, $count ) ) {
115                        $result = $count;
116                        break;
117                    }
118                } else {
119                    break;
120                }
121            }
122        }
123
124        return $result;
125    }
126}
127
128/** @deprecated class alias since 1.43 */
129class_alias( APCUBagOStuff::class, 'APCUBagOStuff' );