Coverage for wasmtime/_slab.py: 100%
15 statements
« prev ^ index » next coverage.py v7.11.3, created at 2026-09-24 20:48 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2026-09-24 20:48 +0000
1import itertools
2from typing import Dict, Generic, Iterator, TypeVar
5T = TypeVar('T')
8class Slab(Generic[T]):
9 """Maps integer handles to Python objects, safe to use from many threads.
11 Wasmtime objects release their handles from finalizers, and the garbage
12 collector runs finalizers on whichever thread happens to trigger a
13 collection. An application that uses wasmtime from one thread can
14 therefore see `deallocate` run on another thread, concurrently with
15 `allocate`.
17 Handles come from a monotonically increasing counter and are never reused,
18 and the values live in a dict, so there is no free list whose invariant
19 spans several statements for a concurrent call to break. Every step is a
20 single `next()` or dict operation, which the GIL makes atomic, so no lock
21 is needed, and a finalizer that frees another handle mid-call cannot
22 deadlock or see a half-updated slab.
23 """
25 handles: Dict[int, T]
26 counter: Iterator[int]
28 def __init__(self) -> None:
29 # Counting from 1, never 0: callers round-trip a handle through a C
30 # `void *` and read a null one back as `idx or 0`, so a handle of 0 is
31 # indistinguishable from an absent one.
32 self.handles = {}
33 self.counter = itertools.count(1)
35 def allocate(self, val: T) -> int:
36 idx = next(self.counter)
37 self.handles[idx] = val
38 return idx
40 def get(self, idx: int) -> T:
41 return self.handles[idx]
43 def deallocate(self, idx: int) -> None:
44 # Idempotent: freeing a handle twice, or freeing one that was never
45 # handed out, leaves the live handles alone.
46 self.handles.pop(idx, None)