From 9c71a0eebee569091bf092ed5853f02afbcc11ef Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 10 Sep 2023 22:16:05 -0500 Subject: [PATCH 1/7] Issue 51564 - cloned TraceableStack operates differently than StackMiddleware Tested fixing the issue by implementing __clone() for TraceableStack. --- .../Middleware/TraceableMiddleware.php | 6 +++++ .../Middleware/TraceableMiddlewareTest.php | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php b/src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php index 075916d7823e4..6e9ddae498e7e 100644 --- a/src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php @@ -88,4 +88,10 @@ public function stop(): void } $this->currentEvent = null; } + + public function __clone(): void + { + $this->stack = clone $this->stack; + $this->stopwatch = clone $this->stopwatch; + } } diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php index a0006bbf05e07..3871dee85e92d 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php @@ -16,11 +16,14 @@ use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Middleware\StackMiddleware; use Symfony\Component\Messenger\Middleware\TraceableMiddleware; +use Symfony\Component\Messenger\Middleware\TraceableStack; use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\StopwatchEvent; +class_exists(TraceableMiddleware::class); + /** * @author Maxime Steinhausser */ @@ -140,4 +143,26 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope $traced->handle($envelope, new StackMiddleware(new \ArrayIterator([null, $middleware]))); $this->assertSame(1, $middleware->calls); } + + public function testCloneTraceableStack(): void + { + $middlewareIterator = [ + $this->createMock(MiddlewareInterface::class), + $this->createMock(MiddlewareInterface::class), + ]; + + $stack = new TraceableStack( + new StackMiddleware($middlewareIterator), + $this->createMock(Stopwatch::class), + 'command.bus', + 'test' + ); + + $clonedStack = clone $stack; + + $nextFrame = $stack->next(); + $clonedStackNextFrame = $clonedStack->next(); + + self::assertSame($nextFrame, $clonedStackNextFrame); + } } From 1e44f0bb0ab1096de36523839094b97ab3c6fc74 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 12 Sep 2023 21:20:43 -0500 Subject: [PATCH 2/7] Don't clone stopwatch since the clone will not be utilized by the profiler or anything else --- .../Component/Messenger/Middleware/TraceableMiddleware.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php b/src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php index 6e9ddae498e7e..68ddeb424539b 100644 --- a/src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php @@ -92,6 +92,5 @@ public function stop(): void public function __clone(): void { $this->stack = clone $this->stack; - $this->stopwatch = clone $this->stopwatch; } } From e46dba85b6b2c40cf9a97437dab8ad4fe1c7441a Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 12 Sep 2023 22:12:36 -0500 Subject: [PATCH 3/7] Refactor and cleanup --- .../Middleware/TraceableMiddlewareTest.php | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php index 3871dee85e92d..db55c75a4f8e1 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php @@ -146,23 +146,16 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope public function testCloneTraceableStack(): void { - $middlewareIterator = [ + $stackMiddleware = new StackMiddleware([ $this->createMock(MiddlewareInterface::class), $this->createMock(MiddlewareInterface::class), - ]; - - $stack = new TraceableStack( - new StackMiddleware($middlewareIterator), - $this->createMock(Stopwatch::class), - 'command.bus', - 'test' - ); + ]); - $clonedStack = clone $stack; + $stopwatch = $this->createMock(Stopwatch::class); - $nextFrame = $stack->next(); - $clonedStackNextFrame = $clonedStack->next(); + $traceableStack = new TraceableStack($stackMiddleware, $stopwatch, 'command.bus', 'test'); + $clonedStack = clone $traceableStack; - self::assertSame($nextFrame, $clonedStackNextFrame); + self::assertSame($traceableStack->next(), $clonedStack->next()); } } From 36dc8dc0fbe1f23a15bc9d715dbf859bbe8fb679 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 15 Sep 2023 07:44:46 -0500 Subject: [PATCH 4/7] Test stopwatch start and stop when there's a cloned TraceableStack --- .../Middleware/TraceableMiddlewareTest.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php index db55c75a4f8e1..a4eed23d55be8 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php @@ -153,7 +153,24 @@ public function testCloneTraceableStack(): void $stopwatch = $this->createMock(Stopwatch::class); - $traceableStack = new TraceableStack($stackMiddleware, $stopwatch, 'command.bus', 'test'); + $series = [ + [$this->matches('"%sMiddlewareInterface%s" on "command_bus"'), 'messenger.middleware'], + [$this->matches('"%sMiddlewareInterface%s" on "command_bus"'), 'messenger.middleware'], + ]; + $stopwatch->expects($this->exactly(2)) + ->method('start') + ->willReturnCallback(function (string $name, string $category = null) use (&$series) { + [$constraint, $expectedCategory] = array_shift($series); + + $constraint->evaluate($name); + $this->assertSame($expectedCategory, $category); + + return $this->createMock(StopwatchEvent::class); + }) + ; + $stopwatch->expects($this->never())->method('stop'); + + $traceableStack = new TraceableStack($stackMiddleware, $stopwatch, 'command_bus', 'messenger.middleware'); $clonedStack = clone $traceableStack; self::assertSame($traceableStack->next(), $clonedStack->next()); From dd8a3b36479cbf1ee3276126cd8abde26926f238 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 16 Sep 2023 14:11:27 -0500 Subject: [PATCH 5/7] Move class_exists() inside of the test as it is irrelevant outside --- .../Messenger/Tests/Middleware/TraceableMiddlewareTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php index a4eed23d55be8..59ff13c8251c9 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php @@ -22,8 +22,6 @@ use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\StopwatchEvent; -class_exists(TraceableMiddleware::class); - /** * @author Maxime Steinhausser */ @@ -146,6 +144,9 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope public function testCloneTraceableStack(): void { + // import TraceableStack + class_exists(TraceableMiddleware::class); + $stackMiddleware = new StackMiddleware([ $this->createMock(MiddlewareInterface::class), $this->createMock(MiddlewareInterface::class), From 7ae895f1ac35b52aeb92c8abb56b89a4ad8ac1a8 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 16 Sep 2023 22:18:22 -0500 Subject: [PATCH 6/7] Rename and refactor test to focus on unstacking the clone of TraceableStack independently Notes: - This test manually unstacks TraceableStack and it's clone instead of using a MessageBus, which is what StackMiddlewareTest::testClone() uses. I did this because MessageBus always dispatches with a new StackMiddleware(). I thought about using a custom MessageBus mock, however, I'm not sure if it's needed to test unstacking. - Removed stopwatch expects() as the stopwatch is not currently under test --- .../Middleware/TraceableMiddlewareTest.php | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php index 59ff13c8251c9..0d56f36303573 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php @@ -142,38 +142,36 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope $this->assertSame(1, $middleware->calls); } - public function testCloneTraceableStack(): void + public function testClonedTraceableStackUnstacksIndependently(): void { // import TraceableStack class_exists(TraceableMiddleware::class); $stackMiddleware = new StackMiddleware([ + null, $this->createMock(MiddlewareInterface::class), $this->createMock(MiddlewareInterface::class), ]); $stopwatch = $this->createMock(Stopwatch::class); - $series = [ - [$this->matches('"%sMiddlewareInterface%s" on "command_bus"'), 'messenger.middleware'], - [$this->matches('"%sMiddlewareInterface%s" on "command_bus"'), 'messenger.middleware'], - ]; - $stopwatch->expects($this->exactly(2)) - ->method('start') - ->willReturnCallback(function (string $name, string $category = null) use (&$series) { - [$constraint, $expectedCategory] = array_shift($series); + $traceableStack = new TraceableStack($stackMiddleware, $stopwatch, 'command_bus', 'messenger.middleware'); + $clonedStack = clone $traceableStack; - $constraint->evaluate($name); - $this->assertSame($expectedCategory, $category); + $traceableStackMiddleware1 = $traceableStack->next(); + $traceableStackMiddleware2 = $traceableStack->next(); + $traceableStackTail = $traceableStack->next(); + self::assertSame($stackMiddleware, $traceableStackTail); - return $this->createMock(StopwatchEvent::class); - }) - ; - $stopwatch->expects($this->never())->method('stop'); + // unstack clonedStack independently + $clonedStackMiddleware1 = $clonedStack->next(); + self::assertSame($traceableStackMiddleware1, $clonedStackMiddleware1); + self::assertNotSame($traceableStackMiddleware2, $clonedStackMiddleware1); - $traceableStack = new TraceableStack($stackMiddleware, $stopwatch, 'command_bus', 'messenger.middleware'); - $clonedStack = clone $traceableStack; + $clonedStackMiddleware2 = $clonedStack->next(); + self::assertSame($traceableStackMiddleware2, $clonedStackMiddleware2); - self::assertSame($traceableStack->next(), $clonedStack->next()); + $clonedStackTail = $clonedStack->next(); + self::assertNotSame($stackMiddleware, $clonedStackTail, 'stackMiddleware was also cloned'); } } From 859db92cd207e56f7cd2173be64fdc662d8f1ae4 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 16 Sep 2023 23:29:00 -0500 Subject: [PATCH 7/7] Test a cloned TraceableStack uses the same stopwatch as the original --- .../Middleware/TraceableMiddlewareTest.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php index 0d56f36303573..1cea529a5840d 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/TraceableMiddlewareTest.php @@ -174,4 +174,59 @@ class_exists(TraceableMiddleware::class); $clonedStackTail = $clonedStack->next(); self::assertNotSame($stackMiddleware, $clonedStackTail, 'stackMiddleware was also cloned'); } + + public function testClonedTraceableStackUsesSameStopwatch(): void + { + // import TraceableStack + class_exists(TraceableMiddleware::class); + + $middlewareIterable = [null, $this->createMock(MiddlewareInterface::class)]; + + $stackMiddleware = new StackMiddleware($middlewareIterable); + + $stopwatch = $this->createMock(Stopwatch::class); + $stopwatch->expects($this->exactly(2))->method('isStarted')->willReturn(true); + + $startSeries = [ + [$this->matches('"%sMiddlewareInterface%s" on "command_bus"'), 'messenger.middleware'], + [$this->identicalTo('Tail on "command_bus"'), 'messenger.middleware'], + [$this->matches('"%sMiddlewareInterface%s" on "command_bus"'), 'messenger.middleware'], + [$this->identicalTo('Tail on "command_bus"'), 'messenger.middleware'], + ]; + $stopwatch->expects($this->exactly(4)) + ->method('start') + ->willReturnCallback(function (string $name, string $category = null) use (&$startSeries) { + [$constraint, $expectedCategory] = array_shift($startSeries); + + $constraint->evaluate($name); + $this->assertSame($expectedCategory, $category); + + return $this->createMock(StopwatchEvent::class); + }) + ; + + $stopSeries = [ + $this->matches('"%sMiddlewareInterface%s" on "command_bus"'), + $this->matches('"%sMiddlewareInterface%s" on "command_bus"'), + ]; + $stopwatch->expects($this->exactly(2)) + ->method('stop') + ->willReturnCallback(function (string $name) use (&$stopSeries) { + $constraint = array_shift($stopSeries); + $constraint->evaluate($name); + + return $this->createMock(StopwatchEvent::class); + }) + ; + + $traceableStack = new TraceableStack($stackMiddleware, $stopwatch, 'command_bus', 'messenger.middleware'); + $clonedStack = clone $traceableStack; + + // unstack the stacks independently + $traceableStack->next(); + $traceableStack->next(); + + $clonedStack->next(); + $clonedStack->next(); + } }