On this page
Managed spell queue๏
๐ก Intermediate ยท Lesson 44
Wait for a pooled object instead of failing - managed_spell_mode="queue". A queue pool is limited_quantity with patience: at its limit a meld WAITS for a release, up to managed_spell_timeout_seconds (required for a queue, refused for the other modes). A release hands the waiting meld the released object. When the timeout passes first, the meld raises ManagedSpellExhaustedError naming the queue, its limit and its timeout.
The timeout bounds every wait, so a queue never blocks forever: choose it from how long a caller may afford to wait for a turn.
Before you run๏
Use the Intermediate guide for prerequisite concepts. Run from a checkout with Melder installed and Python 3.14 free-threading selected. The collection download includes the level's local helper modules.
Run the saved script๏
python UX_and_AIX_experiences/02_intermediate/44_managed_spell_queue.py
py -3.14t UX_and_AIX_experiences/02_intermediate/44_managed_spell_queue.py
Public surface๏
md.Spellbook.bind (managed_spell_mode, managed_spell_limit, managed_spell_timeout_seconds), md.ManagedSpellMode.queue, md.Conduit.meld, md.ManagedSpellExhaustedError (mode, limit, timeout_seconds), md.ReleaseManagedSpell (release, describe), md.Conduit.cleanup
Code๏
1"""
2TIER: intermediate (44)
3GOAL: Wait for a pooled object instead of failing - managed_spell_mode="queue".
4 A queue pool is limited_quantity with patience: at its limit a meld WAITS for a
5 release, up to managed_spell_timeout_seconds (required for a queue, refused for
6 the other modes). A release hands the waiting meld the released object. When the
7 timeout passes first, the meld raises ManagedSpellExhaustedError naming the
8 queue, its limit and its timeout.
9
10 The timeout bounds every wait, so a queue never blocks forever: choose it from
11 how long a caller may afford to wait for a turn.
12SURFACE EXERCISED: md.Spellbook.bind (managed_spell_mode, managed_spell_limit,
13 managed_spell_timeout_seconds), md.ManagedSpellMode.queue,
14 md.Conduit.meld, md.ManagedSpellExhaustedError (mode, limit,
15 timeout_seconds), md.ReleaseManagedSpell (release, describe),
16 md.Conduit.cleanup
17"""
18import threading
19import time
20from typing import List, Tuple
21
22import melder as md
23
24
25class GpuSlot:
26 """The one GPU slot a renderer may use; callers queue for it.
27
28 Bound with a generous timeout, so a waiting meld is served by a release.
29 """
30
31 def __init__(self) -> None:
32 """Reserve the slot; `jobs` counts the work done with it."""
33 self.jobs: int = 0
34
35
36class ExportSlot:
37 """A single export slot bound with a short timeout, to show the queue's refusal."""
38
39 def __init__(self) -> None:
40 """Reserve the slot."""
41 self.jobs: int = 0
42
43
44def pool_counts(cls: type) -> Tuple[int, int, int]:
45 """Return (alive, idle, out) for the one pool of a managed class."""
46 (snapshot,) = md.ReleaseManagedSpell().describe(cls)
47 return snapshot["count"], snapshot["idle_count"], snapshot["out_count"]
48
49
50def main() -> None:
51 """Hand a released slot to a waiting meld, then let a short wait run out.
52
53 Contract:
54 The waiting meld runs on a second thread. Its outcome is deterministic: the
55 pool's only slot is out, so the waiter can only finish after the release,
56 and it then holds exactly that slot. The refusal asserts the wait lasted at
57 least the bound timeout and carries the queue's policy.
58 Returns:
59 None.
60 """
61 book = md.Spellbook()
62 try:
63 book.bind(
64 spell=GpuSlot,
65 existence=md.Existence.managed_spell_quantity,
66 managed_spell_mode=md.ManagedSpellMode.queue,
67 managed_spell_limit=1,
68 managed_spell_timeout_seconds=5.0,
69 )
70 book.bind(
71 spell=ExportSlot,
72 existence=md.Existence.managed_spell_quantity,
73 managed_spell_mode="queue",
74 managed_spell_limit=1,
75 managed_spell_timeout_seconds=0.25,
76 )
77 conduit = book.conjure()
78 try:
79 held: GpuSlot = conduit.meld("GpuSlot")
80 held.jobs += 1
81 received: List[GpuSlot] = []
82 started = threading.Event()
83
84 def wait_for_slot() -> None:
85 """Meld a GpuSlot; with the only slot out, this waits for a release."""
86 started.set()
87 received.append(conduit.meld("GpuSlot"))
88
89 waiter = threading.Thread(target=wait_for_slot, name="renderer-2")
90 waiter.start()
91 started.wait()
92 time.sleep(0.2)
93 # The only slot is out, so the waiter cannot have finished.
94 assert waiter.is_alive() and not received
95 print("the second renderer is queued while the slot is out:", waiter.is_alive())
96
97 md.ReleaseManagedSpell().release(held)
98 waiter.join(timeout=5.0)
99 assert not waiter.is_alive()
100 assert received and received[0] is held and received[0].jobs == 1
101 assert pool_counts(GpuSlot) == (1, 0, 1)
102 print("the release handed the same slot to the waiting renderer:", received[0] is held)
103 md.ReleaseManagedSpell().release(received[0])
104
105 # A short timeout: the wait runs out and the meld is refused.
106 busy: ExportSlot = conduit.meld("ExportSlot")
107 began = time.monotonic()
108 try:
109 conduit.meld("ExportSlot")
110 raise AssertionError("a second export slot was leased past the limit")
111 except md.ManagedSpellExhaustedError as refused:
112 waited = time.monotonic() - began
113 assert waited >= 0.25
114 assert refused.mode == "queue" and refused.limit == 1
115 assert refused.timeout_seconds == 0.25
116 print(f"the export queue gave up after {waited:.2f} s:", refused.mode, "timeout", refused.timeout_seconds)
117 md.ReleaseManagedSpell().release(busy)
118 assert pool_counts(ExportSlot) == (1, 1, 0)
119 finally:
120 conduit.cleanup()
121 finally:
122 book.cleanup()
123
124
125if __name__ == "__main__":
126 main()
Check the outcome๏
The script contains its own assertions or demonstrated refusal paths. Run it to evaluate those checks against your installed version. The code above is taken directly from the saved file; no run output is invented here.