Skip to content

Commit 9e225aa

Browse files
author
Oliver Stark
authored
Merge pull request #61 from ostark/test-rate-limiter
Fix & improve RateLimiter
2 parents abab77c + 6eddc51 commit 9e225aa

15 files changed

+204
-50
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [3.1.0] - 2022-09-22
6+
7+
> {note} Upgrading is highly recommended. The previous version did not limit concurrency of queue runners. `ASYNC_QUEUE_CONCURRENCY` defaults to `1` now.
8+
9+
- Fixed RateLimiter
10+
- Added tests for RateLimiter / concurrency
11+
- Concurrency of queue runners defaults to `1` now
12+
13+
514
## [3.0.0] - 2022-05-12
615
- Craft 4 support
716
- PHP 8 syntax

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ PHP_BINARY="/usr/local/Cellar/php71/7.1.0_11/bin/php"
5555
```
5656

5757

58-
By default `2` background processes handle the queue. With the `ASYNC_QUEUE_CONCURRENCY` ENV var you can modify this behaviour.
58+
By default `1` background process handles the queue. With the `ASYNC_QUEUE_CONCURRENCY` ENV var you can modify this behaviour.
5959
```
6060
# No concurrency
6161
ASYNC_QUEUE_CONCURRENCY=1

composer.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,14 @@
2323
"require": {
2424
"php": "^8.0",
2525
"craftcms/cms": "^4.0.0",
26-
"symfony/process": "^5.0",
27-
"treeware/plant": "^0.1.0"
26+
"symfony/process": "^5.0"
2827
},
2928
"autoload": {
3029
"psr-4": {
3130
"ostark\\AsyncQueue\\": "src/"
3231
}
3332
},
3433
"extra": {
35-
"treeware": {},
3634
"name": "AsyncQueue",
3735
"handle": "async-queue",
3836
"hasCpSettings": false,
@@ -51,8 +49,7 @@
5149
"config": {
5250
"allow-plugins": {
5351
"yiisoft/yii2-composer": true,
54-
"craftcms/plugin-installer": true,
55-
"treeware/plant": true
52+
"craftcms/plugin-installer": true
5653
}
5754
},
5855
"prefer-stable": true,

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: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,28 @@ public function __construct(Plugin $plugin)
2626

2727
public function __invoke(PushEvent $event): void
2828
{
29+
2930
$context = ($event->job instanceof JobInterface)
3031
? $event->job->getDescription()
3132
: 'Not instanceof craft\queue\JobInterface';
3233

3334
// Run queue in the background
3435
if ($this->plugin->getRateLimiter()->canIUse($context)) {
36+
3537
try {
36-
$this->plugin->getProcess()->start();
38+
$process = $this->plugin->getProcess()->start();
3739
$this->plugin->getRateLimiter()->increment();
3840
$handled = true;
3941

42+
$process->wait();
43+
4044
} catch (PhpExecutableNotFound) {
41-
Craft::debug(
45+
Craft::error(
4246
'QueueHandler::startBackgroundProcess() (PhpExecutableNotFound)',
4347
'async-queue'
4448
);
4549
} catch (RuntimeException | LogicException $e) {
46-
Craft::debug(
50+
Craft::error(
4751
Craft::t(
4852
'async-queue',
4953
'QueueHandler::startBackgroundProcess() (Job status: {status}. Exit code: {code})', [
@@ -54,7 +58,6 @@ public function __invoke(PushEvent $event): void
5458
'async-queue'
5559
);
5660
}
57-
5861
}
5962

6063
// Log what's going on
@@ -69,7 +72,7 @@ protected function logPushEvent(PushEvent $event, bool $handled = false): void
6972
}
7073

7174
if ($event->job instanceof BaseJob) {
72-
Craft::debug(
75+
Craft::info(
7376
Craft::t(
7477
'async-queue',
7578
'New PushEvent for {job} job - ({handled})', [

src/QueueCommand.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class QueueCommand extends Component
99
{
1010
public const DEFAULT_SCRIPT = "craft";
1111

12-
public const DEFAULT_ARGS = "queue/run";
12+
public const DEFAULT_ARGS = "queue/run -v";
1313

1414
public const EVENT_PREPARE_COMMAND = 'prepareCommand';
1515

@@ -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
}
@@ -67,6 +69,7 @@ protected function decorate(string $commandLine): string
6769

6870
// default decoration
6971
return "nice -n 15 {$commandLine} > /dev/null 2>&1 &";
72+
7073
}
7174

7275
}

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
{

src/Settings.php

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,9 @@
44

55
class Settings extends Model
66
{
7-
// Public Properties
8-
// =========================================================================
7+
public int $concurrency;
98

10-
/**
11-
* @var integer
12-
*/
13-
public $concurrency;
14-
15-
/**
16-
* @var integer
17-
*/
18-
public $poolLifetime;
19-
20-
/**
21-
* @var bool
22-
*/
23-
public $enabled = true;
9+
public bool $enabled = true;
2410

2511

2612
/**
@@ -29,8 +15,7 @@ class Settings extends Model
2915
public function __construct(array $config = [])
3016
{
3117
$config = array_merge([
32-
'concurrency' => (int)$this->env('ASYNC_QUEUE_CONCURRENCY', 2),
33-
'poolLifetime' => (int)$this->env('ASYNC_QUEUE_POOL_LIFETIME', 3600),
18+
'concurrency' => (int) $this->env('ASYNC_QUEUE_CONCURRENCY', 1),
3419
'enabled' => ($this->env('DISABLE_ASYNC_QUEUE', '0') == '1') ? false : true
3520
], $config);
3621

src/TestUtility/TestJob.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class TestJob extends BaseJob
1313
*/
1414
public function execute($queue): void
1515
{
16-
sleep(10);
16+
sleep(3);
1717
}
1818

1919

tests/BackgroundProcessRunTest.php

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

33+
$process->wait();
34+
35+
// give it some time to write the test file
36+
usleep(150000);
37+
3338
$this->assertEquals(0, $process->getExitCode());
3439
$this->assertTrue($process->isSuccessful());
3540
$this->assertEquals(\Symfony\Component\Process\Process::STATUS_TERMINATED, $process->getStatus());
3641

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

4044
$content = json_decode(file_get_contents(TEST_FILE), true);
41-
4245
$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']);
46+
$this->assertStringContainsString('craft.php', $content['$argv'][0]);
47+
$this->assertStringContainsString('queue/run', $content['$argv'][1]);
4548
$this->assertGreaterThanOrEqual($content['timestamp'], time());
49+
}
4650

51+
/**
52+
* @covers \ostark\AsyncQueue\BackgroundProcess::start
53+
*/
54+
public function test_process_does_not_block(): void
55+
{
56+
$command = new \ostark\AsyncQueue\QueueCommand('craft.php', '--sleep');
57+
$bgProcess = new BackgroundProcess($command);
58+
$process = $bgProcess->start();
4759

48-
}
60+
// give it some time to write the test file
61+
usleep(150000);
4962

63+
$this->assertFileExists(TEST_FILE);
5064

65+
$content = json_decode(file_get_contents(TEST_FILE), true);
66+
$this->assertGreaterThanOrEqual($content['timestamp'], time());
67+
68+
}
5169
}

0 commit comments

Comments
 (0)