Acquiring, Preempting, Holding, and Releasing Resources
This tutorial mirrors the second C tutorial: first the resource concepts, then process signals, then a small mouse/rat/cat stress test showing preemption and interruption.
Resources and Resource Pools
Cimba Python provides the same two resource concepts:
cimba.Resourceis a binary semaphore. One process may hold it.cimba.ResourcePoolis a counting semaphore. Several processes may hold units from a fixed capacity.
The ordinary pattern is identical to C:
assert server.acquire() == cimba.SUCCESS
try:
cimba.hold(service_time)
finally:
server.release()
For a resource pool:
assert tugs.acquire(3) == cimba.SUCCESS
cimba.hold(0.5)
tugs.release(3)
If a process requests more units than the pool capacity, the binding raises an error from the native assertion path. If the request is valid but unavailable, the process waits in priority order until the units can be acquired or until it is interrupted, stopped, or preempted.
Preemptions and Interruptions
Calls that may yield return a signal. The most important values are:
cimba.SUCCESS: the call completed normally.cimba.PREEMPTED: a higher-priority process took a held resource.cimba.INTERRUPTED: a generic interruption signal.cimba.STOPPED,cimba.CANCELLED, andcimba.TIMEOUTfor other native process outcomes.
Positive integer signals are available to the application:
def cat(target):
cimba.hold(0.5)
target.interrupt(77)
def waiting_mouse(cheese):
sig = cheese.acquire(1)
if sig == 77:
print("the cat interrupted this wait")
Preemption uses process priorities. A higher-priority process can call
preempt() to acquire a resource by taking it from lower-priority holders:
def mouse(me, resource):
assert resource.acquire() == cimba.SUCCESS
sig = cimba.hold(10.0)
if sig == cimba.PREEMPTED:
assert resource.held_by(me) == 0
def rat(me, resource):
cimba.hold(1.0)
me.priority = 10
assert resource.preempt() == cimba.SUCCESS
resource.release()
Targets like mouse and rat need pass_process=True when started
because they inspect or update the cimba.Process object.
Buffers and Object Queues, Interrupted
Buffers and queues are not preempted in the same sense as resources, but a waiting put or get can be interrupted. The return values carry the partial state:
sig, obtained = buffer.get(10)
if sig != cimba.SUCCESS:
print(f"only got {obtained} before interruption")
For object queues and priority queues, interrupted gets return (signal,
None) and interrupted puts return a non-success signal.
While the Cat Is Away…
The C tutorial uses a lively model of mice acquiring cheese, rats preempting it, and a cat interrupting waiting rodents. The Python tutorial keeps the same semantics in compact, deterministic functions that are easy to test:
import cimba
def run_preemption_demo() -> list[tuple]:
log = []
def mouse(me, resource):
assert resource.acquire() == cimba.SUCCESS
log.append(("mouse-acquired", cimba.time(), resource.held_by(me)))
sig = cimba.hold(10.0)
log.append(("mouse-hold-returned", cimba.time(), sig, resource.held_by(me)))
def rat(me, resource):
cimba.hold(1.0)
me.priority = 10
assert resource.preempt() == cimba.SUCCESS
log.append(("rat-preempted", cimba.time(), resource.held_by(me), resource.available))
resource.release()
with cimba.Simulation(seed=21) as sim:
cheese = cimba.Resource("Cheese")
cimba.Process("Mouse", mouse, cheese, priority=0, pass_process=True).start()
cimba.Process("Rat", rat, cheese, priority=0, pass_process=True).start()
sim.execute()
return log
def run_interruption_demo() -> list[tuple]:
log = []
def holder(cheese):
assert cheese.acquire(4) == cimba.SUCCESS
cimba.hold(2.0)
cheese.release(4)
def waiting_mouse(me, cheese):
cimba.hold(0.1)
sig = cheese.acquire(1)
log.append(("waiting-mouse", cimba.time(), sig, cheese.held_by(me), cheese.in_use))
def cat(target):
cimba.hold(0.5)
target.interrupt(77)
with cimba.Simulation(seed=22) as sim:
cheese = cimba.ResourcePool("Cheese", capacity=4)
cimba.Process("Holder", holder, cheese, priority=0).start()
target = cimba.Process("WaitingMouse", waiting_mouse, cheese, priority=0, pass_process=True).start()
cimba.Process("Cat", cat, target).start()
sim.execute()
return log
def main() -> None:
print(run_preemption_demo())
print(run_interruption_demo())
if __name__ == "__main__":
main()
The important lesson is the same as in C: any call that may yield can return
with a signal other than SUCCESS. Model code should decide what those
signals mean and update its own Python state accordingly.
Real World Uses
Preemption and interruption are useful in models where priority changes matter: emergency-room triage, machine breakdowns, operating-system scheduling, transportation disruptions, and manufacturing job shops. In Python these interactions use the same native Cimba scheduler and resource guards as the C API, with Python objects carrying the model-specific state.