-
Hello, EDIT: digging to the I've got a big DIY FSM with lots of states and signals. Workflow seems to be a great thing that will simplify the code, make it clear, and managable. The last puzzle piece I miss is a signal reactor with a mask. Sort of: public function execute()
{
// match only the signals in the mask or throw timeout exception
$signal = yield $this->waitFor(signals: [
MyEnumWithSignals::Signal1,
MyEnumWithSignals::Signal2,
MyEnumWithSignals::Signal3,
]);
dump($signal); // output the received signal
} My current implementation is the following: protected ?MyEnumWithSignals $lastSignal = null;
// somewhy calling signal method signal() causes silent failure
#[SignalMethod]
public function emit(MyEnumWithSignals $signal): void
{
$this->lastSignal = $signal;
}
public function waitFor(
array $signals = [],
int $timeout = 0,
): PromiseInterface
{
$waiter = function() use ($signals) { // predicate to determine if the signal is received
return $this->lastSignal !== null &&
(!$signals || in_array($this->lastSignal, $signals, true));
};
if ($timeout > 0) { // handle the timeout
// convenience method stands for WorkflowStub::awaitWithTimeout
$resp = $this->awaitWithTimeout($timeout, $waiter);
} else {
// convenience method stands for WorkflowStub::await
$resp = $this->await($waiter);
}
// That's the tricky part: after the signal is received (predicate() => true),
// get the actual signal and resolve the awaitable with it.
// originally, I've been trying to return sideEffect from the onResolved handler,
// but that didn't work
return $resp->then(function($value) {
if (!$value) { // timeout
return reject(new DomainException('Timeout'));
}
$signal = $this->lastSignal;
$this->lastSignal = null;
return resolve($signal);
});
}
So am I getting it right or I shouldn't mess with the Promise instance? Thank you. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 10 replies
-
Under the hood, a workflow execution from start to finish is not a single execution but several partial executions. After each partial execution, the workflow unloads from memory to wait for the activities to finish (which could be seconds or hours) and then reloads later. https://github.com/laravel-workflow/laravel-workflow/blob/master/src/Workflow.php#L199 During a partial execution, the result of a promise represents the result of an activity. If the activity hasn't finished, then an unresolved promise is returned. https://github.com/laravel-workflow/laravel-workflow/blob/master/src/ActivityStub.php#L53 Once you have this promise you can yield it to unload the workflow from memory and wait for the activity to complete. When the activity is complete, the workflow runs again and the same call to https://github.com/laravel-workflow/laravel-workflow/blob/master/src/ActivityStub.php#L46 When you yield the resolved promise, the workflow doesn't unload. Instead, it unwraps the promise and sends the result to the coroutine. https://github.com/laravel-workflow/laravel-workflow/blob/master/src/Workflow.php#L191 You can think of these promises as "logical" in the sense that they are made of several actual promises. Kind of like how a logical disk is made of many physical ones. I hope my explanation helped with understanding the inner workings more. As for what you're trying to achieve, if I understand correctly, this is how you would do it. use Workflow\ActivityStub;
use Workflow\SignalMethod;
use Workflow\Workflow;
use Workflow\WorkflowStub;
class MyWorkflow extends Workflow
{
private bool $ready1 = false;
private bool $ready2 = false;
#[SignalMethod]
public function setReady1($ready1)
{
$this->ready1 = $ready1;
}
#[SignalMethod]
public function setReady2($ready2)
{
$this->ready2 = $ready2;
}
public function execute()
{
$signaled = yield from $this->awaitAnyCondition([
fn() => $this->ready1,
fn() => $this->ready2,
], 30);
if (! $signaled) {
// handle timeout logic here
return 'timeout';
}
// handle normal logic here
return 'ok';
}
private function awaitAnyCondition(array $conditions, ?int $timeout = null)
{
$conditionCheck = fn () => array_reduce($conditions, fn ($carry, $condition) => $carry || $condition(), false);
if ($timeout) {
return yield WorkflowStub::awaitWithTimeout($timeout, $conditionCheck);
}
return yield WorkflowStub::await($conditionCheck);
}
} |
Beta Was this translation helpful? Give feedback.
For this we can modify the awaitAnyCondition method from earlier.