Summary#
A parent parked at await Workflow.Run("pick", pick) is waiting for that pick. If the order is withdrawn meanwhile —
an event arm sends the parent to a terminal — the body that was waiting is gone, and nobody will ever read the pick's
outcome. So the pick is called off: its run is stopped, and if the pick was itself waiting on something, that is
stopped too.
You write nothing for this. It is what ending a run means.
Signature#
state Placed {
subscribe Withdraw();
enter { await Workflow.Run("pick", pick); goto Shipped; } // parked here…
on Withdraw { goto Withdrawn; } // …when this ends the run: the pick is stopped
}Description#
Which runs are called off?#
The ones the run was still waiting for when the code waiting for them ended:
| the run was… | and then… | called off |
|---|---|---|
parked at await Workflow.Run(…) | an event arm ended it, or somebody stopped it | the awaited child |
| parked at a join of legs (Parallel legs (start several, then wait for them)) | the same | every leg still running |
| parked on tasks that wait (Tasks that wait (Task.WhenAll across days)) | the same | the child each task waits on |
past a Task.WhenAny | its body finished | the child the losing task still waits on |
Which are not?#
Anything the run had stopped waiting for of its own accord:
- a child started without
await— fire-and-forget is a run of its own (Workflow.Run (start a workflow)); - a leg left running on purpose with
losers: Losers.Continue; - every child of a run whose own body carried it to its end — that body consumed its waits.
Where does a called-off run end?#
At its workflow's own terminal cancel state, when it declares one: the state it is in runs its exit { }, its
slots and clocks retire, and its tracked field takes the terminal's value — so the row says what happened to it. A
workflow that declares several cancel terminals ends at the one named Cancelled; one that declares none is ended
without a transition, and its tracked field stays where it was.
Stopping a child ends its waits by the same rule, so a tree is called off level by level, however deep.
Does compensation run?#
No. A called-off run is stopped where it stands, as the run that was waiting for it was. If withdrawing has to undo
something, say so where the withdrawal is written — in the arm that handles it — or keep the work inside a
saga whose own catch routes the cancellation.
Examples#
using Osysharp.Workflow;
enum OrderStatus { Placed, Shipped, Withdrawn }
enum PickStatus { Picking, Picked, CalledOff }
entity Order {
[Required, MaxLength(40)] string Reference;
OrderStatus Status = OrderStatus.Placed;
security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}
entity Pick {
[Required] Order Order;
PickStatus Status = PickStatus.Picking;
security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}
workflow PickFlow {
Tracks = Pick.Status; Autostart = false; Initial = Picking;
event Report();
state Picking { subscribe Report(); on Report { goto Picked; } }
terminal success Picked { }
terminal cancel CalledOff { Message = "nobody is waiting for this pick"; }
}
workflow OrderFlow {
Tracks = Order.Status; Autostart = false; Initial = Placed;
event Withdraw();
state Placed {
subscribe Withdraw();
enter {
var pick = Workflow.Once("make-pick", () => new Pick { Order = this.Item });
await Workflow.Run("pick", pick);
goto Shipped;
}
on Withdraw { goto Withdrawn; }
}
terminal success Shipped { }
terminal cancel Withdrawn { Message = "the order was withdrawn"; }
}
[Test]
void withdrawing_the_order_stops_the_pick_it_was_waiting_for() {
var order = new Order { Reference = "SO-1" };
Workflow.Run(order);
Workflow.Settle(order);
var pick = Pick.Single(p => p.Order == order);
Assert.Equal(PickStatus.Picking, pick.Status); // the order is parked, waiting for it
OrderFlow.RaiseWithdraw(order);
Workflow.Settle(order);
Assert.Equal(OrderStatus.Withdrawn, order.Status);
Workflow.Settle(pick);
Assert.Equal(PickStatus.CalledOff, pick.Status); // …and so the pick is called off, at its own terminal
}See also#
- Workflow.Run (start a workflow) — awaiting a child run
- Parallel legs (start several, then wait for them) — joins, and what happens to a leg that lost
- Tasks that wait (Task.WhenAll across days) — tasks that wait on child runs
- Transitions — where this item may go next — how an event arm moves a run