Coverage for wasmtime/_slab.py: 100%

15 statements  

« 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 

3 

4 

5T = TypeVar('T') 

6 

7 

8class Slab(Generic[T]): 

9 """Maps integer handles to Python objects, safe to use from many threads. 

10 

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`. 

16 

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 """ 

24 

25 handles: Dict[int, T] 

26 counter: Iterator[int] 

27 

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) 

34 

35 def allocate(self, val: T) -> int: 

36 idx = next(self.counter) 

37 self.handles[idx] = val 

38 return idx 

39 

40 def get(self, idx: int) -> T: 

41 return self.handles[idx] 

42 

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)