Fix delayed retry push middleware pipeline#274
Fix delayed retry push middleware pipeline#274samdark wants to merge 3 commits intorm-push-middlewaresfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes delayed retry behavior by ensuring delay middlewares run inside the real push middleware pipeline (wrapping the actual adapter push), and exposes runtime push middleware overrides on QueueInterface to support that reliably.
Changes:
- Add
withMiddlewares()/withMiddlewaresAdded()toQueueInterfaceand implement them across queue implementations/decorators. - Update
ExponentialDelayMiddlewareto retry via a queue instance with the delay middleware added to the push pipeline (removing the prior no-op handler approach). - Add a regression test that verifies the delay is active at the exact moment the adapter’s
push()is invoked; update affected mocks accordingly.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/QueueInterface.php |
Exposes runtime push middleware override APIs used by retry/delay handling. |
src/Middleware/FailureHandling/Implementation/ExponentialDelayMiddleware.php |
Uses withMiddlewaresAdded(...)->push(...) so delay middleware wraps the actual push. |
src/Debug/QueueDecorator.php |
Preserves debug collection when returning queue instances with overridden middleware stacks. |
src/Middleware/Push/NoopMessageHandlerPush.php |
Removed now-unnecessary no-op push handler. |
stubs/StubQueue.php |
Updated to satisfy new QueueInterface methods. |
tests/App/DummyQueue.php |
Updated to satisfy new QueueInterface methods. |
tests/Unit/Middleware/FailureHandling/Implementation/ExponentialDelayMiddlewareTest.php |
Adds regression coverage with a delay-aware adapter + updates mocks. |
tests/Unit/Middleware/FailureHandling/Implementation/SendAgainMiddlewareTest.php |
Updates queue mock to support new middleware override call chain. |
tests/Integration/MiddlewareTest.php |
Updates queue mock to support new middleware override call chain. |
Comments suppressed due to low confidence (1)
tests/Unit/Middleware/FailureHandling/Implementation/ExponentialDelayMiddlewareTest.php:280
- These helper classes are declared in the main test namespace/file. To reduce the chance of class-name collisions across the test suite and to make intent clearer, consider moving them into a dedicated
Supportnamespace/file (as done in other middleware tests) or using anonymous classes scoped to the test method.
final class AdapterContextDelayMiddleware implements DelayMiddlewareInterface
{
private float $delay = 0.0;
public function __construct(
private readonly DelayAwareAdapter $adapter,
) {}
public function withDelay(float $seconds): self
{
$new = clone $this;
$new->delay = $seconds;
return $new;
}
public function processPush(MessageInterface $message, MessageHandlerPushInterface $handler): MessageInterface
{
$this->adapter->activeDelay = $this->delay;
try {
return $handler->handlePush($message);
} finally {
$this->adapter->activeDelay = null;
}
}
}
final class DelayAwareAdapter implements AdapterInterface
{
/**
* @var list<float|null>
*/
public array $delaysDuringPush = [];
public ?float $activeDelay = null;
public function runExisting(callable $handlerCallback): void {}
public function status(string|int $id): MessageStatus
{
return MessageStatus::DONE;
}
public function push(MessageInterface $message): MessageInterface
{
$this->delaysDuringPush[] = $this->activeDelay;
return $message;
}
public function subscribe(callable $handlerCallback): void {}
}
final class ThrowingFailureHandler implements MessageFailureHandlerInterface
{
public function handleFailure(FailureHandlingRequest $request): FailureHandlingRequest
{
throw $request->getException();
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| public function withMiddlewares(MiddlewarePushInterface|callable|array|string ...$middlewareDefinitions): self; | ||
|
|
There was a problem hiding this comment.
withMiddlewares() / withMiddlewaresAdded() are new public API methods but are missing docblocks, unlike the rest of QueueInterface. Please document what they do, what argument formats are supported, and (importantly) the middleware execution order when adding/replacing definitions so callers know where the added middleware sits in the pipeline.
| public function withMiddlewares(MiddlewarePushInterface|callable|array|string ...$middlewareDefinitions): self; | |
| /** | |
| * Returns a queue instance with push middleware definitions replaced by the specified ones. | |
| * | |
| * Supported middleware definition formats are: | |
| * - a {@see MiddlewarePushInterface} instance; | |
| * - a callable middleware definition; | |
| * - a string service ID or class name; | |
| * - an array definition understood by the implementation/container. | |
| * | |
| * The resulting middleware pipeline uses the provided definitions in the same order they are passed | |
| * to this method. Replacing definitions discards any previously configured middleware. | |
| * | |
| * @param MiddlewarePushInterface|callable|array|string ...$middlewareDefinitions Middleware definitions | |
| * to use for push handling. | |
| * | |
| * @return self A queue instance configured with exactly the specified push middleware definitions. | |
| */ | |
| public function withMiddlewares(MiddlewarePushInterface|callable|array|string ...$middlewareDefinitions): self; | |
| /** | |
| * Returns a queue instance with additional push middleware definitions appended to the existing ones. | |
| * | |
| * Supported middleware definition formats are: | |
| * - a {@see MiddlewarePushInterface} instance; | |
| * - a callable middleware definition; | |
| * - a string service ID or class name; | |
| * - an array definition understood by the implementation/container. | |
| * | |
| * The resulting middleware pipeline preserves the existing middleware order and then appends the provided | |
| * definitions in the same order they are passed to this method. In other words, previously configured | |
| * middleware executes before the middleware added here. | |
| * | |
| * @param MiddlewarePushInterface|callable|array|string ...$middlewareDefinitions Middleware definitions | |
| * to append to the push handling pipeline. | |
| * | |
| * @return self A queue instance configured with the existing push middleware followed by the specified | |
| * additional middleware definitions. | |
| */ |
Summary
QueueInterface.QueueDecoratorreturns queue instances with overridden middleware stacks.NoopMessageHandlerPush, which made delay middleware run around a no-op instead of the actual push.Queueand a delay-aware adapter.Why
DelayMiddlewareInterfaceextendsMiddlewarePushInterface, so a delay implementation may reasonably apply behavior around the downstream handler rather than only transforming the message.Old behavior was effectively:
The current branch changed that to:
This PR changes
ExponentialDelayMiddlewareto use:That makes the delay middleware wrap the real final push handler again:
The regression test uses a real
Queueand anAdapterInterfaceimplementation that records the active delay value at the exact momentpush()is called. The old no-op-handler approach recordsnull; the fixed pipeline records the configured delay.Validation
git diff --checkphp -l src/QueueInterface.phpphp -l src/Middleware/FailureHandling/Implementation/ExponentialDelayMiddleware.phpphp -l src/Debug/QueueDecorator.phpphp -l stubs/StubQueue.phpphp -l tests/App/DummyQueue.phpphp -l tests/Unit/Middleware/FailureHandling/Implementation/ExponentialDelayMiddlewareTest.php./vendor/bin/phpunit tests/Unit/Middleware/FailureHandling/Implementation/ExponentialDelayMiddlewareTest.php tests/Unit/Middleware/FailureHandling/Implementation/SendAgainMiddlewareTest.php tests/Integration/MiddlewareTest.php./vendor/bin/phpunit