Resources: DynamicResource
A resource is what an entity must hold while it works, and may have to wait for. This page shows how to declare one, how its queue is ordered, how it hands out units, and what happens when its capacity changes during a run.
What It Is
A resource is a number of identical units: packers, packing benches, checkout lanes, loading bays. A task asks for one unit, waits in the resource's queue while every unit is busy, holds the unit while it works, and gives it back. How many units there are is the resource's capacity, and it can change while the run goes on, as when packers go on a break.
How to Use It
Declare a Resource
A resource has a current_cap, the number of units now, and a max_cap, the most a live change can set. run() builds a DynamicResource for each one the context declares; in the low-level API you build it after registering the parameters:
packers = DynamicResource(env, "Shop", "packers") # capacity from the registry: 2
def order(env, order_id):
with packers.request() as request:
yield request # waits until a unit is free
yield env.timeout(10.0) # holds the unit for 10 s
# leaving the with block releases the unit
packers.request() is an event that happens when a unit is granted, and leaving the with block gives the unit back. A request whose with block is left before it is granted, for example after a timeout, is cancelled and never takes a unit.
Queue Order
queue sets the order in which a resource serves the requests waiting for it:
fifo, the default: the oldest first;lifo: the newest first;priority: the lowest priority number first, and the oldest first among equal priorities.
Four requests that join while the only unit is busy are served in these orders:
def served_order(queue):
"""Four requests join while the only bench is busy; returns who is served when."""
env = DynamicRealtimeEnvironment(factor=0.0)
env.registry.register_sim_parameter(
SimParameter(
sim_id="Shop",
resources={"benches": CapacityConfig(current_cap=1, max_cap=1)},
)
)
bench = DynamicResource(env, "Shop", "benches", queue=queue)
served = []
def use(name, priority, after=0.0):
yield env.timeout(after)
# The priority counts only under the priority order
with bench.request(priority=priority) as request:
yield request
served.append(name)
yield env.timeout(1.0)
env.process(use("first", 1)) # takes the bench at once
for name, priority in [
("orders-1", 1),
("express-1", 0),
("orders-2", 1),
("express-2", 0),
]:
env.process(use(name, priority, after=0.1)) # all join while it is busy
env.run(until=10)
return served[1:]
for queue in ("fifo", "lifo", "priority"):
print(f"{queue:<8} {', '.join(served_order(queue))}")
fifo orders-1, express-1, orders-2, express-2
lifo express-2, orders-2, express-1, orders-1
priority express-1, express-2, orders-1, orders-2
capacity 2, in use 4
The last line is the capacity drop that Under Live Changes describes, below. The same four requests, drawn:

Task Priority
Under queue: priority, each task gives its requests a priority: a number, the same for every run of the task; an expression, read when the task joins the queue, such as attr.tier for the entity's attribute tier; or, in the builder, a function of the task id and the entity's attributes. The attributes exist before the request queues, and the task's own payload does not, so a priority cannot read it; an expression can read the previous task's payload as payload.<field>.
app = (
SimulationContext(sim_id="Shop", factor=0.0, random_seed=42)
.add_resource("packers", current_cap=1, max_cap=4, queue="priority")
.add_service("packing", dist="constant", value=1.5)
.add_arrival("orders", dist="exponential", rate=0.5, attributes={"tier": 2})
)
# Each order's own priority, from its attributes, when it joins the queue;
# a number or an expression such as "attr.tier" works too
@app.task(
service_id="packing",
resource_id="packers",
priority=lambda task_id, attributes: attributes["tier"],
)
def pack_order(task_id: int):
return {"event_type": "order_packed"}
A low-level process passes the priority to the request: packers.request(priority=entity.attributes["tier"]), as the queue-order example above does. A resource whose queue is fifo or lifo ignores it.
A priority on a task whose resource does not have queue: priority is refused: when the blueprint loads, or when run() starts. Priorities shows what priority does to waits.
Registry Paths and Readings
Each resource has two registry paths, <sim_id>.resources.<name>.current_cap and .max_cap, which a scenario, an ingress message or a process can change; Live Parameters explains how. With statistics at the minimal level or above, each resource publishes four readings, such as Shop.stats.resources.packers.utilization: capacity, in_use, queue_length and utilization, the busy units as a percentage of the capacity. Statistics lists what the other levels add.
How It Works
A DynamicResource is built from SimPy parts. Each request waits in a SimPy PriorityStore, ranked by the queue order and a sequence number, so requests that rank the same are served in the order they arrived. Free units are tokens in a SimPy Container, the pool, whose size follows the registry. A dispatcher process waits for a request and a token, and gives the token to the first request:

If the request it takes was cancelled, the dispatcher puts the token back and takes the next. in_use counts the units held by tasks: it goes up when a request is granted and down when a unit is released.
SimPy and desflow. SimPy's Resource serves requests in the order they arrive, and PriorityResource by priority. Both take their capacity when they are created and keep it: capacity has no setter, so assigning a new one raises AttributeError (SimPy 4.1). desflow builds its own resource so the capacity can follow the registry and the queue order can be chosen; the request() call and the with block are SimPy's, so code written for SimPy works unchanged. SimPy and desflow compares the two.
Under Live Changes
A change to current_cap takes effect at once, without stopping any work:
- Growing adds tokens to the pool, and waiting requests start.
- Shrinking removes idle tokens at once. The units still busy above the new capacity are owed: while tokens are owed, each released token is discarded instead of returning to the pool, so no waiting task starts until the busy units are within the new capacity.
- Growing again before the owed tokens arrive first cancels them, then adds any difference to the pool.
- A
current_capoutside 0 tomax_capis clamped to that range, with a warning. - Lowering
max_caplowers the pool's limit. Acurrent_capabove the newmax_capis stored asmax_cap, with a warning.
The example above takes three of five packers off the floor while four orders are being packed: the capacity is 2 at once, and in_use stays 4 until two orders finish. The animation steps through it:

def breakdown(env):
"""Takes 3 of 5 packers off the floor at t=5 s. Orders being packed are not stopped."""
env.registry.update("Shop.resources.packers.current_cap", 5)
yield env.timeout(5.0)
env.registry.update("Shop.resources.packers.current_cap", 2)
yield env.timeout(0.1) # the resource applies the change in its own process
print(f"capacity {packers.capacity}, in use {packers.in_use}")
Until the owed units finish, in_use is above capacity, and the utilization reading is above 100.
Stability and Limits
- Utilisation decides waits. A resource whose load is at or above its capacity has a queue that grows for as long as the run lasts; Queues and Capacity shows how to work it out.
- Priority needs
queue: priority. A task priority on a resource of another order is a load error, while a low-level request's priority is ignored underfifoandlifo. - Priority can starve. Under
priority, a request with a high number is served only when no lower number is waiting; if the low numbers alone keep the resource busy, it is never served. - One resource per task. A task that needs two resources at once is a process: Multi-Resource Handoffs.
- A shrink never stops work. A resource that must stop work in progress, such as a breakdown, needs a process: Preemptive Breakdowns.
Amounts and items that tasks take and add, rather than hold, are Stores and Containers.