Skip to content

Handlers should be lazy loaded #3

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

Handlers should be lazy loaded #3

wants to merge 1 commit into from

Conversation

mvrhov
Copy link

@mvrhov mvrhov commented Dec 25, 2017

No description provided.

@@ -19,15 +20,19 @@
*/
class HandlerLocator implements HandlerLocatorInterface
{
/** @var ContainerInterface */
Copy link
Owner

Choose a reason for hiding this comment

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

Either on 3 lines or to be removed :)

$handler = new MessageHandlerCollection($handler);

if (is_array($handler)) {
$handler = new MessageHandlerCollection(array_map(function ($handler) { return $this->serviceLocator->get($handler); }, $handler));
Copy link
Owner

Choose a reason for hiding this comment

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

Could you add the callable's body on its own line?

@@ -20,6 +20,7 @@

<!-- Handlers -->
<service id="message.handler_resolver" class="Symfony\Component\Message\HandlerLocator">
<argument type="service" id="service_container"/>
Copy link
Owner

Choose a reason for hiding this comment

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

We actually do not inject the service_container, right? So:

<argument /> <!-- Service Locator -->

/**
* Maps a message (its class) to a given handler.
*
* @var array
*/
private $messageToHandlerMapping;

public function __construct(array $messageToHandlerMapping = array())
public function __construct(ContainerInterface $serviceLocator, array $messageToHandlerMapping = array())
Copy link

@ro0NL ro0NL Dec 25, 2017

Choose a reason for hiding this comment

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

Cant the locator do the mapping as well? $handler = $locator->get(get_class($message))

Meaning that multiple handlers per message are normalized to a chain/collection handler at compile time.

@sroze sroze force-pushed the add-message-component branch 4 times, most recently from 2824e9a to af34b5c Compare December 25, 2017 12:16
@@ -79,7 +81,22 @@ private function findHandlers(ContainerBuilder $container): array
$handlersByMessage[$message] = call_user_func_array('array_merge', $handlersByMessage[$message]);
}

return $handlersByMessage;

$definitions = array();
Copy link
Author

Choose a reason for hiding this comment

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

@ro0NL is this what you had in mind

Copy link

Choose a reason for hiding this comment

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

Yep, exactly.

} else {
$d = new Definition(MessageHandlerCollection::class, array($handlers));
$d->setPrivate(true);
$definitions[hash('sha1', $message)] = $d;
Copy link
Author

Choose a reason for hiding this comment

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

Do we need to prefix the service name or are we sure that sha1 is unique enough?
If we go for prefix. Suggestions. I'd add MHC short for MessageHandlerCollection

Copy link
Owner

Choose a reason for hiding this comment

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

Why not MessageHandlerCollection ? :-)

Copy link
Author

@mvrhov mvrhov Dec 25, 2017

Choose a reason for hiding this comment

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

The prefix? I find it rather long 40 characters of sha1 + 24 for prefix. We'll have problems on windows when this gets dumped to filesystem with the new container (aka service per file) (Too long path)

Copy link
Owner

Choose a reason for hiding this comment

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

Fair enough, let's start with that and then we'll see the reviews in Symfony's main PR anyway :)

foreach ($this->handlers as $handler) {
yield $handler($message);
}
return array_map(function ($handler) use ($message) {
Copy link
Author

Choose a reason for hiding this comment

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

@sroze: Sorry I had to revert this, otherwise the generator was returned from this and no handler was called.

Copy link
Owner

Choose a reason for hiding this comment

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

Oh... I see, good point! Good reason to revert it 👍

Copy link
Owner

Choose a reason for hiding this comment

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

FYI I've merged this change already in the main branch.

} else {
$d = new Definition(MessageHandlerCollection::class, array($handlers));
$d->setPrivate(true);
$definitions[hash('sha1', $message)] = $d;
Copy link
Owner

Choose a reason for hiding this comment

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

Why not MessageHandlerCollection ? :-)

foreach ($this->handlers as $handler) {
yield $handler($message);
}
return array_map(function ($handler) use ($message) {
Copy link
Owner

Choose a reason for hiding this comment

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

Oh... I see, good point! Good reason to revert it 👍

use Symfony\Component\Message\Exception\NoHandlerForMessageException;
use Symfony\Component\Message\Handler\MessageHandlerCollection;

/**
* @author Samuel Roze <samuel.roze@gmail.com>
*/
class HandlerLocator implements HandlerLocatorInterface
Copy link
Owner

Choose a reason for hiding this comment

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

Can you then rename to ContainerHandlerLocator ?

$d = new Definition(MessageHandlerCollection::class, array($handlers));
$d->setPrivate(true);
$definitions[hash('sha1', $message)] = $d;
$handlersByMessage[$message] = new Reference(hash('sha1', $message));
Copy link
Owner

Choose a reason for hiding this comment

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

Can you re-use the hash generated the line above instead of re-generating it?

@sroze sroze force-pushed the add-message-component branch from 3d4d82b to 7549ea5 Compare December 25, 2017 21:11
@mvrhov
Copy link
Author

mvrhov commented Dec 26, 2017

I restored the original HandlerLocator. It might come handy to someone.

@@ -7,20 +7,20 @@
<services>
<defaults public="false" />

<!-- Bus -->
<!-- Message Bus -->
Copy link
Author

Choose a reason for hiding this comment

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

on purpose, in preparation for event bus

<service id="message_bus" class="Symfony\Component\Message\MessageBus" public="true">
<argument type="collection" /> <!-- Middlewares -->
</service>

<service id="Symfony\Component\Message\MessageBusInterface" alias="message_bus" />

<!-- Handlers -->
<service id="message.handler_resolver" class="Symfony\Component\Message\HandlerLocator">
<argument type="collection" /> <!-- Message to handler mapping -->
<service id="message.container_message_handler_resolver" class="Symfony\Component\Message\ContainerHandlerLocator">
Copy link
Author

Choose a reason for hiding this comment

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

message on purpose, in preparation for event bus

@sroze
Copy link
Owner

sroze commented Dec 27, 2017

I will remove the changes "in preparation of #4" from this PR, and merge. Thank you @mvrhov 👍 👏

/**
* @author Miha Vrhovnik <miha.vrhovnik@gmail.com>
*/
class ContainerHandlerLocator implements HandlerLocatorInterface
Copy link

Choose a reason for hiding this comment

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

WDYT about Factory instead of Locator?

Copy link
Owner

Choose a reason for hiding this comment

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

A bit late 🙊

Copy link

@ro0NL ro0NL Dec 27, 2017

Choose a reason for hiding this comment

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

that implies renaming the interface to HandlerFactoryInterface right? Tend to agree we're indeed factorizing here :) but current naming is fine also IMHO.

@sroze
Copy link
Owner

sroze commented Dec 27, 2017

Merged in symfony@3847968.

@sroze sroze closed this Dec 27, 2017
*/
private $serviceLocator;

public function __construct(ContainerInterface $serviceLocator)
Copy link

Choose a reason for hiding this comment

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

SF usually calls this one $container

@mvrhov mvrhov deleted the lazyLoadHandlers branch December 27, 2017 10:43
sroze pushed a commit that referenced this pull request Mar 23, 2019
…s not exist (neeckeloo)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[Messenger] Improved message when handler class does not exist

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? |no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

**Problem:**

When defining a non existing messenger handler class in the `services.yml` config file, we encounter this confusing error message:

```
services:
    App\Handler\NonExistentHandler:
        tags: [messenger.message_handler]
```

```
PHP Fatal error:  Uncaught Symfony\Component\Debug\Exception\FatalThrowableError: Argument 1 passed to Symfony\Component\Messenger\DependencyInjection\MessengerPass::guessHandledClasses() must be an instance of ReflectionClass, null given, called in /app/vendor/symfony/messenger/DependencyInjection/MessengerPass.php on line 93 in /app/vendor/symfony/messenger/DependencyInjection/MessengerPass.php:189
Stack trace:
    #0 /app/vendor/symfony/messenger/DependencyInjection/MessengerPass.php(93): Symfony\Component\Messenger\DependencyInjection\MessengerPass->guessHandledClasses(NULL, 'App\\Application...')
    #1 /app/vendor/symfony/messenger/DependencyInjection/MessengerPass.php(74): Symfony\Component\Messenger\DependencyInjection\MessengerPass->registerHandlers(Object(Symfony\Component\DependencyInjection\ContainerBuilder), Array)
    #2 /app/vendor/symfony/dependency-injection/Compiler/Compiler.php(95): Symfony\Component\Messenger\DependencyInjection\MessengerPass->process(Object(Symfony\Component\DependencyInjection\ContainerBuilder))
    #3 / in /app/vendor/symfony/messenger/DependencyInjection/MessengerPass.php on line 189
```

**Proposal:**

We can throw a more relevant exception (RuntimeException) in this case to help the developer to have a better understanding of the issue.

```Invalid service "App\Handler\NonExistentHandler": class "App\Handler\NonExistentHandler" does not exist.```

Commits
-------

6ab9274 Improve error message when defining messenger handler class that does not exists
sroze pushed a commit that referenced this pull request Mar 31, 2019
…logs (fabpot)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[Messenger] Add missing information in messenger logs

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | yes-ish
| New feature?  | yes-ish
| BC breaks?    | no
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

When using `messenger:consume`, I get the following logs:

```
2019-03-25T11:39:05+01:00 [info] Received message "Symfony\Component\Mailer\EnvelopedMessage"
2019-03-25T11:39:05+01:00 [warning] An exception occurred while handling message "Symfony\Component\Mailer\EnvelopedMessage"
2019-03-25T11:39:05+01:00 [info] Retrying Symfony\Component\Mailer\EnvelopedMessage - retry #1.
2019-03-25T11:39:05+01:00 [info] Sending message "Symfony\Component\Mailer\EnvelopedMessage" with "Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport"
2019-03-25T11:39:06+01:00 [info] Received message "Symfony\Component\Mailer\EnvelopedMessage"
2019-03-25T11:39:06+01:00 [warning] An exception occurred while handling message "Symfony\Component\Mailer\EnvelopedMessage"
2019-03-25T11:39:06+01:00 [info] Retrying Symfony\Component\Mailer\EnvelopedMessage - retry #2.
2019-03-25T11:39:06+01:00 [info] Sending message "Symfony\Component\Mailer\EnvelopedMessage" with "Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport"
2019-03-25T11:39:09+01:00 [info] Received message "Symfony\Component\Mailer\EnvelopedMessage"
2019-03-25T11:39:09+01:00 [warning] An exception occurred while handling message "Symfony\Component\Mailer\EnvelopedMessage"
2019-03-25T11:39:09+01:00 [info] Retrying Symfony\Component\Mailer\EnvelopedMessage - retry #3.
2019-03-25T11:39:09+01:00 [info] Sending message "Symfony\Component\Mailer\EnvelopedMessage" with "Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport"
```

So, an. error occurred, but I have no idea what's going on. The exception is in the context, but the context is not displayed by default. So, this PR fixes it.

Commits
-------

20664ca [Messenger] added missing information in messenger logs
sroze pushed a commit that referenced this pull request Apr 22, 2019
… is empty (yceruto)

This PR was merged into the 4.2 branch.

Discussion
----------

[HttpKernel] Fix get session when the request stack is empty

| Q             | A
| ------------- | ---
| Branch?       | 4.2
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT

This bug happen behind an exception on a kernel response event, when one collector (e.g. `RequestDataCollector`) is trying to get the request session and the request stack is currently empty.

**Reproducer**
https://github.com/yceruto/get-session-bug (`GET /`)

See logs on terminal:
```bash
Apr 15 20:29:03 |ERROR| PHP    2019-04-15T20:29:03-04:00 Call to a member function isSecure() on null
Apr 15 20:29:03 |ERROR| PHP    PHP Fatal error:  Uncaught Symfony\Component\Debug\Exception\FatalThrowableError: Call to a member function isSecure() on null in /home/yceruto/demos/getsession/vendor/symfony/http-kernel/EventListener/SessionListener.php:43
Apr 15 20:29:03 |DEBUG| PHP    Stack trace:
Apr 15 20:29:03 |DEBUG| PHP    #0 /home/yceruto/demos/getsession/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php(59): Symfony\Component\HttpKernel\EventListener\SessionListener->getSession()
Apr 15 20:29:03 |DEBUG| PHP    #1 /home/yceruto/demos/getsession/vendor/symfony/http-foundation/Request.php(707): Symfony\Component\HttpKernel\EventListener\AbstractSessionListener->Symfony\Component\HttpKernel\EventListener\{closure}()
Apr 15 20:29:03 |DEBUG| PHP    #2 /home/yceruto/demos/getsession/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php(65): Symfony\Component\HttpFoundation\Request->getSession()
Apr 15 20:29:03 |DEBUG| PHP    #3 /home/yceruto/demos/getsession/vendor/symfony/http-kernel/Profiler/Profiler.php(167): Symfony\Component\HttpKernel\DataCollector\RequestDataCollector->collect(Object(Symfony\Component\HttpFoundation\Request), Object(Symfony\Component\HttpFoundation\Respo in /home/yceruto/demos/getsession/vendor/symfony/http-kernel/EventListener/SessionListener.php on line 43
```

Friendly ping @nicolas-grekas as author of the previous PR symfony#28244

Commits
-------

d62ca37 Fix get session when the request stack is empty
sroze pushed a commit that referenced this pull request Jul 10, 2019
This PR was merged into the 4.3 branch.

Discussion
----------

[Messenger] improve logs

| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets |
| License       | MIT
| Doc PR        |

The logs are currently very confusing and duplicated:

- When handled sync the uncaught error it logged and displayed by the console / http error handler anyway. Currently it the warning is duplicated and useless:

```
14:26:11 WARNING   [messenger] An exception occurred while handling message "{class}": OUCH, THAT HURTS! GO TO MOM!
14:26:11 ERROR     [console] Error thrown while running command "{class}". Message: "OUCH, THAT HURTS! GO TO MOM!"

In HandleMessageMiddleware.php line 82:

  [Symfony\Component\Messenger\Exception\HandlerFailedException]
  OUCH, THAT HURTS! GO TO MOM!
```

- When handling async is was even confusing because the actual error was logged as warning and the retry (which is a good thing) was the error.

```
13:48:15 WARNING   [messenger] An exception occurred while handling message "{class}": OUCH, THAT HURTS! GO TO MOM!
13:48:15 ERROR     [messenger] Retrying {class} - retry #1.
```

Now it's must clearer and adds even context like the delay:

```
16:20:11 ERROR     [messenger] Error thrown while handling message {class}. Dispatching for retry #3 using 4000 ms delay. Error: "OUCH, THAT HURTS! GO TO MOM!"
...
16:20:15 CRITICAL  [messenger] Error thrown while handling message {class}. Removing from transport after 3 retries. Error: "OUCH, THAT HURTS! GO TO MOM!"
```

Commits
-------

2ac7027 [Messenger] improve logs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants