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

Download this collection ยท Source on GitHub

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.

More intermediate examples ยท Level guide

API contracts๏ƒ