Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

## 2.20.3 - 2026-11-04

- Rollback, embedded Workflow package: 2.20.3 changes no schema, history
event or public API. Rolling back restores the shared-cursor, empty
receive replay and fake task-drain precision bugs. A kept stream now
records receives at the current step. Earlier versions look them up at the opening step and can replay
those runs differently after rollback.
- A message on one stream stays receivable until it is consumed or expires
(#1011). The run's message cursor is shared by signals, updates and every
stream, so a signal, an update or a receive on another stream used to move
Expand All @@ -24,6 +31,18 @@
its replay matches too. Consumptions that earlier releases recorded at
the opened step still replay.

- `WorkflowStub::runReadyTasks()` now drains fake-workflow tasks at their
exact due time, including fractional seconds (#1026). It compares the
sampled clock using the configured task model's persisted precision.
SQLite previously skipped tasks due at that instant because it compared
their fractional timestamps with a whole-second query value. Future
fractional deadlines remain pending until they are due.
- Known limitation (#1024): a query can return nothing for a message an
update handler received, because the query replays the handler at the
wrong step. The query changes no message rows. The workflow's own replay
and completed output still return what the handler received. This issue
predates 2.20.3 and remains open.

## 2.20.2 - 2026-11-04

- Rollback, embedded Workflow package: 2.20.2 changes no schema, history
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"dev-main": "2.0.x-dev"
},
"durable-workflow": {
"product-train": "2.20.2",
"product-train": "2.20.3",
"laravel-embedded-upgrade-contract": "resources/laravel-embedded-upgrade-contract.json",
"laravel-dependency-security-policy": "resources/laravel-dependency-security-policy.json"
},
Expand Down
26 changes: 22 additions & 4 deletions tests/Unit/V2/ExporterExecutionTracingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public function testCollectorFailureDoesNotChangeColdWorkflowCompletion(string $
$server->stop();
}
try {
$admitted = $this->worker($directory, 'admit', $address);
$admitted = $this->worker($directory, 'admit', $address, $mode === 'refused' ? 0 : 1);
$workers = [];
for ($i = 0; $i < 3; ++$i) {
$workers[] = $this->worker($directory, 'work', $address);
$workers[] = $this->worker($directory, 'work', $address, $mode === 'refused' ? 0 : $i + 2);
}
$finished = $this->worker($directory, 'inspect', $address);
$finished = $this->worker($directory, 'inspect', $address, $mode === 'refused' ? 0 : 4);
self::assertSame('completed', $finished['status']);
self::assertSame([9007199254740993, null, '雪'], $finished['output']);
self::assertSame(3, $finished['tasks']);
Expand Down Expand Up @@ -230,7 +230,22 @@ private function requests(string $directory): array
) : [];
}

private function worker(string $directory, string $mode, string $address): array
private function awaitRequests(string $directory, int $count): array
{
$deadline = hrtime(true) + 1_000_000_000;

do {
$requests = $this->requests($directory);
if (count($requests) >= $count) {
return $requests;
}
usleep(1_000);
} while (hrtime(true) < $deadline);

return $this->requests($directory);
}

private function worker(string $directory, string $mode, string $address, int $expectedRequestCount): array
{
$process = new Process([
PHP_BINARY, __DIR__ . '/../../Fixtures/V2/tracing-process.php', $directory, $mode, 'once',
Expand All @@ -249,6 +264,9 @@ private function worker(string $directory, string $mode, string $address): array
if ($mode === 'work') {
self::assertStringContainsString('Export failure', $process->getErrorOutput());
}
// Settle this worker's timed-out export before the next worker
// snapshots the shared journal to detect premature exports.
self::assertCount($expectedRequestCount, $this->awaitRequests($directory, $expectedRequestCount));

return $result;
}
Expand Down
Loading