Back to Claude Scientific Skills

Shared resources

skills/simpy/references/resources.md

2.55.08.3 KB
Original Source

Shared resources

Verified 2026-07-23 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.

Choose by modeled quantity

PrimitiveModeled stateWaiting operation
Resourcelimited concurrent usersrequest()
PriorityResourceusers plus priority queuerequest(priority=...)
PreemptiveResourcepriority users that may be displacedrequest(..., preempt=...)
Containerhomogeneous numeric levelput(amount), get(amount)
StoreFIFO concrete objectsput(item), get()
FilterStoreobjects selected by predicateget(filter)
PriorityStorecomparable/priority-wrapped objectsput(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.

Resource

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.
python
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:

python
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. Equal-priority, equal-time ties use deterministic scheduler/queue ordering.

python
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.

PreemptiveResource

PreemptiveResource extends PriorityResource. A request accepts:

  • priority: lower number means higher priority;
  • preempt=True: permit displacement of a lower-priority current user.
python
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 until level + amount <= capacity;
  • get(amount) waits until level >= amount;
  • amounts must be strictly positive;
  • 0 <= init <= capacity.
python
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.

python
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.

FilterStore

FilterStore.get(predicate) takes the first available item for which the predicate returns truthy:

python
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.

PriorityStore

PriorityStore returns the smallest item according to comparison. Prefer simpy.PriorityItem(priority, item) so payloads need not be comparable:

python
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 payloads preserve the wrapper's comparison behavior; if business policy requires a specific tie-breaker, include one explicitly in a comparable dataclass/tuple and test it.

Waiting, cancellation, and reneging

Use a condition to race a request against patience:

python
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.

Sources

See sources.md for the 4.1.2 shared-resource guide and API reference.