SimPy PreemptiveResource Explained: Interrupting for Priority

PreemptiveResource is the aggressive sibling of PriorityResource. It doesn't just jump the queue—it kicks out whoever's currently using the resource.

PriorityResource vs PreemptiveResource

PriorityResource: "I'll wait, but put me first in line."

PreemptiveResource: "Move. Now."

import simpy

# Priority: high priority waits for current user to finish
priority_resource = simpy.PriorityResource(env, capacity=1)

# Preemptive: high priority interrupts current user
preemptive_resource = simpy.PreemptiveResource(env, capacity=1)

Basic Example

import simpy

def task(env, name, resource, priority, duration):
    with resource.request(priority=priority) as req:
        yield req
        try:
            print(f"{name} starts at {env.now}")
            yield env.timeout(duration)
            print(f"{name} finishes at {env.now}")
        except simpy.Interrupt as interrupt:
            print(f"{name} preempted at {env.now}")

env = simpy.Environment()
cpu = simpy.PreemptiveResource(env, capacity=1)

env.process(task(env, "LowPriority", cpu, priority=10, duration=10))
env.process(task(env, "HighPriority", cpu, priority=1, duration=3))

env.run()

Output:

LowPriority starts at 0
HighPriority starts at 0
LowPriority preempted at 0
HighPriority finishes at 3

The high-priority task immediately interrupts the low-priority one.

Handling Preemption

When preempted, simpy.Interrupt is raised. You must handle it:

def job(env, resource, priority):
    with resource.request(priority=priority) as req:
        yield req
        try:
            yield env.timeout(10)
        except simpy.Interrupt:
            # What happens when preempted?
            print("I got kicked out!")
            # Maybe try again, maybe give up

Without the try/except, preemption crashes your process.

Resuming After Preemption

Common pattern: resume where you left off:

def resilient_job(env, resource, priority, total_work):
    remaining = total_work

    while remaining > 0:
        with resource.request(priority=priority) as req:
            yield req
            start = env.now
            try:
                yield env.timeout(remaining)
                remaining = 0  # Completed
            except simpy.Interrupt:
                remaining -= (env.now - start)  # Work done
                print(f"Preempted, {remaining:.1f} remaining")

    print(f"Completed at {env.now}")

The job keeps trying until all work is done, even if repeatedly preempted.

Real-World Examples

Machine Repair

def regular_job(env, machine, duration):
    with machine.request(priority=2) as req:
        yield req
        try:
            yield env.timeout(duration)
            print(f"Job completed at {env.now}")
        except simpy.Interrupt:
            print(f"Job interrupted for repair at {env.now}")

def repair(env, machine):
    with machine.request(priority=1) as req:  # Higher priority
        yield req
        print(f"Repair starts at {env.now}")
        yield env.timeout(5)
        print(f"Repair ends at {env.now}")

def breakdown(env, machine):
    yield env.timeout(3)  # Break down at time 3
    env.process(repair(env, machine))

env = simpy.Environment()
machine = simpy.PreemptiveResource(env, capacity=1)
env.process(regular_job(env, machine, 10))
env.process(breakdown(env, machine))
env.run()

Emergency Override

NORMAL = 10
EMERGENCY = 1

def use_resource(env, name, resource, priority):
    with resource.request(priority=priority) as req:
        yield req
        try:
            print(f"{name} using resource at {env.now}")
            yield env.timeout(20)
            print(f"{name} finished at {env.now}")
        except simpy.Interrupt:
            print(f"{name} yielded to emergency at {env.now}")

Preemption Order

Like PriorityResource, lower priority number = more important:

# Priority 1 preempts priority 5
# Priority 5 preempts priority 10
# Equal priorities: later arrival cannot preempt

Equal priorities don't preempt each other—that would cause infinite loops.

Preventing Preemption

Some requests shouldn't be preempted:

with resource.request(priority=5, preempt=False) as req:
    yield req
    # This won't be interrupted by higher priority
    yield env.timeout(10)

preempt=False means "I respect priority order in the queue, but once I'm running, don't interrupt me."

Statistics with Preemption

Track preemptions:

stats = {'completed': 0, 'preempted': 0}

def job(env, resource, priority):
    with resource.request(priority=priority) as req:
        yield req
        try:
            yield env.timeout(10)
            stats['completed'] += 1
        except simpy.Interrupt:
            stats['preempted'] += 1

# After simulation
total = stats['completed'] + stats['preempted']
print(f"Preemption rate: {stats['preempted']/total*100:.1f}%")

When to Use PreemptiveResource

Use when: - Emergency handling (machine breakdown repair) - Real-time systems with hard deadlines - Operating system CPU scheduling - Critical process must start immediately

Don't use when: - Work can't be resumed (preemption wastes it) - Fairness matters - Starvation of low priority is unacceptable - The real system doesn't actually preempt

The Danger of Preemption

Preemption can cause: - Wasted work (if preempted jobs restart from scratch) - Starvation (low priority never finishes) - Complexity (handling interrupts everywhere)

Use judiciously. Often PriorityResource is enough.

Comparison Table

Feature Resource PriorityResource PreemptiveResource
Queue order FIFO Priority Priority
Interrupts No No Yes
Requires interrupt handling No No Yes
Fair Yes No No

Example: Operating System Scheduler

def process(env, name, cpu, priority, burst_time):
    remaining = burst_time

    while remaining > 0:
        with cpu.request(priority=priority) as req:
            yield req
            start = env.now
            try:
                yield env.timeout(remaining)
                remaining = 0
                print(f"{name} finished at {env.now}")
            except simpy.Interrupt:
                remaining -= (env.now - start)
                print(f"{name} preempted, {remaining:.1f} remaining")

env = simpy.Environment()
cpu = simpy.PreemptiveResource(env, capacity=1)

# Low priority background task
env.process(process(env, "Background", cpu, priority=10, burst_time=20))

# High priority arrives later
def spawn_high_priority(env, cpu):
    yield env.timeout(5)
    env.process(process(env, "HighPriority", cpu, priority=1, burst_time=5))

env.process(spawn_high_priority(env, cpu))
env.run()

Summary

PreemptiveResource: - Interrupts lower-priority current users - Requires try/except for interrupt handling - Useful for emergencies and real-time systems - Can cause starvation and wasted work

Preemption is powerful. Use it when the situation demands it.

Next Steps


Discover the Power of Simulation

Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.

Explore the Bootcamp