On this page
Managed spell reset methods๏
๐ก Intermediate ยท Lesson 45
Hand the next caller a clean object - managed_spell_reset_methods. A pooled object is reused, so whatever the last holder left in it would leak to the next one. Name its reset methods at bind: zero-argument methods the pool calls, in the order given, on EVERY release, before the object is idle again. The next lease then gets the same object, reset.
A reset that raises means the object cannot be trusted: the pool destroys it (its disposal methods run, its place is freed) and the reset's error propagates from the release. The next meld builds a fresh object.
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/45_managed_spell_reset_methods.py
py -3.14t UX_and_AIX_experiences/02_intermediate/45_managed_spell_reset_methods.py
Public surface๏
md.Spellbook.bind (managed_spell_reset_methods, disposal_method_names), md.Existence.managed_spell_quantity, md.Conduit.meld, md.ReleaseManagedSpell (release, describe), md.Conduit.cleanup
Code๏
1"""
2TIER: intermediate (45)
3GOAL: Hand the next caller a clean object - managed_spell_reset_methods.
4 A pooled object is reused, so whatever the last holder left in it would leak to
5 the next one. Name its reset methods at bind: zero-argument methods the pool
6 calls, in the order given, on EVERY release, before the object is idle again.
7 The next lease then gets the same object, reset.
8
9 A reset that raises means the object cannot be trusted: the pool destroys it
10 (its disposal methods run, its place is freed) and the reset's error propagates
11 from the release. The next meld builds a fresh object.
12SURFACE EXERCISED: md.Spellbook.bind (managed_spell_reset_methods,
13 disposal_method_names), md.Existence.managed_spell_quantity,
14 md.Conduit.meld, md.ReleaseManagedSpell (release, describe),
15 md.Conduit.cleanup
16"""
17from typing import List, Tuple
18
19import melder as md
20
21
22class ReportBuilder:
23 """Accumulates report lines for one request; must start empty for the next one.
24
25 `clear_lines` and `rewind` are its reset methods; `calls` records the order the
26 pool ran them, as a lesson diagnostic.
27 """
28
29 def __init__(self) -> None:
30 """Start with no lines and the cursor at the top."""
31 self.lines: List[str] = []
32 self.cursor: int = 0
33 self.calls: List[str] = []
34
35 def clear_lines(self) -> None:
36 """Drop the previous request's lines."""
37 self.lines.clear()
38 self.calls.append("clear_lines")
39
40 def rewind(self) -> None:
41 """Move the cursor back to the top."""
42 self.cursor = 0
43 self.calls.append("rewind")
44
45
46class TemplateCache:
47 """A pooled cache whose reset refuses once the cache is corrupted.
48
49 `close` is its disposal method; the pool runs it when it destroys the object.
50 """
51
52 def __init__(self) -> None:
53 """Start valid and open."""
54 self.corrupted: bool = False
55 self.closed: bool = False
56
57 def reset(self) -> None:
58 """Empty the cache for the next holder; a corrupted cache cannot be reset."""
59 if self.corrupted:
60 raise ValueError("template cache is corrupted and cannot be reset")
61
62 def close(self) -> None:
63 """Dispose the cache; called when the pool destroys this object."""
64 self.closed = True
65
66
67def pool_counts(cls: type) -> Tuple[int, int, int]:
68 """Return (alive, idle, out) for the one pool of a managed class."""
69 (snapshot,) = md.ReleaseManagedSpell().describe(cls)
70 return snapshot["count"], snapshot["idle_count"], snapshot["out_count"]
71
72
73def main() -> None:
74 """Reset a pooled object on release, then watch a failing reset destroy one.
75
76 Contract:
77 Checks the reset order, the reused object's clean state, and that a raising
78 reset disposes the object, frees its place and propagates its error.
79 Returns:
80 None.
81 """
82 book = md.Spellbook()
83 try:
84 book.bind(
85 spell=ReportBuilder,
86 existence=md.Existence.managed_spell_quantity,
87 managed_spell_reset_methods=["clear_lines", "rewind"],
88 )
89 book.bind(
90 spell=TemplateCache,
91 existence=md.Existence.managed_spell_quantity,
92 managed_spell_reset_methods=["reset"],
93 disposal_method_names=["close"],
94 )
95 conduit = book.conjure()
96 release = md.ReleaseManagedSpell().release
97 try:
98 builder: ReportBuilder = conduit.meld("ReportBuilder")
99 builder.lines.extend(["header", "totals"])
100 builder.cursor = 2
101 release(builder)
102 # The resets ran in the order named at bind, on the release itself.
103 assert builder.calls == ["clear_lines", "rewind"]
104 assert builder.lines == [] and builder.cursor == 0
105 print("release ran the resets in order:", builder.calls)
106
107 nxt: ReportBuilder = conduit.meld("ReportBuilder")
108 assert nxt is builder and nxt.lines == []
109 print("the next request got the same builder, clean:", nxt is builder)
110 release(nxt)
111
112 cache: TemplateCache = conduit.meld("TemplateCache")
113 cache.corrupted = True
114 try:
115 release(cache)
116 raise AssertionError("a corrupted cache went back into the pool")
117 except ValueError as error:
118 # The pool destroyed the object: disposal ran and its place was freed.
119 assert cache.closed
120 assert pool_counts(TemplateCache) == (0, 0, 0)
121 print("a failing reset destroyed the cache and raised:", error)
122
123 fresh: TemplateCache = conduit.meld("TemplateCache")
124 assert fresh is not cache and not fresh.corrupted
125 assert pool_counts(TemplateCache) == (1, 0, 1)
126 print("the next meld built a fresh cache:", fresh is not cache)
127 release(fresh)
128 finally:
129 conduit.cleanup()
130 finally:
131 book.cleanup()
132
133
134if __name__ == "__main__":
135 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.