Skip to content

Commit 2f810be

Browse files
authored
Merge pull request #795 from durable-workflow/release/2.5.20-watchdog
Release 2.5.20 with bounded database watchdog throttles
2 parents d4bc406 + 47ac8ca commit 2f810be

6 files changed

Lines changed: 505 additions & 6 deletions

File tree

‎composer.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
"dev-main": "2.0.x-dev"
8080
},
8181
"durable-workflow": {
82-
"product-train": "2.5.19",
82+
"product-train": "2.5.20",
8383
"laravel-embedded-upgrade-contract": "resources/laravel-embedded-upgrade-contract.json",
8484
"laravel-dependency-security-policy": "resources/laravel-dependency-security-policy.json"
8585
},

‎src/Support/CacheThrottle.php‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Workflow\Support;
6+
7+
use Illuminate\Cache\DatabaseStore;
8+
use Illuminate\Database\SqlServerConnection;
9+
use Illuminate\Support\Carbon;
10+
use Illuminate\Support\Facades\Cache;
11+
12+
final class CacheThrottle
13+
{
14+
public static function acquire(string $key, string|bool $value, int $seconds, ?string $activeMarker = null): bool
15+
{
16+
$cache = Cache::store();
17+
$store = $cache->getStore();
18+
if (! $store instanceof DatabaseStore || $store->getConnection() instanceof SqlServerConnection) {
19+
return ($activeMarker === null || ! $cache->has($activeMarker))
20+
&& $cache->add($key, $value, $seconds);
21+
}
22+
23+
// DatabaseStore::add deletes expired rows before inserting. Renew this
24+
// throttle in place so competing queue polls do not delete the same row.
25+
$table = config('cache.stores.' . Cache::getDefaultDriver() . '.table', 'cache');
26+
$prefixedKey = $store->getPrefix() . $key;
27+
$query = $store->getConnection()
28+
->table($table)
29+
->useWritePdo()
30+
->where('key', $prefixedKey);
31+
$now = Carbon::now()->getTimestamp();
32+
if ($activeMarker !== null) {
33+
$markerExpiration = $store->getConnection()
34+
->table($table)
35+
->useWritePdo()
36+
->where('key', $store->getPrefix() . $activeMarker)->value('expiration');
37+
if ($markerExpiration !== null && $markerExpiration >= $now) {
38+
// A wake just before chain expiry must not delay its replacement.
39+
$seconds = min($seconds, max(1, (int) $markerExpiration - $now));
40+
}
41+
}
42+
$expiration = $query->value('expiration');
43+
$values = [
44+
'value' => serialize($value),
45+
'expiration' => $now + $seconds,
46+
];
47+
if ($expiration === null) {
48+
return $store->getConnection()
49+
->table($table)
50+
->insertOrIgnore([
51+
'key' => $prefixedKey,
52+
...$values,
53+
]) === 1;
54+
}
55+
if ($expiration > $now) {
56+
return false;
57+
}
58+
59+
return $query->where('expiration', '<=', $now)
60+
->update($values) === 1;
61+
}
62+
}

‎src/V2/TaskWatchdog.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Support\Facades\DB;
99
use Illuminate\Support\Facades\Schema;
1010
use Throwable;
11+
use Workflow\Support\CacheThrottle;
1112
use Workflow\V2\Contracts\HistoryProjectionRole;
1213
use Workflow\V2\Contracts\WorkflowTaskRepairProjectionRole;
1314
use Workflow\V2\Enums\RunStatus;
@@ -97,7 +98,7 @@ public static function runPass(
9798
}
9899

99100
if ($respectThrottle) {
100-
if (! Cache::add(self::LOOP_THROTTLE_KEY, true, TaskRepairPolicy::loopThrottleSeconds())) {
101+
if (! CacheThrottle::acquire(self::LOOP_THROTTLE_KEY, true, TaskRepairPolicy::loopThrottleSeconds())) {
101102
return self::emptyReport(
102103
$connection,
103104
$queue,

‎src/Watchdog.php‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Illuminate\Support\Str;
1919
use Workflow\Models\StoredWorkflow;
2020
use Workflow\States\WorkflowPendingStatus;
21+
use Workflow\Support\CacheThrottle;
2122

2223
class Watchdog implements ShouldBeEncrypted, ShouldQueue
2324
{
@@ -60,16 +61,21 @@ public static function wake(string $connection, ?string $queue = null): void
6061
DB::afterCommit(static function () use ($connection, $queue, $timeout): void {
6162
$generation = self::newGeneration();
6263

64+
if (! CacheThrottle::acquire(
65+
self::LOOP_THROTTLE_KEY,
66+
$generation,
67+
self::bootstrapWindow($timeout),
68+
self::CACHE_KEY,
69+
) || Cache::has(self::CACHE_KEY)) {
70+
return;
71+
}
72+
6373
$watchdog = Cache::lock(self::CHAIN_LOCK_KEY, self::leaseDuration($timeout))
6474
->get(static function () use ($connection, $queue, $timeout, $generation): ?self {
6575
if (! self::storedWorkflowTableExists() || Cache::has(self::CACHE_KEY)) {
6676
return null;
6777
}
6878

69-
if (! Cache::add(self::LOOP_THROTTLE_KEY, $generation, self::bootstrapWindow($timeout))) {
70-
return null;
71-
}
72-
7379
if (! self::hasRecoverablePendingWorkflows($timeout) || Cache::has(self::CACHE_KEY)) {
7480
return null;
7581
}

0 commit comments

Comments
 (0)