Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Messenger] Dispatch events before & after each handler#52425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
BenMorel wants to merge1 commit intosymfony:7.4
base:7.4
Choose a base branch
Loading
fromBenMorel:messenger_handler_events

Conversation

BenMorel
Copy link
Contributor

@BenMorelBenMorel commentedNov 2, 2023
edited by OskarStark
Loading

QA
Branch?7.3
Bug fix?no
New feature?yes
Deprecations?no
Issues-
LicenseMIT

Context

We audit every message that goes through Messenger, together with each run of every handler, and its outcome (success/failure). At the moment, we have to do a lot of gymnastics in our subscriber to properly audit a message:

  • when we receive aWorkerMessageHandledEvent, we know that all handlers have succeeded, but in case the message was redelivered, we may have already audited some of the handler successes. So:
    • for eachHandledStamp:
      • check if we have a matching customAuditedHandledStamp:
        • if we don't:
          • log the succcess for this handler
          • add theAuditedHandledStamp
  • when we receive aWorkerMessageFailedEvent:
    • if the exception is aHandlerFailedException:
      • for each nested exception:
        • trace back the exception up toHandleMessageMiddleware to find the handler name
        • log the failure for this handler
    • because some handlers may still have succeeded, run the same logic as forWorkerMessageHandledEvent

There is an additional issue with this: while this works fine for async messages consumed by workers, this logic isn't triggered for sync messages. Because we also use the worker events to keep a stack of message ids in our subscriber, and stamp each message with its parent id, our current auditing solution cannot properly infer the parent-child relationship of say, an async Event triggered by a sync Command.

Proposed solution

This PR aims to solve all the issues above, and simplify auditing a lot, by introducing 3 events triggered in the handler loop ofHandleMessageMiddleware:

  • HandlerStartingEvent (dispatched right before calling the handler)
  • HandlerSuccessEvent
  • HandlerFailureEvent

ruudk, kbond, bram123, and DjordyKoert reacted with heart emoji
@carsonbotcarsonbot added this to the6.4 milestoneNov 2, 2023
@BenMorelBenMorelforce-pushed themessenger_handler_events branch 3 times, most recently from93f7c9d to2406c88CompareNovember 2, 2023 13:49
Copy link

@Pi-BoufPi-Bouf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

YES !

@OskarStark
Copy link
Contributor

This must target 7.1, due to feature freeze period.

Comment on lines 18 to 25
private Envelope $envelope;
private string $handlerName;

public function __construct(Envelope $envelope, string $handlerName)
{
$this->envelope = $envelope;
$this->handlerName = $handlerName;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Please use constructor property promotion

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yes! A couple questions:

  • as we target PHP 8.1, do we want to keepprivate + getters, or can we usepublic readonly instead? (and if targeting Symfony 7 and PHP 8.2, straightreadonly class with public promoted properties)
  • does Symfony allow a mix of promoted and non-promoted, like this:
    finalclass HandlerFailureEventextends AbstractHandlerEvent{publicfunction__construct(Envelope$envelope,string$handlerName,public\Throwable$exception,    ) {parent::__construct($envelope,$handlerName);    }}

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've settled onpublic readonly as I could find other occurrences in the7.0 branch. I also used a mix of promoted and non-promoted inHandlerFailureEvent, as in the code sample above.

Comment on lines +15 to +17
* Event dispatched before a handler is called.
*/
final class HandlerStartingEvent extends AbstractHandlerEvent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The name of the class does not make it clear, that it is dispatched before.

Maybe BeforeHandlerStartEvent?

ruudk, BenMorel, and valtzu reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I actually thought about this, but wanted to keep things organized by prefix, likeWorker* events. I don't mind though, what about justBeforeHandlerEvent?

@@ -35,6 +39,7 @@ class HandleMessageMiddleware implements MiddlewareInterface
public function __construct(
private HandlersLocatorInterface $handlersLocator,
private bool $allowNoHandlers = false,
private ?EventDispatcherInterface $eventDispatcher = null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
private ?EventDispatcherInterface$eventDispatcher =null,
private ?EventDispatcherInterface$dispatcher =null,

I like it more, but lets see what the others say

BenMorel reacted with eyes emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

ActuallySendMessageMiddlewareuses$eventDispatcher, so I guess it's better to keep it consistent here?

@@ -97,6 +106,14 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
} catch (\Throwable $e) {
$exceptions[$handlerDescriptor->getName()] = $e;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
$exceptions[$handlerDescriptor->getName()] =$e;
$exceptions[$handlerDescriptor->getName()] =$e;
$this->eventDispatcher?->dispatch(newHandlerFailureEvent($envelope,$handlerDescriptor->getName(),$e));

Same for the success case and remove the$e variable?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't mind dispatching the failure event in thecatch block, however I'm a bit reluctant about dispatching the success event in thetry block, as a failure in any listener/subscriber of this event would mark thehandler as failed.

That's why I kept things outside thetry/catch.

@@ -58,6 +63,10 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
continue;
}

$this->eventDispatcher?->dispatch(new HandlerStartingEvent($envelope, $handlerDescriptor->getName()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It would be great if the$handlerDescriptor is fully passed to this event, and that a subscriber can mutate it.

This would solve my use case that I tried to implement in:

It would allow event subscribers to:

  • read everything from the HandlerDescriptor, so also the options
  • set a new HandlerDescriptor that has a custom callable that does specific logic.

BenMorel reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm all for adding the wholeHandlerDescriptor in the events, you're right that it opens up a whole lot of possibilities.

However, I have no idea of the potential consequences of live-replacing a handler, so I'll leave it to others to decide if making theHandlerDescriptor replaceable is the way to go!

ruudk reacted with thumbs up emoji
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@ro0NL@OskarStark What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I have to admit that I also don't know what consequences this can have

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@ruudk Do you actually have a need to replace theHandlerDescriptor at the moment, or can we leave it for a later PR to maybe make it replaceable in the future?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Having it would be great, but maybe I can use the event system already to do what I want. I agree that we can later always decide to open this up.

Could we at least add the full HandlerDescriptor instead of only the name? That would make it a bit better.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Could we at least add the full HandlerDescriptor instead of only the name? That would make it a bit better.

Yes, definitely. I'll change this.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@ruudk events now have the fullHandlerDescriptor!

@@ -97,6 +106,14 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
} catch (\Throwable $e) {
$exceptions[$handlerDescriptor->getName()] = $e;
Copy link
Contributor

@ruudkruudkNov 3, 2023
edited by OskarStark
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It would be great if a HandlerFailureEvent subscriber could silence an exception.

Right now, it's only possible to log them. But by always passing the exception to the HandlerFailureEvent and dispatching that, a subscriber could say "ignore this".

A use case could be to annotate#[IgnoreException(SomeTimeoutException::class)] on the handler. In case of an exception, a HandlerFailureEvent subscriber would see that the handler wants to ignore this exception, and it will silence it.

From Messenger perspective all will be fine again.

Related to

Copy link
ContributorAuthor

@BenMorelBenMorelNov 3, 2023
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We could add aHandlerFailureEvent::ignoreException(bool $ignore) method indeed. I don't mind adding this feature, and again I'll leave it to others to decide if adding it is a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@ro0NL@OskarStark What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Also fine, but then we should let a subscriber mutate the handler descriptor. See my previous comment.

@BenMorel
Copy link
ContributorAuthor

This must target 7.1, due to feature freeze period.

Thank you, where is this documented please? I can't find any information on theRelease Process page nor on theReleases page. Googling only gives me blog entries for older Symfony releases.

@OskarStark

This comment was marked as outdated.

@OskarStark

This comment was marked as outdated.

Comment on lines +20 to +21
public readonly Envelope $envelope,
public readonly HandlerDescriptor $handlerDescriptor,
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Do you mean no abstract? What aboutAbstractWorkerMessageEvent?

@welcoMatticwelcoMattic removed their request for reviewNovember 8, 2023 22:27
OskarStark added a commit to symfony/symfony-docs that referenced this pull requestNov 9, 2023
This PR was squashed before being merged into the 6.3 branch.Discussion----------Add info about feature freeze periodFollows*symfony/symfony#52425 (comment)Commits-------809f169 Add info about feature freeze period
@BenMorelBenMorelforce-pushed themessenger_handler_events branch from2479453 to77b4b53CompareNovember 29, 2023 21:24
@BenMorelBenMorel changed the base branch from7.0 to7.1November 29, 2023 21:24
@BenMorel
Copy link
ContributorAuthor

I've just rebased this PR against the new 7.1 branch. Is there something I can do to get it merged?

As it stands, there are a few points in the comments that would need to be ruled on. Ideally I'd like to keep this PR simple for now, but keep things extendable if someone else wants to build extra features on top of it.

@Jeroeny
Copy link
Contributor

What if the callHandler method was moved to an interface (HandlerExecutor) ? Then there can also be a Traceable version of it to measure and debug handling per handler.

ruudk, jerowork, and 94noni reacted with thumbs up emojiruudk reacted with hooray emoji

@ruudk
Copy link
Contributor

What if the callHandler method was moved to an interface (HandlerExecutor) ? Then there can also be a Traceable version of it to measure and debug handling per handler.

I like this a lot 🙌

It makes a lot of sense to be able to probe execution of the handler(s). This makes#50980 and#50998 absolute. As with this interface, I can now create my own HandlerExecutor middleware that to do transaction wrapping or exception ignoring.

jerowork reacted with thumbs up emoji

@jerowork
Copy link
Contributor

What if the callHandler method was moved to an interface (HandlerExecutor) ? Then there can also be a Traceable version of it to measure and debug handling per handler.

Exactly what I was thinking (its in the name I guess 😉 ).
MakingcallHandler protected would solve it, but extracting the calling handler to a separate class makes it more flexible and open for extension for everybody who wants to change the calling handler for whatever reason (for me I would like to have a upgraded way of injecting other (optional) parameters in a handler)

@xabbuhxabbuh modified the milestones:7.1,7.2May 15, 2024
@fabpotfabpot modified the milestones:7.2,7.3Nov 20, 2024
@fabpotfabpot modified the milestones:7.3,7.4May 26, 2025
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@ruudkruudkruudk left review comments

@OskarStarkOskarStarkOskarStark left review comments

@Pi-BoufPi-BoufPi-Bouf approved these changes

@xabbuhxabbuhAwaiting requested review from xabbuh

@lyrixxlyrixxAwaiting requested review from lyrixx

@dunglasdunglasAwaiting requested review from dunglas

@ycerutoycerutoAwaiting requested review from yceruto

@kbondkbondAwaiting requested review from kbond

@chalasrchalasrAwaiting requested review from chalasr

@jderussejderusseAwaiting requested review from jderusse

Assignees
No one assigned
Projects
None yet
Milestone
7.4
Development

Successfully merging this pull request may close these issues.

9 participants
@BenMorel@OskarStark@Jeroeny@ruudk@jerowork@Pi-Bouf@fabpot@xabbuh@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp