Process Interaction
Packed orders now wait for a courier, who comes every 10 minutes and takes every order that is waiting. An order that waits too long goes by post instead, which costs more and takes longer. This page asks: when does a waiting order resume, and how long must it be allowed to wait so that most orders go by courier?
- Decision: how long a packed order may wait for the courier: 5 minutes or 8.
- Measures: the orders sent by courier and by post, and the mean wait for dispatch.
- Runs: two runs of an 8-hour day, one for each limit, each with the seed 42, up to the moment each order leaves the shop: the courier's trip and the post are left out.
- Assumptions: orders arrive at random, every order takes the same time to pack, and the courier is always on time.
What You Will Learn
This page shows how to:
- fire a signal from one process and wait for it in another;
- wait for the first of a signal and a time limit;
- measure how a limit splits the orders between two outcomes.
What You Add
This page adds a signal, courier_left, that the courier fires as it leaves, and a dispatch task that waits for either the signal or its limit, whichever comes first. The shop is the one from First Simulation: one packer, an order every 2 seconds on average and 1.5 seconds of packing.
A Signal
So far a process has waited for time, a resource or an item. Processes can also wait for each other. In SimPy one process yields an event, and another process succeeds it, which marks the event as happened. Every process waiting for the event then resumes. desflow gives such an event a name and calls it a signal. A signal is declared once, and a task fires it when the task finishes. The signal can fire again later, and each firing wakes every process that is waiting for it at that moment:
| docs/snippets/tutorials/process_interaction_low_level.py:signal | |
|---|---|
Signal holds one pending SimPy Event. wait() returns it. fire(value) succeeds it and puts a fresh one in its place. So a process that starts waiting after a firing waits for the next firing. SimPy and desflow describes events.
The courier arrives every 10 minutes, and its task fires the signal with its payload. Save the files from Full Files in one folder and run the commands there:
| docs/snippets/yaml/process_interaction.yaml:courier | |
|---|---|
| docs/snippets/tutorials/process_interaction.py:courier | |
|---|---|
| docs/snippets/tutorials/process_interaction_low_level.py:courier | |
|---|---|
Wait for the First of Two
A packed order waits for the courier or for its limit. SimPy waits for the first of several events with a | b, an AnyOf. desflow's wait does the same with any, whose branches each have a name, here courier and limit. After the wait, wait.<branch>.done says whether that branch happened, so next can route on it:
"${LIMIT:-5 min}" reads the environment variable LIMIT, 5 minutes when it is not set, so LIMIT="8 min" desflow run process_interaction.yaml runs the longer limit.
env.any_of([...]) is the AnyOf. Its value holds the events that happened, so courier in done is the YAML's wait.courier.done.
The task publishes queued when it starts waiting and started when the wait ends, so its wait statistics measure the time waiting for the courier:
What You Get and Why
When does a waiting order resume? It resumes when the courier leaves, if the courier leaves within the order's limit, and at the limit otherwise. Each figure here comes from one run of the 8-hour day with the seed 42. With a limit of 5 minutes, 6,952 orders go by courier and 7,020 by post. With a limit of 8 minutes, 11,005 go by courier and 2,872 by post, and the mean wait for dispatch rises from 225.3 to 288.2 seconds. How long must an order be allowed to wait? Of the two limits, 8 minutes is the one at which most orders go by courier, 79% of them. At 5 minutes, fewer than half do:

Each tab prints both runs: the orders sent by courier and by post, and the mean wait for dispatch.
Orders are packed at random moments, so the time from packing to the next courier is spread evenly over the 10 minutes between couriers. A limit of 5 minutes catches the courier for half the orders, and a limit of 8 minutes for 8 in 10. The runs show these shares: 49.8% and 79.3% of the orders go by courier. Every waiting order resumes at the same firing, so the courier takes them all at once. These shares come from one run, and Replications explains how runs with other seeds would give each share an interval.
SimPy and desflow shows the plain SimPy forms. Signals and Waiting describes every kind of wait, and Wait for One or All shows ties and failures.
Key Takeaways
- A signal wakes every task waiting for it at once.
- A wait for the first of two events resumes at whichever comes first: here the courier or the limit.
- The share of orders that catch the courier follows from the limit and the time between couriers.
Full Files
Full files
| docs/snippets/tutorials/process_interaction_low_level.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | |