Skip to main content
Version: 2.0 prerelease

Signal + Timer

Workflow\V2 supports both await($condition, timeout: $seconds, conditionKey: $key) for timeout-backed condition waits and await('signal-name', timeout: $seconds) for timeout-backed named signal waits.

Use it when the workflow should continue as soon as some durable replayed state becomes true, but should also unblock after a deadline if that state never changes.

use Workflow\UpdateMethod;
use function Workflow\V2\await;
use Workflow\V2\Attributes\Type;
use Workflow\V2\Workflow;

use function Workflow\V2\minutes;

#[Type('approval-with-timeout')]
class MyWorkflow extends Workflow
{
private bool $ready = false;

public function handle(): string
{
$approved = await(fn () => $this->ready, timeout: minutes(5), conditionKey: 'approval.ready');

return $approved ? 'approved' : 'timed out';
}

#[UpdateMethod]
public function markReady(bool $ready = true): array
{
$this->ready = $ready;

return ['ready' => $this->ready];
}
}

await() with a timeout: parameter works like this:

  • The predicate must depend only on replayed durable inputs (activity results, update mutations, child-workflow results). Non-durable state will not wake the wait.
  • The optional condition key is a stable operator label for the wait. Replay validates the key against prior history so a redeployment cannot silently reuse the same workflow step for a different predicate.
  • If the predicate becomes true before the timer fires, await() returns true.
  • If the deadline wins first, await() returns false.

Timer sugar helpers are available for readable timeout values: seconds(), minutes(), hours(), days(), weeks(), months(), years().

To change the predicate durably from application code, call the declared update:

use Workflow\V2\WorkflowStub;

$workflow = WorkflowStub::load('approval-with-timeout');

$workflow->markReady();

The return value is true when the condition becomes true before the timeout task fires and false when the timeout wins.

For named signals, await('name', timeout: minutes(5)) returns the signal payload when the signal arrives and null when the timeout wins. null is reserved for timeout: no-argument signals resolve to true, one argument resolves to that value, and multiple arguments resolve to an array. Workflow\V2 does not use legacy #[SignalMethod] mutator methods to flip workflow state.

awaitWithTimeout() Helper

Workflow::awaitWithTimeout($timeout, $condition, $conditionKey = null) is a sugar wrapper for await($condition, timeout: $timeout, conditionKey: $conditionKey). The argument order puts the deadline first to make the timeout intent obvious at the call site.

use function Workflow\V2\{await, minutes};
use Workflow\V2\Workflow;

#[Type('approval-with-timeout')]
class MyWorkflow extends Workflow
{
private bool $ready = false;

public function handle(): string
{
$approved = Workflow::awaitWithTimeout(
minutes(5),
fn () => $this->ready,
'approval.ready',
);

return $approved ? 'approved' : 'timed out';
}

#[UpdateMethod]
public function markReady(bool $ready = true): array
{
$this->ready = $ready;

return ['ready' => $this->ready];
}
}

Return semantics are the same as await() with a timeout: parameter: the call returns true when the condition wins, false when the deadline wins for a callable predicate, the signal payload when a named signal wins, and null when the deadline wins for a named signal.

awaitWithTimeout() reads "wait at most this long for a condition" rather than "wait for a condition, with a timeout option". Use whichever spelling reads more clearly at the call site — the runtime treats them identically and they record the same workflow history.