Skip to content

Commit 85ac760

Browse files
[Cache] add integration with Messenger to allow computing cached values in a worker
1 parent 755f411 commit 85ac760

File tree

9 files changed

+277
-2
lines changed

9 files changed

+277
-2
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,9 @@ private function addCacheSection(ArrayNodeDefinition $rootNode)
959959
->scalarNode('provider')
960960
->info('Overwrite the setting from the default provider for this adapter.')
961961
->end()
962+
->scalarNode('messenger_bus')
963+
->example('"message_bus" to send early expiration events to the default Messenger bus.')
964+
->end()
962965
->scalarNode('clearer')->end()
963966
->end()
964967
->end()

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand;
118118
use Symfony\Component\Yaml\Yaml;
119119
use Symfony\Contracts\Cache\CacheInterface;
120+
use Symfony\Contracts\Cache\CallbackInterface;
120121
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
121122
use Symfony\Contracts\HttpClient\HttpClientInterface;
122123
use Symfony\Contracts\Service\ResetInterface;
@@ -348,6 +349,8 @@ public function load(array $configs, ContainerBuilder $container)
348349
->addTag('config_cache.resource_checker');
349350
$container->registerForAutoconfiguration(EnvVarProcessorInterface::class)
350351
->addTag('container.env_var_processor');
352+
$container->registerForAutoconfiguration(CallbackInterface::class)
353+
->addTag('container.reversible');
351354
$container->registerForAutoconfiguration(ServiceLocator::class)
352355
->addTag('container.service_locator');
353356
$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
@@ -142,6 +142,11 @@
142142
<argument>null</argument> <!-- use igbinary_serialize() when available -->
143143
</service>
144144

145+
<service id="cache.message_handler" class="Symfony\Component\Cache\Messenger\Handler">
146+
<tag name="messenger.message_handler" />
147+
<argument type="service" id="reverse_container" />
148+
</service>
149+
145150
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer">
146151
<argument type="collection" />
147152
</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
@@ -273,6 +273,7 @@
273273
<xsd:attribute name="public" type="xsd:boolean" />
274274
<xsd:attribute name="default-lifetime" type="xsd:integer" />
275275
<xsd:attribute name="provider" type="xsd:string" />
276+
<xsd:attribute name="messenger-bus" type="xsd:string" />
276277
<xsd:attribute name="clearer" type="xsd:string" />
277278
</xsd:complexType>
278279

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);
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, \Closure $setMetadata, ?LoggerInterface $logger)
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, $setMetadata, $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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
static $setMetadata;
61+
62+
$setMetadata = $setMetadata ?? \Closure::bind(
63+
function (CacheItem $item, float $startTime) {
64+
if ($item->expiry > $endTime = microtime(true)) {
65+
$item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry;
66+
$item->newMetadata[CacheItem::METADATA_CTIME] = 1000 * (int) ($endTime - $startTime);
67+
}
68+
},
69+
null,
70+
CacheItem::class
71+
);
72+
73+
$startTime = microtime(true);
74+
$pool = $message->findPool($this->reverseContainer);
75+
$callback = $message->findCallback($this->reverseContainer);
76+
$value = $callback($item);
77+
$setMetadata($item, $startTime);
78+
$pool->save($item->set($value));
79+
}
80+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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\Adapter\AdapterInterface;
15+
use Symfony\Component\Cache\CacheItem;
16+
use Symfony\Component\DependencyInjection\ReverseContainer;
17+
18+
/**
19+
* Conveys a cached value that needs to be computed.
20+
*/
21+
final class Message
22+
{
23+
private $item;
24+
private $pool;
25+
private $callback;
26+
27+
public static function create(ReverseContainer $reverseContainer, callable $callback, CacheItem $item, AdapterInterface $pool): ?self
28+
{
29+
try {
30+
$item = clone $item;
31+
$item->set(null);
32+
} catch (\Exception $e) {
33+
return null;
34+
}
35+
36+
$pool = $reverseContainer->getId($pool);
37+
38+
if (\is_object($callback)) {
39+
if (null === $id = $reverseContainer->getId($callback)) {
40+
return null;
41+
}
42+
43+
$callback = '@'.$id;
44+
} elseif (!\is_array($callback)) {
45+
$callback = (string) $callback;
46+
} elseif (!\is_object($callback[0])) {
47+
$callback = [(string) $callback[0], (string) $callback[1]];
48+
} else {
49+
if (null === $id = $reverseContainer->getId($callback[0])) {
50+
return null;
51+
}
52+
53+
$callback = ['@'.$id, (string) $callback[1]];
54+
}
55+
56+
return new self($item, $pool, $callback);
57+
}
58+
59+
public function getItem(): CacheItem
60+
{
61+
return $this->item;
62+
}
63+
64+
public function getPool(): string
65+
{
66+
return $this->pool;
67+
}
68+
69+
public function getCallback()
70+
{
71+
return $this->callback;
72+
}
73+
74+
public function findPool(ReverseContainer $reverseContainer): AdapterInterface
75+
{
76+
return $reverseContainer->getService($this->pool);
77+
}
78+
79+
public function findCallback(ReverseContainer $reverseContainer): callable
80+
{
81+
if (\is_string($callback = $this->callback)) {
82+
return '@' === $callback[0] ? $reverseContainer->getService(substr($callback, 1)) : $callback;
83+
}
84+
if ('@' === $callback[0][0]) {
85+
$callback[0] = $reverseContainer->getService(substr($callback[0], 1));
86+
}
87+
88+
return $callback;
89+
}
90+
91+
private function __construct(CacheItem $item, string $pool, $callback)
92+
{
93+
$this->item = $item;
94+
$this->pool = $pool;
95+
$this->callback = $callback;
96+
}
97+
}

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