Skip to content

Commit bac41f9

Browse files
[Cache] add integration with Messenger to allow computing cached values in a worker
1 parent ac1e429 commit bac41f9

File tree

12 files changed

+283
-12
lines changed

12 files changed

+283
-12
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,9 @@ private function addCacheSection(ArrayNodeDefinition $rootNode)
905905
->scalarNode('provider')
906906
->info('Overwrite the setting from the default provider for this adapter.')
907907
->end()
908+
->scalarNode('messenger_bus')
909+
->example('"message_bus" to send early expiration events to the default Messenger bus.')
910+
->end()
908911
->scalarNode('clearer')->end()
909912
->end()
910913
->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand;
111111
use Symfony\Component\Yaml\Yaml;
112112
use Symfony\Contracts\Cache\CacheInterface;
113+
use Symfony\Contracts\Cache\CallbackInterface;
113114
use Symfony\Contracts\Service\ResetInterface;
114115
use Symfony\Contracts\Service\ServiceSubscriberInterface;
115116

@@ -327,6 +328,8 @@ public function load(array $configs, ContainerBuilder $container)
327328
->addTag('config_cache.resource_checker');
328329
$container->registerForAutoconfiguration(EnvVarProcessorInterface::class)
329330
->addTag('container.env_var_processor');
331+
$container->registerForAutoconfiguration(CallbackInterface::class)
332+
->addTag('container.reversible');
330333
$container->registerForAutoconfiguration(ServiceLocator::class)
331334
->addTag('container.service_locator');
332335
$container->registerForAutoconfiguration(ServiceSubscriberInterface::class)

src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@
138138
<argument>null</argument> <!-- use igbinary_serialize() when available -->
139139
</service>
140140

141+
<service id="cache.message_handler" class="Symfony\Component\Cache\Messenger\Handler">
142+
<tag name="messenger.message_handler" />
143+
<argument type="service" id="reverse_container" />
144+
</service>
145+
141146
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer">
142147
<argument type="collection" />
143148
</service>

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@
264264
<xsd:attribute name="public" type="xsd:boolean" />
265265
<xsd:attribute name="default-lifetime" type="xsd:integer" />
266266
<xsd:attribute name="provider" type="xsd:string" />
267+
<xsd:attribute name="messenger-bus" type="xsd:string" />
267268
<xsd:attribute name="clearer" type="xsd:string" />
268269
</xsd:complexType>
269270

src/Symfony/Component/Cache/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it
88
* deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead
99
* deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead
10+
* added integration with Messenger to allow computing cached values in a worker
1011

1112
4.2.0
1213
-----

src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Cache\Adapter\AbstractAdapter;
1515
use Symfony\Component\Cache\Adapter\ArrayAdapter;
16+
use Symfony\Component\Cache\Messenger\Dispatcher;
1617
use Symfony\Component\DependencyInjection\ChildDefinition;
1718
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -31,15 +32,21 @@ class CachePoolPass implements CompilerPassInterface
3132
private $cachePoolClearerTag;
3233
private $cacheSystemClearerId;
3334
private $cacheSystemClearerTag;
35+
private $reverseContainerId;
36+
private $reversibleTag;
37+
private $messageHandlerId;
3438

35-
public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer')
39+
public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer', string $reverseContainerId = 'reverse_container', string $reversibleTag = 'container.reversible', string $messageHandlerId = 'cache.message_handler')
3640
{
3741
$this->cachePoolTag = $cachePoolTag;
3842
$this->kernelResetTag = $kernelResetTag;
3943
$this->cacheClearerId = $cacheClearerId;
4044
$this->cachePoolClearerTag = $cachePoolClearerTag;
4145
$this->cacheSystemClearerId = $cacheSystemClearerId;
4246
$this->cacheSystemClearerTag = $cacheSystemClearerTag;
47+
$this->reverseContainerId = $reverseContainerId;
48+
$this->reversibleTag = $reversibleTag;
49+
$this->messageHandlerId = $messageHandlerId;
4350
}
4451

4552
/**
@@ -54,13 +61,15 @@ public function process(ContainerBuilder $container)
5461
}
5562
$seed .= '.'.$container->getParameter('kernel.container_class');
5663

64+
$needsMessageHandler = false;
5765
$pools = [];
5866
$clearers = [];
5967
$attributes = [
6068
'provider',
6169
'name',
6270
'namespace',
6371
'default_lifetime',
72+
'messenger_bus',
6473
'reset',
6574
];
6675
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
@@ -99,13 +108,24 @@ public function process(ContainerBuilder $container)
99108
if ($tags[0][$attr]) {
100109
$pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]);
101110
}
111+
} elseif ('messenger_bus' === $attr) {
112+
$needsMessageHandler = true;
113+
$pool->addMethodCall('setCallbackWrapper', [(new Definition(Dispatcher::class))
114+
->addArgument(new Reference($tags[0]['messenger_bus']))
115+
->addArgument(new Reference($this->reverseContainerId))
116+
->addArgument((new Definition('callable'))
117+
->setFactory([new Reference($id), 'setCallbackWrapper'])
118+
->addArgument(null)
119+
),
120+
]);
121+
$pool->addTag($this->reversibleTag);
102122
} elseif ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass()) {
103123
$pool->replaceArgument($i++, $tags[0][$attr]);
104124
}
105125
unset($tags[0][$attr]);
106126
}
107127
if (!empty($tags[0])) {
108-
throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0]))));
128+
throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "messenger_bus" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0]))));
109129
}
110130

111131
if (null !== $clearer) {
@@ -115,6 +135,10 @@ public function process(ContainerBuilder $container)
115135
$pools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
116136
}
117137

138+
if (!$needsMessageHandler) {
139+
$container->removeDefinition($this->messageHandlerId);
140+
}
141+
118142
$notAliasedCacheClearerId = $this->cacheClearerId;
119143
while ($container->hasAlias($this->cacheClearerId)) {
120144
$this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId);

src/Symfony/Component/Cache/LockRegistry.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Cache;
1313

14+
use Psr\Log\LoggerInterface;
1415
use Symfony\Contracts\Cache\CacheInterface;
1516
use Symfony\Contracts\Cache\ItemInterface;
1617

@@ -23,7 +24,7 @@
2324
*
2425
* @author Nicolas Grekas <p@tchwork.com>
2526
*/
26-
class LockRegistry
27+
final class LockRegistry
2728
{
2829
private static $openedFiles = [];
2930
private static $lockedFiles = [];
@@ -75,7 +76,7 @@ public static function setFiles(array $files): array
7576
return $previousFiles;
7677
}
7778

78-
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool)
79+
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, LoggerInterface $logger = null)
7980
{
8081
$key = self::$files ? crc32($item->getKey()) % \count(self::$files) : -1;
8182

@@ -87,11 +88,20 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s
8788
try {
8889
// race to get the lock in non-blocking mode
8990
if (flock($lock, LOCK_EX | LOCK_NB)) {
91+
$logger && $logger->info('Lock acquired, now computing item "{key}"', ['key' => $item->getKey()]);
9092
self::$lockedFiles[$key] = true;
9193

92-
return $callback($item, $save);
94+
$value = $callback($item, $save);
95+
96+
if ($save) {
97+
$pool->save($item->set($value));
98+
$save = false;
99+
}
100+
101+
return $value;
93102
}
94103
// if we failed the race, retry locking in blocking mode to wait for the winner
104+
$logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
95105
flock($lock, LOCK_SH);
96106
} finally {
97107
flock($lock, LOCK_UN);
@@ -103,13 +113,15 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s
103113

104114
try {
105115
$value = $pool->get($item->getKey(), $signalingCallback, 0);
116+
$logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
106117
$save = false;
107118

108119
return $value;
109120
} catch (\Exception $e) {
110121
if ($signalingException !== $e) {
111122
throw $e;
112123
}
124+
$logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
113125
}
114126
}
115127
}
@@ -126,6 +138,6 @@ private static function open(int $key)
126138
restore_error_handler();
127139
}
128140

129-
self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
141+
return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
130142
}
131143
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Messenger;
13+
14+
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\Cache\Adapter\AdapterInterface;
16+
use Symfony\Component\Cache\CacheItem;
17+
use Symfony\Component\Cache\Stamp\HandledStamp;
18+
use Symfony\Component\DependencyInjection\ReverseContainer;
19+
use Symfony\Component\Messenger\MessageBusInterface;
20+
21+
/**
22+
* Sends the computation of cached values to a message bus.
23+
*/
24+
class Dispatcher
25+
{
26+
private $bus;
27+
private $reverseContainer;
28+
private $callbackWrapper;
29+
30+
public function __construct(MessageBusInterface $bus, ReverseContainer $reverseContainer, callable $callbackWrapper = null)
31+
{
32+
$this->bus = $bus;
33+
$this->reverseContainer = $reverseContainer;
34+
$this->callbackWrapper = $callbackWrapper;
35+
}
36+
37+
public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, LoggerInterface $logger = null)
38+
{
39+
if (!$item->isHit() || null === $message = Message::create($this->reverseContainer, $callback, $item, $pool)) {
40+
// The item is stale or the callback cannot be reversed: we must compute the value now
41+
$logger && $logger->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]);
42+
43+
return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $logger) : $callback($item, $save);
44+
}
45+
46+
$envelope = $this->bus->dispatch($message);
47+
48+
if ($logger) {
49+
if ($envelope->last(HandledStamp::class)) {
50+
$logger->info('Item "{key}" was computed online', ['key' => $item->getKey()]);
51+
} else {
52+
$logger->info('Item "{key}" sent for offline computation', ['key' => $item->getKey()]);
53+
}
54+
}
55+
56+
// The item's value is not stale, no need to write it to the backend
57+
$save = false;
58+
59+
return $message->getItem()->get() ?? $item->get();
60+
}
61+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Messenger;
13+
14+
use Symfony\Component\Cache\CacheItem;
15+
use Symfony\Component\DependencyInjection\ReverseContainer;
16+
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
17+
18+
/**
19+
* Computes cached values sent to a message bus.
20+
*/
21+
class Handler implements MessageHandlerInterface
22+
{
23+
private $reverseContainer;
24+
private $processedNonces = [];
25+
26+
public function __construct(ReverseContainer $reverseContainer)
27+
{
28+
$this->reverseContainer = $reverseContainer;
29+
}
30+
31+
public function __invoke(Message $message)
32+
{
33+
$item = $message->getItem();
34+
$metadata = $item->getMetadata();
35+
$expiry = $metadata[CacheItem::METADATA_EXPIRY] ?? 0;
36+
$ctime = $metadata[CacheItem::METADATA_CTIME] ?? 0;
37+
38+
if ($expiry && $ctime) {
39+
// skip duplicate or expired messages
40+
41+
$processingNonce = [$expiry, $ctime];
42+
$pool = $message->getPool();
43+
$key = $item->getKey();
44+
45+
if (($this->processedNonces[$pool][$key] ?? null) === $processingNonce) {
46+
return;
47+
}
48+
49+
if (microtime(true) >= $expiry) {
50+
return;
51+
}
52+
53+
$this->processedNonces[$pool] = [$key => $processingNonce] + ($this->processedNonces[$pool] ?? []);
54+
55+
if (\count($this->processedNonces[$pool]) > 100) {
56+
array_pop($this->processedNonces[$pool]);
57+
}
58+
}
59+
60+
$pool = $message->findPool($this->reverseContainer);
61+
$callback = $message->findCallback($this->reverseContainer);
62+
$value = $callback($item);
63+
$pool->save($item->set($value));
64+
}
65+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy