8000 Introduce LockMiddleware · symfony/symfony@0b67664 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0b67664

Browse files
Introduce LockMiddleware
1 parent 272f021 commit 0b67664

File tree

8 files changed

+239
-0
lines changed

8 files changed

+239
-0
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -2095,6 +2095,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
20952095
['id' => 'reject_redelivered_message_middleware'],
20962096
['id' => 'dispatch_after_current_bus'],
20972097
['id' => 'failed_message_processing_middleware'],
2098+
['id' => 'lock_middleware'],
20982099
],
20992100
'after' => [
21002101
['id' => 'send_message'],

src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware;
2929
use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware;
3030
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
31+
use Symfony\Component\Messenger\Middleware\LockMiddleware;
3132
use Symfony\Component\Messenger\Middleware\RejectRedeliveredMessageMiddleware;
3233
use Symfony\Component\Messenger\Middleware\RouterContextMiddleware;
3334
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
@@ -86,6 +87,11 @@
8687
->tag('monolog.logger', ['channel' => 'messenger'])
8788
->call('setLogger', [service('logger')->ignoreOnInvalid()])
8889

90+
->set('messenger.middleware.lock_middleware', LockMiddleware::class)
91+
->args([
92+
service('lock.factory')->nullOnInvalid(),
93+
])
94+
8995
->set('messenger.middleware.add_bus_name_stamp_middleware', AddBusNameStampMiddleware::class)
9096
->abstract()
9197

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Messenger\Message;
13+
14+
use Symfony\Component\Lock\Key;
15+
16+
interface LockableMessage
17+
{
18+
/**
19+
* Returns null if you want to force the dispatch of the message.
20+
*/
21+
public function getKey(): ?Key;
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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\Messenger\Middleware;
13+
14+
use Symfony\Component\Lock\LockFactory;
15+
use Symfony\Component\Messenger\Envelope;
16+
use Symfony\Component\Messenger\Message\LockableMessage;
17+
use Symfony\Component\Messenger\Stamp\LockStamp;
18+
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
19+
20+
final class LockMiddleware implements MiddlewareInterface
21+
{
22+
private ?LockFactory $lockFactory = null;
23+
24+
public function __construct(
25+
?LockFactory $lockFactory,
26+
) {
27+
$this->lockFactory = $lockFactory;
28+
}
29+
30+
public function handle(Envelope $envelope, StackInterface $stack): Envelope
31+
{
32+
if (null === $this->lockFactory) {
33+
return $stack->next()->handle($envelope, $stack);
34+
}
35+
36+
$message = $envelope->getMessage();
37+
38+
// If we're trying to dispatch a lockable message.
39+
if ($message instanceof LockableMessage && null === $envelope->last(ReceivedStamp::class)) {
40+
$key = $message->getKey();
41+
42+
if (null !== $key) {
43+
// The acquire call must be done before stamping the message
44+
// in order to have the full state of the key in the stamp.
45+
$canAcquire = $this->lockFactory->createLockFromKey($key, autoRelease: false)->acquire();
46+
47+
$envelope = $envelope->with(new LockStamp($key));
48+
if (!$canAcquire) {
49+
return $envelope;
50+
}
51+
}
52+
}
53+
54+
try {
55+
$envelope = $stack->next()->handle($envelope, $stack);
56+
} finally {
57+
// If we've received a lockable message, we're releasing it.
58+
if (null !== $envelope->last(ReceivedStamp::class)) {
59+
$stamp = $envelope->last(LockStamp::class);
60+
if ($stamp instanceof LockStamp) {
61+
$this->lockFactory->createLockFromKey($stamp->getKey(), autoRelease: false)->release();
62+
}
63+
}
64+
}
65+
66+
return $envelope;
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\Messenger\Stamp;
13+
14+
use Symfony\Component\Lock\Key;
15+
16+
final class LockStamp implements StampInterface
17+
{
18+
private Key $key;
19+
20+
public function __construct(Key $key)
21+
{
22+
$this->key = $key;
23+
}
24+
25+
public function getKey(): Key
26+
{
27+
return $this->key;
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Symfony\Component\Messenger\Tests\Fixtures;
4+
5+
use Symfony\Component\Lock\Key;
6+
use Symfony\Component\Messenger\Message\LockableMessage;
7+
8+
class DummyLockableMessage implements DummyMessageInterface, LockableMessage
9+
{
10+
private string $message;
11+
12+
private ?Key $key;
13+
14+
public function __construct(string $message, ?Key $key)
15+
{
16+
$this->message = $message;
17+
$this->key = $key;
18+
}
19+
20+
public function getMessage(): string
21+
{
22+
return $this->message;
23+
}
24+
25+
public function getKey(): ?Key
26+
{
27+
return $this->key;
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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\Messenger\Tests\Middleware;
13+
14+
use Symfony\Component\Lock\Key;
15+
use Symfony\Component\Lock\LockFactory;
16+
use Symfony\Component\Lock\Store\FlockStore;
17+
use Symfony\Component\Lock\Store\SemaphoreStore;
18+
use Symfony\Component\Messenger\Envelope;
19+
use Symfony\Component\Messenger\Middleware\LockMiddleware;
20+
use Symfony\Component\Messenger\Stamp\LockStamp;
21+
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
22+
use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase;
23+
use Symfony\Component\Messenger\Tests\Fixtures\DummyLockableMessage;
24+
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
25+
26+
final class LockMiddlewareTest extends MiddlewareTestCase
27+
{
28+
public function testLockMiddlewareIgnoreIfMessageIsNotLockable()
29+
{
30+
$message = new DummyMessage('Hello');
31+
$envelope = new Envelope($message);
32+
33+
$lockFactory = $this->createMock(LockFactory::class);
34+
$lockFactory->expects($this->never())->method('createLock');
35+
36+
$decorator = new LockMiddleware($lockFactory);
37+
38+
$decorator->handle($envelope, $this->getStackMock(true));
39+
}
40+
41+
public function testLockMiddlewareIgnoreIfMessageHasNoKey()
42+
{
43+
$message = new DummyLockableMessage('Hello', null);
44+
$envelope = new Envelope($message);
45+
46+
$lockFactory = $this->createMock(LockFactory::class);
47+
$lockFactory->expects($this->never())->method('createLock');
48+
49+
$decorator = new LockMiddleware($lockFactory);
50+
51+
$decorator->handle($envelope, $this->getStackMock(true));
52+
}
53+
54+
public function testLockMiddlewareIfMessageHasKey()
55+
{
56+
$message = new DummyLockableMessage('Hello', new Key('id'));
57+
$envelope = new Envelope($message);
58+
59+
if (SemaphoreStore::isSupported()) {
60+
$store = new SemaphoreStore();
61+
} else {
62+
$store = new FlockStore();
63+
}
64+
65+
$decorator = new LockMiddleware(new LockFactory($store));
66+
67+
$envelope = $decorator->handle($envelope, $this->getStackMock(true));
68+
$this->assertNotNull($envelope->last(LockStamp::class));
69+
70+
$message2 = new DummyLockableMessage('Hello', new Key('id'));
71+
$envelope2 = new Envelope($message2);
72+
73+
$decorator->handle($envelope2, $this->getStackMock(false));
74+
75+
// Simulate receiving the first message
76+
$envelope = $envelope->with(new ReceivedStamp('transport'));
77+
$decorator->handle($envelope, $this->getStackMock(true));
78+
79+
$message3 = new DummyLockableMessage('Hello', new Key('id'));
80+
$envelope3 = new Envelope($message3);
81+
$decorator->handle($envelope3, $this->getStackMock(true));
82+
}
83+
}

src/Symfony/Component/Messenger/composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"symfony/http-kernel": "^6.4|^7.0",
2929
"symfony/process": "^6.4|^7.0",
3030
"symfony/property-access": "^6.4|^7.0",
31+
"symfony/lock": "^6.4|^7.0",
3132
"symfony/rate-limiter": "^6.4|^7.0",
3233
"symfony/routing": "^6.4|^7.0",
3334
"symfony/serializer": "^6.4|^7.0",

0 commit comments

Comments
 (0)
0