11
11
12
12
namespace Symfony \Component \Cache \Traits ;
13
13
14
- use Psr \ Cache \CacheItemPoolInterface ;
14
+ use Symfony \ Component \ Cache \Adapter \ AdapterInterface ;
15
15
use Symfony \Component \Cache \CacheItem ;
16
16
use Symfony \Component \Cache \Exception \InvalidArgumentException ;
17
17
use Symfony \Component \Cache \LockRegistry ;
18
+ use Symfony \Contracts \Cache \CacheInterface ;
19
+ use Symfony \Contracts \Cache \ItemInterface ;
18
20
19
21
/**
20
- * An implementation for CacheInterface that provides stampede protection via probabilistic early expiration.
21
- *
22
- * @see https://en.wikipedia.org/wiki/Cache_stampede
23
- *
24
22
* @author Nicolas Grekas <p@tchwork.com>
25
23
*
26
24
* @internal
27
25
*/
28
26
trait GetTrait
29
27
{
28
+ private $ callbackWrapper = array (LockRegistry::class, 'compute ' );
29
+
30
+ /**
31
+ * Wraps the callback passed to ->get() in a callable.
32
+ *
33
+ * @param callable(ItemInterface, callable, CacheInterface):mixed $callbackWrapper
34
+ *
35
+ * @return callable the previous callback wrapper
36
+ */
37
+ public function setCallbackWrapper (callable $ callbackWrapper ): callable
38
+ {
39
+ $ previousWrapper = $ this ->callbackWrapper ;
40
+ $ this ->callbackWrapper = $ callbackWrapper ;
41
+
42
+ return $ previousWrapper ;
43
+ }
44
+
30
45
/**
31
46
* {@inheritdoc}
32
47
*/
33
48
public function get (string $ key , callable $ callback , float $ beta = null )
34
49
{
35
- if (0 > $ beta ) {
36
- throw new InvalidArgumentException (sprintf ('Argument "$beta" provided to "%s::get()" must be a positive number, %f given. ' , \get_class ($ this ), $ beta ));
37
- }
38
-
39
- return $ this ->doGet ($ this , $ key , $ callback , $ beta ?? 1.0 );
50
+ return $ this ->doGet ($ this , $ key , $ callback , $ beta );
40
51
}
41
52
42
- private function doGet (CacheItemPoolInterface $ pool , string $ key , callable $ callback , float $ beta )
53
+ private function doGet (AdapterInterface $ pool , string $ key , callable $ callback , ? float $ beta )
43
54
{
44
- retry:
55
+ if (0 > $ beta = $ beta ?? 1.0 ) {
56
+ throw new InvalidArgumentException (sprintf ('Argument "$beta" provided to "%s::get()" must be a positive number, %f given. ' , \get_class ($ this ), $ beta ));
57
+ }
58
+
45
59
$ t = 0 ;
46
60
$ item = $ pool ->getItem ($ key );
47
61
$ recompute = !$ item ->isHit () || INF === $ beta ;
48
62
49
- if ($ item instanceof CacheItem && 0 < $ beta ) {
63
+ if (0 < $ beta ) {
50
64
if ($ recompute ) {
51
65
$ t = microtime (true );
52
66
} else {
53
67
$ metadata = $ item ->getMetadata ();
54
- $ expiry = $ metadata [CacheItem ::METADATA_EXPIRY ] ?? false ;
55
- $ ctime = $ metadata [CacheItem ::METADATA_CTIME ] ?? false ;
68
+ $ expiry = $ metadata [ItemInterface ::METADATA_EXPIRY ] ?? false ;
69
+ $ ctime = $ metadata [ItemInterface ::METADATA_CTIME ] ?? false ;
56
70
57
71
if ($ ctime && $ expiry ) {
58
72
$ t = microtime (true );
@@ -69,11 +83,32 @@ private function doGet(CacheItemPoolInterface $pool, string $key, callable $call
69
83
return $ item ->get ();
70
84
}
71
85
72
- if (!LockRegistry::save ($ key , $ pool , $ item , $ callback , $ t , $ value )) {
73
- $ beta = 0 ;
74
- goto retry;
86
+ static $ save ;
87
+
88
+ $ save = $ save ?? \Closure::bind (
89
+ function (AdapterInterface $ pool , ItemInterface $ item , $ value , float $ startTime ) {
90
+ if ($ startTime && $ item ->expiry > $ endTime = microtime (true )) {
91
+ $ item ->newMetadata [ItemInterface::METADATA_EXPIRY ] = $ item ->expiry ;
92
+ $ item ->newMetadata [ItemInterface::METADATA_CTIME ] = 1000 * (int ) ($ endTime - $ startTime );
93
+ }
94
+ $ pool ->save ($ item ->set ($ value ));
95
+
96
+ return $ value ;
97
+ },
98
+ null ,
99
+ CacheItem::class
100
+ );
101
+
102
+ // don't wrap nor save recursive calls
103
+ if (null === $ callbackWrapper = $ this ->callbackWrapper ) {
104
+ return $ callback ($ item );
75
105
}
106
+ $ this ->callbackWrapper = null ;
76
107
77
- return $ value ;
108
+ try {
109
+ return $ save ($ pool , $ item , $ callbackWrapper ($ item , $ callback , $ pool ), $ t );
110
+ } finally {
111
+ $ this ->callbackWrapper = $ callbackWrapper ;
112
+ }
78
113
}
79
114
}
0 commit comments