Skill Tracer
Skills › Scientific Agent Skills › simpy › resources.md

resources.md

277 lines · skills/simpy/references/resources.md at 5f2bc03

Open on GitHub
1# Shared resources
2
3Reviewed 2026-10-01 against SimPy 4.1.2.
4
5All resource operations return Events. An operation that cannot complete waits in
6the corresponding queue; yielding the Event suspends the process. Resource events
7are context managers so exception/interrupt unwinding can release an acquired
8request or cancel a pending operation.
9

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 9
10## Choose by modeled quantity
12| Primitive | Modeled state | Waiting operation |
13|---|---|---|
14| `Resource` | limited concurrent users | `request()` |
15| `PriorityResource` | users plus priority queue | `request(priority=...)` |
16| `PreemptiveResource` | priority users that may be displaced | `request(..., preempt=...)` |
17| `Container` | homogeneous numeric level | `put(amount)`, `get(amount)` |
18| `Store` | FIFO concrete objects | `put(item)`, `get()` |
19| `FilterStore` | objects selected by predicate | `get(filter)` |
20| `PriorityStore` | comparable/priority-wrapped objects | `put(item)`, `get()` |
22These primitives implement synchronization and congestion. They do not decide
23whether a queue discipline or capacity assumption is valid for the real system.

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.

Raw lines 10 to 24
25## Resource
27`Resource(env, capacity=1)` is semaphore-like. `capacity` must be positive.
28Observable state:
30- `count`: allocated slots;
31- `users`: granted request events;
32- `queue`: pending request events;
33- `capacity`: maximum simultaneous users.
35```python
36import simpy
38def user(env, resource, duration):
39 with resource.request() as request:
40 yield request
41 yield env.timeout(duration)
43env = simpy.Environment()
44server = simpy.Resource(env, capacity=2)
45for duration in (2, 3, 4):
46 env.process(user(env, server, duration))
47env.run()
48```
50The context manager calls `release(request)` after an acquired request and
51`cancel()` for an abandoned pending request. With manual management, always release
52the exact request token:
54```python
55request = resource.request()
56try:
57 yield request
58 yield env.timeout(2)
59finally:
60 if request in resource.users:
61 resource.release(request)
62 elif not request.triggered:
63 request.cancel()
64```
66`release()` succeeds immediately, even if passed a request that is not a current
67user; such a call does not make the model logically correct. Prefer the context
68manager.

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

Raw lines 25 to 69
70## PriorityResource
72`PriorityResource` orders pending `PriorityRequest`s by smaller numeric priority,
73then request time, then `not preempt` (preempting first). Exact key ties retain
74insertion order in the stable sorted queue. `PriorityResource` itself never
75preempts current users; the shared request key still includes the flag.
77```python
78def prioritized(env, resource, name, priority):
79 with resource.request(priority=priority) as request:
80 yield request
81 print(name, "started", env.now)
82 yield env.timeout(2)
84env = simpy.Environment()
85resource = simpy.PriorityResource(env, capacity=1)
86env.process(prioritized(env, resource, "routine", 10))
87env.process(prioritized(env, resource, "urgent", 0))
88env.run()
89```
91Priority does not preempt a current user. A later high-priority request only
92overtakes queued lower-priority requests.

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 93
94## PreemptiveResource
96`PreemptiveResource` extends `PriorityResource`. A request accepts:
98- `priority`: lower number means higher priority;
99- `preempt=True`: permit displacement of a lower-priority current user.
101```python
102def task(env, resource, name, priority, duration):
103 with resource.request(priority=priority, preempt=True) as request:
104 try:
105 yield request
106 started = env.now
107 yield env.timeout(duration)
108 except simpy.Interrupt as interrupt:
109 cause = interrupt.cause
110 used = env.now - cause.usage_since
111 print(name, "preempted by", cause.by.name, "after", used)
113env = simpy.Environment()
114cpu = simpy.PreemptiveResource(env, capacity=1)
115env.process(task(env, cpu, "background", 5, 10))
117def urgent(env):
118 yield env.timeout(2)
119 yield env.process(task(env, cpu, "urgent", 0, 1))
121env.process(urgent(env))
122env.run()
123```
125The preempted process receives `simpy.Interrupt`. Its `cause` is
126`simpy.resources.resource.Preempted`:
128- `cause.by`: Process that made the preempting request;
129- `cause.usage_since`: time the displaced request began using the resource;
130- `cause.resource`: resource involved.
132The timeout representing interrupted work is not canceled; the process's callback
133is removed from it. Track completed work and schedule a new timeout to resume.
135Priority outranks the preempt flag. A queued request with higher priority and
136`preempt=False` can prevent a lower-priority preempting request from displacing a
137user. Do not mix policies without a tested discipline specification.

PreemptiveResource

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.

Raw lines 94 to 138
139## Container
141`Container(env, capacity=float("inf"), init=0)` models homogeneous matter. Current
142state is `level`.
144- `put(amount)` waits until `level + amount <= capacity`;
145- `get(amount)` waits until `level >= amount`;
146- amounts must be strictly positive;
147- `0 <= init <= capacity`.
149```python
150env = simpy.Environment()
151tank = simpy.Container(env, capacity=100, init=40)
153def transfer(env):
154 yield tank.put(30)
155 assert tank.level == 70
156 yield tank.get(20)
157 assert tank.level == 50
159env.process(transfer(env))
160env.run()
161```
163Pending operations are visible through `put_queue` and `get_queue`. A blocked put
164or get can deadlock the model if no future process can change the level. Bound
165execution and assert conservation:
167`initial + completed_puts - completed_gets == final_level`.

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

Raw lines 139 to 168
169## Store
171`Store(env, capacity=float("inf"))` holds concrete Python objects. `put(item)` waits
172when full; `get()` waits when empty. Available objects are in `items`; pending
173operations are in `put_queue` and `get_queue`.
175```python
176env = simpy.Environment()
177buffer = simpy.Store(env, capacity=2)
179def producer(env):
180 for item in ("a", "b", "c"):
181 yield buffer.put(item)
183def consumer(env):
184 for _ in range(3):
185 item = yield buffer.get()
186 print(item)
188env.process(producer(env))
189env.process(consumer(env))
190env.run()
191```
193Store is FIFO for available items and ordinary pending requests.

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 194
195## FilterStore
197`FilterStore.get(predicate)` takes the first available item for which the predicate
198returns truthy:
200```python
201env = simpy.Environment()
202machines = simpy.FilterStore(env, capacity=2)
203machines.items = [
204 {"id": "small", "size": 1},
205 {"id": "large", "size": 2},
208def borrow_large(env):
209 machine = yield machines.get(lambda item: item["size"] >= 2)
210 yield env.timeout(1)
211 yield machines.put(machine)
213env.process(borrow_large(env))
214env.run()
215```
217Unlike a plain FIFO get queue, a later FilterStore request can complete before an
218earlier request when only the later predicate matches. Predicates execute inside
219resource processing: keep them deterministic, fast, side-effect-free, and defined
220in trusted model code. The bundled JSON CLIs do not accept predicate code.

FilterStore

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 221
222## PriorityStore
224`PriorityStore` returns the smallest item according to comparison. Prefer
225`simpy.PriorityItem(priority, item)` so payloads need not be comparable:
227```python
228env = simpy.Environment()
229work = simpy.PriorityStore(env)
231def schedule(env):
232 yield work.put(simpy.PriorityItem(10, "routine"))
233 yield work.put(simpy.PriorityItem(1, "urgent"))
234 first = yield work.get()
235 assert first.item == "urgent"
237env.process(schedule(env))
238env.run()
239```
241Equal-priority `PriorityItem` objects do not guarantee FIFO order: the heap compares
242priorities without a stable insertion tie-breaker. If policy requires FIFO ties,
243use `simpy.PriorityItem((priority, sequence_number), payload)` with a unique
244monotonically increasing sequence number and test it. PriorityStore's `items` is a
245heap, not a fully sorted list; retrieve through `get()`.

PriorityStore

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().

Raw lines 222 to 246
247## Waiting, cancellation, and reneging
249Use a condition to race a request against patience:
251```python
252def customer(env, resource, patience):
253 with resource.request() as request:
254 result = yield request | env.timeout(patience)
255 if request not in result:
256 return "reneged" # Pending request canceled on context exit.
257 yield env.timeout(2)
258 return "served"
259```
261If retaining resource events outside a context manager:
263- re-yield after a temporary interrupt if still waiting;
264- call `cancel()` when permanently abandoning a pending put/get/request;
265- release only an acquired Resource request;
266- account for losing timeouts that remain in the event queue.

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.
Raw lines 247 to 267
268## Monitoring caveat
270Resource state may change synchronously when an operation is created and again when
271its callbacks are processed. Label whether a sample is pre-call, post-call, grant,
272release, or final. Time-weight state paths; do not average event samples. See
273`monitoring.md`.

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.

Raw lines 268 to 274
275## Sources
277See `sources.md` for the 4.1.2 shared-resource guide and API reference.

Sources

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

Raw lines 275 to 277