1111
1212namespace Symfony \Component \Cache \Traits ;
1313
14- use Psr \ Cache \CacheItemPoolInterface ;
14+ use Symfony \ Component \ Cache \Adapter \ AdapterInterface ;
1515use Symfony \Component \Cache \CacheItem ;
1616use Symfony \Component \Cache \Exception \InvalidArgumentException ;
1717use Symfony \Component \Cache \LockRegistry ;
18+ use Symfony \Contracts \Cache \CacheInterface ;
19+ use Symfony \Contracts \Cache \ItemInterface ;
1820
1921/**
20- * An implementation for CacheInterface that provides stampede protection via probabilistic early expiration.
21- *
22- * @see https://en.wikipedia.org/wiki/Cache_stampede
23- *
2422 * @author Nicolas Grekas <p@tchwork.com>
2523 *
2624 * @internal
2725 */
2826trait GetTrait
2927{
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+
3045 /**
3146 * {@inheritdoc}
3247 */
3348 public function get (string $ key , callable $ callback , float $ beta = null )
3449 {
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 );
4051 }
4152
42- private function doGet (CacheItemPoolInterface $ pool , string $ key , callable $ callback , float $ beta )
53+ private function doGet (AdapterInterface $ pool , string $ key , callable $ callback , ? float $ beta )
4354 {
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+
4559 $ t = 0 ;
4660 $ item = $ pool ->getItem ($ key );
4761 $ recompute = !$ item ->isHit () || INF === $ beta ;
4862
49- if ($ item instanceof CacheItem && 0 < $ beta ) {
63+ if (0 < $ beta ) {
5064 if ($ recompute ) {
5165 $ t = microtime (true );
5266 } else {
5367 $ 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 ;
5670
5771 if ($ ctime && $ expiry ) {
5872 $ t = microtime (true );
@@ -69,11 +83,32 @@ private function doGet(CacheItemPoolInterface $pool, string $key, callable $call
6983 return $ item ->get ();
7084 }
7185
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 );
75105 }
106+ $ this ->callbackWrapper = null ;
76107
77- return $ value ;
108+ try {
109+ return $ save ($ pool , $ item , $ callbackWrapper ($ item , $ callback , $ pool ), $ t );
110+ } finally {
111+ $ this ->callbackWrapper = $ callbackWrapper ;
112+ }
78113 }
79114}
0 commit comments