Skip to content

Commit a8aebfc

Browse files
GitHub #250: Waterline Phase 4: Polish and Accessibility (#511)
1 parent 92f3c11 commit a8aebfc

4 files changed

Lines changed: 46 additions & 28 deletions

File tree

docs/architecture/worker-compatibility.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,13 @@ It does not cover:
8484

8585
Every live worker maintains a heartbeat row under the
8686
`workflow_worker_compatibility_heartbeats` table (or the legacy fallback
87-
cache when the table is unavailable). The row is owned by one
88-
`worker_id` and carries:
87+
cache when the table is unavailable). The fleet auto-detects a missing
88+
table and routes to the cache; operators can also force the cache-only
89+
path by setting
90+
`workflows.v2.compatibility.disable_heartbeat_table`
91+
(`DW_V2_COMPATIBILITY_DISABLE_HEARTBEAT_TABLE`) to true without dropping
92+
the table — useful for sidelining the table during a problematic schema
93+
transition. The row is owned by one `worker_id` and carries:
8994

9095
- **`worker_id`**`hostname:pid:ulid`, generated on first heartbeat
9196
and stable for the life of the worker process. The ULID segment keeps

src/V2/Support/WorkerCompatibilityFleet.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,10 @@ private static function normalizeQueues(?string $queue): array
915915

916916
private static function heartbeatTableExists(): bool
917917
{
918+
if ((bool) config('workflows.v2.compatibility.disable_heartbeat_table', false)) {
919+
return false;
920+
}
921+
918922
try {
919923
return Schema::hasTable((new WorkerCompatibilityHeartbeat())->getTable());
920924
} catch (Throwable) {

src/config/workflows.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,21 @@
108108
'WORKFLOW_V2_PIN_TO_RECORDED_FINGERPRINT',
109109
true
110110
),
111+
// When true, the worker-compatibility fleet bypasses the
112+
// workflow_worker_compatibility_heartbeats table and reads/writes
113+
// exclusively through the legacy cache fallback. The fleet
114+
// already auto-detects a missing table and falls back to cache,
115+
// so the default (false) is correct for almost every deployment.
116+
// Set this to true when you need to force the cache-only path
117+
// without dropping the table — operators who want to temporarily
118+
// sideline the heartbeat table during a problematic schema
119+
// transition, and tests that exercise the cache-fallback branch
120+
// without mutating shared schema state.
121+
'disable_heartbeat_table' => (bool) Env::dw(
122+
'DW_V2_COMPATIBILITY_DISABLE_HEARTBEAT_TABLE',
123+
'WORKFLOW_V2_COMPATIBILITY_DISABLE_HEARTBEAT_TABLE',
124+
false
125+
),
111126
],
112127
'history_budget' => [
113128
'continue_as_new_event_threshold' => (int) Env::dw(

tests/Feature/V2/V2OperatorMetricsTest.php

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace Tests\Feature\V2;
66

77
use Illuminate\Support\Carbon;
8-
use Illuminate\Support\Facades\Schema;
98
use Tests\TestCase;
109
use Workflow\V2\Enums\ActivityStatus;
1110
use Workflow\V2\Enums\CommandOutcome;
@@ -50,31 +49,26 @@ public function testWorkerFleetSnapshotFallsBackToCacheWhenHeartbeatTableIsUnava
5049
->set('workflows.v2.compatibility.namespace', 'metrics-test');
5150

5251
WorkerCompatibilityFleet::clear();
53-
Schema::dropIfExists('workflow_worker_compatibility_heartbeats');
54-
55-
try {
56-
WorkerCompatibilityFleet::record(['build-a'], 'redis', 'default', 'worker-a');
57-
58-
$snapshot = OperatorMetrics::snapshot();
59-
60-
$this->assertSame(1, $snapshot['workers']['active_workers']);
61-
$this->assertSame(1, $snapshot['workers']['active_worker_scopes']);
62-
$this->assertSame(1, $snapshot['workers']['active_workers_supporting_required']);
63-
$this->assertCount(1, $snapshot['workers']['fleet']);
64-
$this->assertSame('worker-a', $snapshot['workers']['fleet'][0]['worker_id']);
65-
$this->assertSame('cache', $snapshot['workers']['fleet'][0]['source']);
66-
$this->assertTrue($snapshot['workers']['fleet'][0]['supports_required']);
67-
} finally {
68-
// Restore the dropped table within this test so the next test
69-
// does not depend on Testbench's per-test migrate:fresh racing
70-
// to re-create it. Re-running the migration's up() keeps the
71-
// restored schema in sync with the canonical definition.
72-
if (! Schema::hasTable('workflow_worker_compatibility_heartbeats')) {
73-
$migration = require __DIR__
74-
. '/../../../src/migrations/2026_04_08_000126_create_worker_compatibility_heartbeats_table.php';
75-
$migration->up();
76-
}
77-
}
52+
53+
// Force the fleet onto the legacy cache-only path via configuration
54+
// instead of dropping the heartbeats table. Dropping the table
55+
// mutated shared schema state and depended on Testbench's per-test
56+
// migrate:fresh in the next test to restore it, which raced when
57+
// the file's tests ran back-to-back in a single PHPUnit process.
58+
config()
59+
->set('workflows.v2.compatibility.disable_heartbeat_table', true);
60+
61+
WorkerCompatibilityFleet::record(['build-a'], 'redis', 'default', 'worker-a');
62+
63+
$snapshot = OperatorMetrics::snapshot();
64+
65+
$this->assertSame(1, $snapshot['workers']['active_workers']);
66+
$this->assertSame(1, $snapshot['workers']['active_worker_scopes']);
67+
$this->assertSame(1, $snapshot['workers']['active_workers_supporting_required']);
68+
$this->assertCount(1, $snapshot['workers']['fleet']);
69+
$this->assertSame('worker-a', $snapshot['workers']['fleet'][0]['worker_id']);
70+
$this->assertSame('cache', $snapshot['workers']['fleet'][0]['source']);
71+
$this->assertTrue($snapshot['workers']['fleet'][0]['supports_required']);
7872
}
7973

8074
public function testWorkerFleetSnapshotUsesRequestedNamespaceInsteadOfConfiguredCompatibilityNamespace(): void

0 commit comments

Comments
 (0)