Modeling Machine Breakdowns in SimPy with PreemptiveResource and Process Interrupts
Why Breakdown Logic Makes or Breaks a Factory Model
Commercial packages such as Plant Simulation or FlexSim come with failure profiles you set up in a dialog box. SimPy, the open-source discrete-event simulation (DES) library for Python, has nothing like that. You build failures yourself from two primitives: process interrupts and priority-based preemptive resources. That takes more work, but every assumption is visible in your code and can be tested. For engineers who already analyze shop-floor data in Python, it is often the fastest way to get from an MTBF spreadsheet to a validated capacity model.
This article walks through the standard pattern, which comes from SimPy's own Machine Shop example, and covers the details that most often cause wrong results.
The Two Building Blocks
1. Process.interrupt(): Each machine runs as a generator process that loops over parts. A separate failure process sleeps for an exponentially distributed time to failure, then calls machine_process.interrupt(). Inside the machine, the yield env.timeout(...) in progress raises a simpy.Interrupt exception. The machine catches it, records how much processing time is left on the part, and goes to repair.
2. simpy.PreemptiveResource: Repair technicians are a shared, limited resource. With a PreemptiveResource, a request with a lower priority number can bump a lower-priority user off the resource. In the Machine Shop pattern, repairs request the technician at priority=1, and low-value "other jobs" such as housekeeping or setup preparation hold the technician at priority=2. A breakdown therefore takes the technician immediately instead of waiting in a FIFO queue.
The core of the machine loop looks like this:
def working(self, repairman):
while True:
remaining = random.normalvariate(PT_MEAN, PT_SIGMA)
while remaining > 0:
start = self.env.now
try:
yield self.env.timeout(remaining)
remaining = 0
except simpy.Interrupt:
self.broken = True
remaining -= self.env.now - start
with repairman.request(priority=1) as req:
yield req
yield self.env.timeout(random.expovariate(1 / MTTR))
self.broken = False
self.parts_made += 1
Best Practices That Protect Validity
Guard against double interrupts. If the failure process fires while the machine is already broken, SimPy raises the interrupt inside the repair code, which is almost never what you want. Check a broken flag before interrupting, as the reference example does.
Decide whether failures are time-based or operation-based. The pattern above ages machines on calendar time, so a starved or blocked machine can still fail. For wear-driven failures, have the failure clock advance only while the machine is busy. Otherwise availability in lightly loaded scenarios will be too pessimistic.
Handle the preempted job explicitly. When a repair bumps a priority-2 job, that job's process gets its own Interrupt, and cause is a Preempted object with by and usage_since attributes. Decide whether the work resumes or starts over, and code it. The resources guide covers the semantics.
Log state transitions, not just totals. Write (machine, start, end, state) tuples like the timeline in the hero image. A Gantt view quickly shows the effect that matters most in multi-machine cells: repair-crew interference. In the chart, Machine 2 spends longer waiting for the single technician than it spends being repaired.
Use common random numbers and replications. Give each scenario the same seeds so that differences between scenarios reflect the design, not sampling noise. Then report confidence intervals across independent replications.
Reading the Results

The chart above sweeps machine MTTF for a five-machine cell with one technician, a 30-minute mean repair time and a nominal 10-minute cycle. Throughput is strongly nonlinear. Going from a 60- to a 240-minute MTTF doubles weekly output, from about 506 to about 1,012 parts. Beyond roughly 1,000 minutes, extra reliability buys little. Notice that a simple availability formula, MTTF / (MTTF + MTTR), overestimates output at low MTTF because it ignores technicians queuing. That gap is exactly what a DES model is for, and it is often the argument for adding a second technician rather than paying for a premium preventive-maintenance contract.
Where to Take It Next
- Replace exponential failures with Weibull distributions fitted to CMMS history to model wear-out.
- Add a preventive-maintenance process that requests the technician at an intermediate priority.
- Put the model behind a parameter sweep or an optimizer to size crews and buffers together.
SimPy is MIT-licensed and installs with pip install simpy (PyPI). The breakdown pattern shown here is about 50 lines of Python and fits into any analytics pipeline.