School of Simulation Logo - Navigate to Homepage

Trustpilot Reviews

← All articles

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

FeatureResourcePriorityResourcePreemptiveResource
Queue orderFIFOPriorityPriority
InterruptsNoNoYes
Requires interrupt handlingNoNoYes
FairYesNoNo

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