resources.md
277 lines · skills/simpy/references/resources.md at 5f2bc03
Shared resources
Reviewed 2026-10-01 against SimPy 4.1.2.
All resource operations return Events. An operation that cannot complete waits in the corresponding queue; yielding the Event suspends the process. Resource events are context managers so exception/interrupt unwinding can release an acquired request or cancel a pending operation.
Raw lines 1 to 9Choose by modeled quantity
| Primitive | Modeled state | Waiting operation |
|---|---|---|
Resource | limited concurrent users | request() |
PriorityResource | users plus priority queue | request(priority=...) |
PreemptiveResource | priority users that may be displaced | request(..., preempt=...) |
Container | homogeneous numeric level | put(amount), get(amount) |
Store | FIFO concrete objects | put(item), get() |
FilterStore | objects selected by predicate | get(filter) |
PriorityStore | comparable/priority-wrapped objects | put(item), get() |
These primitives implement synchronization and congestion. They do not decide whether a queue discipline or capacity assumption is valid for the real system.
Raw lines 10 to 24Resource
Resource(env, capacity=1) is semaphore-like. capacity must be positive. Observable state:
count: allocated slots;users: granted request events;queue: pending request events;capacity: maximum simultaneous users.
import simpy
def user(env, resource, duration):
with resource.request() as request:
yield request
yield env.timeout(duration)
env = simpy.Environment()
server = simpy.Resource(env, capacity=2)
for duration in (2, 3, 4):
env.process(user(env, server, duration))
env.run()The context manager calls release(request) after an acquired request and cancel() for an abandoned pending request. With manual management, always release the exact request token:
request = resource.request()
try:
yield request
yield env.timeout(2)
finally:
if request in resource.users:
resource.release(request)
elif not request.triggered:
request.cancel()release() succeeds immediately, even if passed a request that is not a current user; such a call does not make the model logically correct. Prefer the context manager.
PriorityResource
PriorityResource orders pending PriorityRequests by smaller numeric priority, then request time, then not preempt (preempting first). Exact key ties retain insertion order in the stable sorted queue. PriorityResource itself never preempts current users; the shared request key still includes the flag.
def prioritized(env, resource, name, priority):
with resource.request(priority=priority) as request:
yield request
print(name, "started", env.now)
yield env.timeout(2)
env = simpy.Environment()
resource = simpy.PriorityResource(env, capacity=1)
env.process(prioritized(env, resource, "routine", 10))
env.process(prioritized(env, resource, "urgent", 0))
env.run()Priority does not preempt a current user. A later high-priority request only overtakes queued lower-priority requests.
Raw lines 70 to 93PreemptiveResource
PreemptiveResource extends PriorityResource. A request accepts:
priority: lower number means higher priority;preempt=True: permit displacement of a lower-priority current user.
def task(env, resource, name, priority, duration):
with resource.request(priority=priority, preempt=True) as request:
try:
yield request
started = env.now
yield env.timeout(duration)
except simpy.Interrupt as interrupt:
cause = interrupt.cause
used = env.now - cause.usage_since
print(name, "preempted by", cause.by.name, "after", used)
env = simpy.Environment()
cpu = simpy.PreemptiveResource(env, capacity=1)
env.process(task(env, cpu, "background", 5, 10))
def urgent(env):
yield env.timeout(2)
yield env.process(task(env, cpu, "urgent", 0, 1))
env.process(urgent(env))
env.run()The preempted process receives simpy.Interrupt. Its cause is simpy.resources.resource.Preempted:
cause.by: Process that made the preempting request;cause.usage_since: time the displaced request began using the resource;cause.resource: resource involved.
The timeout representing interrupted work is not canceled; the process's callback is removed from it. Track completed work and schedule a new timeout to resume.
Priority outranks the preempt flag. A queued request with higher priority and preempt=False can prevent a lower-priority preempting request from displacing a user. Do not mix policies without a tested discipline specification.
Container
Container(env, capacity=float("inf"), init=0) models homogeneous matter. Current state is level.
put(amount)waits untillevel + amount <= capacity;get(amount)waits untillevel >= amount;- amounts must be strictly positive;
0 <= init <= capacity.
env = simpy.Environment()
tank = simpy.Container(env, capacity=100, init=40)
def transfer(env):
yield tank.put(30)
assert tank.level == 70
yield tank.get(20)
assert tank.level == 50
env.process(transfer(env))
env.run()Pending operations are visible through put_queue and get_queue. A blocked put or get can deadlock the model if no future process can change the level. Bound execution and assert conservation:
initial + completed_puts - completed_gets == final_level.
Store
Store(env, capacity=float("inf")) holds concrete Python objects. put(item) waits when full; get() waits when empty. Available objects are in items; pending operations are in put_queue and get_queue.
env = simpy.Environment()
buffer = simpy.Store(env, capacity=2)
def producer(env):
for item in ("a", "b", "c"):
yield buffer.put(item)
def consumer(env):
for _ in range(3):
item = yield buffer.get()
print(item)
env.process(producer(env))
env.process(consumer(env))
env.run()Store is FIFO for available items and ordinary pending requests.
Raw lines 169 to 194FilterStore
FilterStore.get(predicate) takes the first available item for which the predicate returns truthy:
env = simpy.Environment()
machines = simpy.FilterStore(env, capacity=2)
machines.items = [
{"id": "small", "size": 1},
{"id": "large", "size": 2},
]
def borrow_large(env):
machine = yield machines.get(lambda item: item["size"] >= 2)
yield env.timeout(1)
yield machines.put(machine)
env.process(borrow_large(env))
env.run()Unlike a plain FIFO get queue, a later FilterStore request can complete before an earlier request when only the later predicate matches. Predicates execute inside resource processing: keep them deterministic, fast, side-effect-free, and defined in trusted model code. The bundled JSON CLIs do not accept predicate code.
Raw lines 195 to 221PriorityStore
PriorityStore returns the smallest item according to comparison. Prefer simpy.PriorityItem(priority, item) so payloads need not be comparable:
env = simpy.Environment()
work = simpy.PriorityStore(env)
def schedule(env):
yield work.put(simpy.PriorityItem(10, "routine"))
yield work.put(simpy.PriorityItem(1, "urgent"))
first = yield work.get()
assert first.item == "urgent"
env.process(schedule(env))
env.run()Equal-priority PriorityItem objects do not guarantee FIFO order: the heap compares priorities without a stable insertion tie-breaker. If policy requires FIFO ties, use simpy.PriorityItem((priority, sequence_number), payload) with a unique monotonically increasing sequence number and test it. PriorityStore's items is a heap, not a fully sorted list; retrieve through get().
Waiting, cancellation, and reneging
Use a condition to race a request against patience:
def customer(env, resource, patience):
with resource.request() as request:
result = yield request | env.timeout(patience)
if request not in result:
return "reneged" # Pending request canceled on context exit.
yield env.timeout(2)
return "served"If retaining resource events outside a context manager:
- re-yield after a temporary interrupt if still waiting;
- call
cancel()when permanently abandoning a pending put/get/request; - release only an acquired Resource request;
- account for losing timeouts that remain in the event queue.
Monitoring caveat
Resource state may change synchronously when an operation is created and again when its callbacks are processed. Label whether a sample is pre-call, post-call, grant, release, or final. Time-weight state paths; do not average event samples. See monitoring.md.