Skip to content

Commit 0c482c5

Browse files
author
Oliver Stark
committed
Fix & improve RateLimiter
1 parent abab77c commit 0c482c5

11 files changed

+4678
-21
lines changed

src/BackgroundProcess.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,10 @@ public function __construct(QueueCommand $command = null)
3535
public function start()
3636
{
3737
$cmd = $this->command->getPreparedCommand();
38-
$cwd = realpath(CRAFT_BASE_PATH);
39-
40-
$process = Process::fromShellCommandline($cmd, $cwd);
38+
$process = Process::fromShellCommandline($cmd);
4139

4240
try {
43-
$process->run();
41+
$process->start();
4442
} catch (\Symfony\Component\Process\Exception\RuntimeException $runtimeException) {
4543
$runtimeException = new RuntimeException($runtimeException->getMessage());
4644
$runtimeException->setProcess($process);

src/Handlers/BackgroundQueueHandler.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ public function __invoke(PushEvent $event): void
5454
'async-queue'
5555
);
5656
}
57-
5857
}
5958

6059
// Log what's going on

src/QueueCommand.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ public function getPreparedCommand(callable $wrapper = null): string
4545
throw new PhpExecutableNotFound('Unable to find php executable.');
4646
}
4747

48-
$commandLine = implode(" ", [$php, $this->scriptName, $this->scriptArgs]);
48+
$path = realpath(CRAFT_BASE_PATH);
49+
$script = $path . DIRECTORY_SEPARATOR . $this->scriptName;
50+
$commandLine = implode(" ", [$php, $script, $this->scriptArgs]);
4951

5052
return $this->decorate($commandLine);
5153
}
@@ -66,7 +68,7 @@ protected function decorate(string $commandLine): string
6668
}
6769

6870
// default decoration
69-
return "nice -n 15 {$commandLine} > /dev/null 2>&1 &";
71+
return "nice -n 15 {$commandLine}"; // > /tmp/null.txt 2>&1 &
7072
}
7173

7274
}

src/RateLimiter.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ public function __construct(Queue $queue, Settings $settings)
3636
*/
3737
public function canIUse(string $context = null): bool
3838
{
39+
if ($this->internalCount >= $this->maxItems) {
40+
return false;
41+
}
42+
3943
try {
44+
$this->queue->channel = 'queue';
4045
$reserved = $this->queue->getTotalReserved();
4146
} catch (\Exception) {
4247
$reserved = 0;
@@ -54,6 +59,11 @@ public function increment(): void
5459
$this->internalCount++;
5560
}
5661

62+
public function getInternalCount(): int
63+
{
64+
return $this->internalCount;
65+
}
66+
5767

5868
protected function logAttempt(int $currentUsage, string $context = null): void
5969
{

tests/BackgroundProcessRunTest.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,36 @@ public function test_start_default_dummy_script_success(): void
3030
$bgProcess = new BackgroundProcess($command);
3131
$process = $bgProcess->start();
3232

33+
$process->wait();
34+
3335
$this->assertEquals(0, $process->getExitCode());
3436
$this->assertTrue($process->isSuccessful());
3537
$this->assertEquals(\Symfony\Component\Process\Process::STATUS_TERMINATED, $process->getStatus());
3638

37-
// Wait 0.25 seconds
38-
usleep(250000);
39+
$this->assertFileExists(TEST_FILE);
3940

4041
$content = json_decode(file_get_contents(TEST_FILE), true);
41-
4242
$this->assertTrue(is_array($content), 'Unable to read and json_decode test file.');
43-
$this->assertContains('craft.php', $content['$argv']);
44-
$this->assertContains('queue/run', $content['$argv']);
43+
$this->assertStringContainsString('craft.php', $content['$argv'][0]);
44+
$this->assertStringContainsString('queue/run', $content['$argv'][1]);
4545
$this->assertGreaterThanOrEqual($content['timestamp'], time());
46+
}
47+
48+
/**
49+
* @covers \ostark\AsyncQueue\BackgroundProcess::start
50+
*/
51+
public function test_process_does_not_block(): void
52+
{
53+
$command = new \ostark\AsyncQueue\QueueCommand('craft.php', '--sleep');
54+
$bgProcess = new BackgroundProcess($command);
55+
$process = $bgProcess->start();
56+
usleep(150000);
4657

58+
$this->assertFileExists(TEST_FILE);
4759

48-
}
60+
$content = json_decode(file_get_contents(TEST_FILE), true);
61+
$this->assertGreaterThanOrEqual($content['timestamp'], time());
4962

63+
}
5064

5165
}

tests/RateLimiterTest.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
use ostark\AsyncQueue\BackgroundProcess;
4+
use PHPUnit\Framework\TestCase;
5+
use yii\queue\PushEvent;
6+
7+
/**
8+
* @covers \ostark\AsyncQueue\RateLimiter
9+
* @covers \ostark\AsyncQueue\Handlers\BackgroundQueueHandler
10+
*/
11+
class RateLimiterTest extends TestCase
12+
{
13+
public $plugin;
14+
15+
public function setUp(): void
16+
{
17+
parent::setUp();
18+
19+
$dummyCommand = new \ostark\AsyncQueue\QueueCommand('craft.php', '--sleep');
20+
$this->plugin = new \ostark\AsyncQueue\Plugin('async-queue', null, []);
21+
$this->plugin->set('async_process', new BackgroundProcess($dummyCommand));
22+
}
23+
24+
public function tearDown(): void
25+
{
26+
parent::tearDown();
27+
@unlink(TEST_FILE);
28+
}
29+
30+
31+
public function test_can_setup_handler_and_invoke_event(): void
32+
{
33+
$this->plugin = new \ostark\AsyncQueue\Plugin('async-queue', null, []);
34+
$handler = new \ostark\AsyncQueue\Handlers\BackgroundQueueHandler($this->plugin);
35+
36+
$handler->__invoke(new PushEvent());
37+
38+
$this->assertSame(1, $this->plugin->getRateLimiter()->getInternalCount());
39+
}
40+
41+
public function test_respect_the_limit_when_adding_multiple_jobs_in_one_request(): void
42+
{
43+
$handler = new \ostark\AsyncQueue\Handlers\BackgroundQueueHandler($this->plugin);
44+
45+
$handler->__invoke(new PushEvent());
46+
$handler->__invoke(new PushEvent());
47+
$handler->__invoke(new PushEvent());
48+
$handler->__invoke(new PushEvent());
49+
50+
$this->assertSame(
51+
$this->plugin->getRateLimiter()->maxItems,
52+
$this->plugin->getRateLimiter()->getInternalCount()
53+
);
54+
}
55+
56+
public function test_stop_spawning_processes_when_too_many_jobs_are_reserved(): void
57+
{
58+
// Fake the reserved count
59+
$queue = new class extends \craft\queue\Queue {
60+
public function getTotalReserved(): int
61+
{
62+
return 5;
63+
}
64+
};
65+
66+
$this->plugin->set('async_rate_limiter', new \ostark\AsyncQueue\RateLimiter($queue, $this->plugin->getSettings()));
67+
$handler = new \ostark\AsyncQueue\Handlers\BackgroundQueueHandler($this->plugin);
68+
69+
$handler->__invoke(new PushEvent());
70+
71+
$this->assertSame(0, $this->plugin->getRateLimiter()->getInternalCount());
72+
}
73+
}

tests/_craft/storage/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy