Skip to content

[Messenger] Add a memory limit option for ConsumeMessagesCommand #26975

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

Merged
merged 1 commit into from
Apr 26, 2018

Conversation

sdelicata
Copy link
Contributor

Q A
Branch? master for features
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
License MIT

New feature to add a memory limit option to the ConsumeMessagesCommand.

bin/console messenger:consume-messages <receiver-name> -m 128


Use the --memory option to limit the memory consumed by the worker:

<info>php %command.full_name% <receiver-name> --memory=128</info>
Copy link
Contributor

Choose a reason for hiding this comment

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

Would personally call it --memory-limit to be the same as in php. It's also not exactly clear that it's in M. Would it be an idea to use G, M, K support here, just like with php?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I did it

@nicolas-grekas nicolas-grekas added this to the 4.1 milestone Apr 18, 2018
@sroze
Copy link
Contributor

sroze commented Apr 18, 2018

Same as for the other PR, could you have some tests? :)

@@ -61,6 +63,10 @@ protected function configure()
Use the --limit option to limit the number of messages received:

<info>php %command.full_name% <receiver-name> --limit=10</info>

Use the --memory-limit option to limit the memory consumed by the worker. Use PHP shorthand byte values [K, M or G]:
Copy link
Contributor

Choose a reason for hiding this comment

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

- Use the --memory-limit option to limit the memory consumed by the worker. Use PHP shorthand byte values [K, M or G]:
+ Use the --memory-limit option to stop the worker if it exceeds a given memory usage limit. You can use shorthand byte values [K, M or G]:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed


private function convertToOctets(string $size): int
{
if (\preg_match('/^(\d+)(.)$/', $size, $matches)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd be stricter than .. Maybe just (G|M|K) and throw an exception if nothing is recognized to prevent any issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@sdelicata sdelicata force-pushed the feat_memory_limit_consumer branch 2 times, most recently from 5a8f69f to 68582dd Compare April 19, 2018 15:57
throw new \InvalidArgumentException('Invalid memory limit given.');
}

return (int) $size;
Copy link
Member

Choose a reason for hiding this comment

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

Could (int) casting be removed? seems unnecessary after \d+ and ... * 1024 ....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, unnecessary anymore, thx


public function stop(): void
{
$this->decoratedReceiver->stop();
Copy link
Member

@yceruto yceruto Apr 19, 2018

Choose a reason for hiding this comment

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

I wondering if should we throw some kind of exception or log after that? I mean, right now it's stopped silently, how can we know if it was finished by memory limit or because it has received all messages?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@yceruto Good idea.
@sroze Did you already imagined such a notification mechanism?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see any reason throwing an exception for this. What is your use-case? Do you need to know this for something?

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, it is probably a good idea to log such behavior (no exception). Maybe an optional LoggerInterface argument that would be called with info when that happens? (note that it wouldn't be on this line but when you actually detect the memory usage line 41 of this line).

@lyrixx any PoV on the logging level?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I disagree exception too, but the optional LoggerInterface is a good idea

{
$this->decoratedReceiver = $decoratedReceiver;
$this->memoryLimit = $this->convertToOctets($memoryLimit);
$this->memoryResolver = $memoryResolver ?? function () {
Copy link
Member

Choose a reason for hiding this comment

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

?: ?

public function memoryProvider()
{
return array(
array(2048, 1024, true),
Copy link
Member

Choose a reason for hiding this comment

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

yield?

}
}

class ReceiverToDecorate implements ReceiverInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you instead use the CallbackReceiver (that needs to be moved to a proper file) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Of course

$size = $matches[1] * 1024;
}
} else {
throw new \InvalidArgumentException('Invalid memory limit given.');
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you throw first to reduce a depth level? i.e. if (!\preg_match(...)) { thow /* ... */; }


public function stop(): void
{
$this->decoratedReceiver->stop();
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, it is probably a good idea to log such behavior (no exception). Maybe an optional LoggerInterface argument that would be called with info when that happens? (note that it wouldn't be on this line but when you actually detect the memory usage line 41 of this line).

@lyrixx any PoV on the logging level?

@sdelicata sdelicata force-pushed the feat_memory_limit_consumer branch 2 times, most recently from 0d2d782 to c1236ce Compare April 20, 2018 12:17
if ($memoryResolver() >= $this->memoryLimit) {
$this->stop();
if ($this->logger) {
$this->logger->info('Receiver stopped due to memory limit exceeded.');
Copy link
Contributor

@sroze sroze Apr 20, 2018

Choose a reason for hiding this comment

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

$this->logger !== null && $this->logger->info('Receiver stopped due to memory limit of {limit} exceeded', array('limit' => $this->memoryLimit));

{
if (!\preg_match('/^(\d+)([G|M|K]*)$/', $size, $matches)) {
throw new \InvalidArgumentException('Invalid memory limit given.');
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Well, you don't need the else anymore :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

😆

private $memoryResolver;

public function __construct(
ReceiverInterface $decoratedReceiver,
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, we don’t use multi-line constructors in Symfony

@sdelicata sdelicata force-pushed the feat_memory_limit_consumer branch 2 times, most recently from 0a8460d to 37ee3a2 Compare April 20, 2018 13:11
sroze
sroze previously approved these changes Apr 20, 2018
@sroze
Copy link
Contributor

sroze commented Apr 20, 2018

The tests are failing. Looks like some infinite loop because the tests for the component just never ends. Can you look at it? Also, I believe we should use inject the logger within the decorators now that it's possible: inject it to the command and to the receivers.

@sdelicata
Copy link
Contributor Author

I've reproduced it. I'll try to fix soon

@sdelicata sdelicata force-pushed the feat_memory_limit_consumer branch from f836b55 to a4f1205 Compare April 20, 2018 19:23
@sdelicata
Copy link
Contributor Author

Test fixed.
MaximumCounterReceiver doesn't increase its internal counter anymore when receiving null message, so it will never stop :p

@sdelicata
Copy link
Contributor Author

Also, I believe we should use inject the logger within the decorators now that it's possible: inject it to the command and to the receivers
@sroze That's exactly what I planed ;)

@sdelicata sdelicata force-pushed the feat_memory_limit_consumer branch from 0babb6d to b13adcb Compare April 20, 2018 20:31
$this->assertNull($message);

if (2 == ++$receivedMessages) {
Copy link
Member

Choose a reason for hiding this comment

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

strict comparison === ?

Copy link
Contributor

Choose a reason for hiding this comment

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

ping @sdelicata

$size = $matches[1] * 1024 * 1024 * 1024;
} elseif ('M' == $matches[2]) {
$size = $matches[1] * 1024 * 1024;
} elseif ('K' == $matches[2]) {
Copy link
Member

Choose a reason for hiding this comment

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

same here and above.

$handler($message);

$memoryResolver = $this->memoryResolver;
if ($memoryResolver() >= $this->memoryLimit) {
Copy link
Contributor

Choose a reason for hiding this comment

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

the limit is described as

Use the --memory-limit option to stop the worker if it exceeds a given memory usage limit

"exceed" means more than the limit, so >, not >=

/**
* @dataProvider memoryProvider
*/
public function testReceiverStopsWhenMemoryLimitExceeded($memoryUsage, $memoryLimit, $shouldStop)
Copy link
Contributor

Choose a reason for hiding this comment

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

please add type declarations

@sroze sroze dismissed their stale review April 21, 2018 14:29

Changes to be made

@sroze
Copy link
Contributor

sroze commented Apr 22, 2018

Status: Needs work

@ostrolucky
Copy link
Contributor

I don't like naming of these. IMO it should signify what's being done when limit is exceeded. Otherwise, there will be confusion when somebody implements receiver with same trigger but another action. Also, I had no idea what it does until I looked into code. I thought it will just wait until memory is freed up or it will try to release some memory.

MaximumCountReceiver -> AbortWhenMaximumCountIsExceededReceiver
MemoryLimitReceiver -> AbortWhenMaximumMemoryIsExceededReceiver

@sroze
Copy link
Contributor

sroze commented Apr 22, 2018

That's a fair point you have here @ostrolucky. Though, no need to "maximum" if we already have "exceeded". Also, the receivers have this notion of "stop", so I'd suggest:

  • MaximumCountReceiver -> StopWhenMessageCountIsExceededReceiver
  • MemoryLimitReceiver -> StopWhenMemoryUsageIsExceededReceiver

$this->decoratedReceiver->stop();
}

private function convertToOctets(string $size): int
Copy link
Member

Choose a reason for hiding this comment

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

I fully agree.

$this->decoratedReceiver->stop();
}

private function convertToOctets(string $size): int
Copy link
Member

Choose a reason for hiding this comment

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

Implementation should be copied from existing code where we already have such logic in different components (see Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector::convertToBytes for instance).

@sdelicata sdelicata force-pushed the feat_memory_limit_consumer branch 2 times, most recently from 6826842 to f8e8125 Compare April 22, 2018 17:53
$this->assertNull($message);

if (2 == ++$receivedMessages) {
Copy link
Contributor

Choose a reason for hiding this comment

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

ping @sdelicata

$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->once())->method('info')
->with(
$this->equalTo('Receiver stopped due to memory limit of {limit} exceeded'),
Copy link
Contributor

Choose a reason for hiding this comment

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

As FYI, you don't need $this->equalTo. This would work:

->with('Receiver stopped due to memory limit of {limit} exceeded', array('limit' => 64 * 1024 * 1024));

$this->decoratedReceiver = $decoratedReceiver;
$this->memoryLimit = $memoryLimit;
$this->logger = $logger;
$this->memoryResolver = $memoryResolver ?: function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

??


public function receive(callable $handler): void
{
$callable = $this->callable;
Copy link
Contributor

Choose a reason for hiding this comment

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

($this->callable)($handler);

@sroze
Copy link
Contributor

sroze commented Apr 25, 2018

@fabpot could you update your review please?


private function convertToBytes($memoryLimit)
{
if ('-1' === $memoryLimit) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I really don't get the point of this -1 here. I appreciate it's coming from a copy/pasted code somewhere else but I'd remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree

@Tobion
Copy link
Contributor

Tobion commented Apr 25, 2018

@sdelicata please squash your commits into a single commit. some commit seems to be authored with a different email, so we cannot squash them.

}

$worker = new Worker($receiver, $this->bus);
$worker->run();
}

private function convertToBytes($memoryLimit)
Copy link
Contributor

Choose a reason for hiding this comment

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

missing types

@sdelicata sdelicata force-pushed the feat_memory_limit_consumer branch from e733ea7 to 7fe7738 Compare April 26, 2018 08:28
@sdelicata sdelicata force-pushed the feat_memory_limit_consumer branch from 7fe7738 to 08f98cf Compare April 26, 2018 08:29
@sdelicata
Copy link
Contributor Author

@Tobion Squash done

@Tobion
Copy link
Contributor

Tobion commented Apr 26, 2018

Thanks for your work on this new feature!

@Tobion Tobion merged commit 08f98cf into symfony:master Apr 26, 2018
Tobion added a commit that referenced this pull request Apr 26, 2018
…agesCommand` (sdelicata)

This PR was merged into the 4.1-dev branch.

Discussion
----------

[Messenger] Add a memory limit option for `ConsumeMessagesCommand`

| Q             | A
| ------------- | ---
| Branch?       | master for features
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| License       | MIT

New feature to add a memory limit option to the ConsumeMessagesCommand.
```
bin/console messenger:consume-messages <receiver-name> -m 128
```

Commits
-------

08f98cf [Messenger] Add a memory limit option for `ConsumeMessagesCommand`
@fabpot fabpot mentioned this pull request May 7, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants