Preemptive Breakdowns
A preemptive breakdown stops a machine in the middle of a job. Here a label printer jams while it prints an order's shipping label. The order stays on the printer while it is repaired, then the rest of its work continues. No other order can start while the printer is down.

@app.task waits out the service time inside its own generator, so your code cannot catch a simpy.Interrupt there. A breakdown needs a task written as a generator that you start with context.spawn, and a process that interrupts it.
Example
docs/snippets/guides/preemptive_breakdowns.py
import logging
import simpy
from desflow import ConsoleEgress, SimulationContext
logging.basicConfig(level=logging.INFO)
LABELLING = 5.0 # seconds to print and attach the shipping label of one order
REPAIR = 10.0 # seconds the printer is down after a jam
JAM_EVERY = 12.0 # seconds between jams
app = (
SimulationContext(sim_id="Shop", factor=1.0)
.add_resource("printer", current_cap=1, max_cap=1)
.add_arrival("orders", dist="exponential", rate=0.2)
.add_egress(ConsoleEgress())
)
# The process of the order being labelled, if any, so a jam can interrupt it.
on_printer = []
def worker_task(context: SimulationContext, task_id: int):
printer = context.get_resource("printer")
key = f"task-{task_id}"
context.env.publish_event(key, {"status": "queued"})
with printer.request() as req:
yield req
context.env.publish_event(key, {"status": "started"})
remaining = LABELLING
while remaining > 0:
on_printer.append(context.env.active_process)
began = context.env.now
try:
yield context.env.timeout(remaining)
remaining = 0
except simpy.Interrupt as interrupt:
remaining -= context.env.now - began
context.env.publish_event(
key, {"status": "interrupted", "cause": interrupt.cause}
)
on_printer.remove(context.env.active_process)
if remaining > 0:
# The order stays on the printer, so no other order can start, and a
# printer under repair cannot jam again.
yield context.env.timeout(REPAIR)
context.env.publish_event(key, {"status": "printer_repaired"})
context.env.publish_event(key, {"status": "finished"})
def chaos_monkey(context: SimulationContext):
while True:
yield context.env.timeout(JAM_EVERY)
if on_printer: # an idle printer has nothing to interrupt
on_printer[0].interrupt("paper_jam")
@app.arrival_loop("orders")
def arrival_loop(context: SimulationContext):
context.spawn(chaos_monkey(context))
task_id = 0
while True:
yield context.wait_for_arrival("orders")
context.spawn(worker_task(context, task_id))
task_id += 1
if __name__ == "__main__":
app.run(until=40.0)
Save it as preemptive_breakdowns.py and run python preemptive_breakdowns.py. It runs for 40 seconds in real time (factor=1.0) and prints each event.
How It Works
on_printerholds the process of the order being labelled, so the breakdown process knows what to interrupt.worker_tasktakes the printer and labels forLABELLINGseconds. When it is interrupted, it subtracts the time already worked, publishesinterruptedwith the cause, keeps the printer forREPAIRseconds, publishesprinter_repairedand labels the rest. It publishesfinishedwhen the work is done.chaos_monkeyinterrupts the order on the printer everyJAM_EVERYseconds. An idle printer, or one under repair, has nothing to interrupt, so it does not jam. For jams at random times, replace the fixed wait with a random draw such asrandom.expovariate(1 / JAM_EVERY).arrival_loopstartschaos_monkeyonce, then starts aworker_taskfor each arriving order.