Landin library reference source

core/failing/failing.ldn

1--  Generic deterministic failure injection.  The wrapper retains a mutable
2--  pointer to its supplied provider and delegates every successful free.
3
4import core/mem
5
6--- Allocator wrapper with deterministic failure injection and cumulative
7--- counters. Borrows its inner provider, delegates permitted allocations and
8--- frees, and refuses in-place growth.
9public counted: type (inner_provider: type) = struct
10    inner: ptr mut inner_provider
11    remaining: usize
12    attempts: usize
13    delegated: usize
14    successes: usize
15    injected_failures: usize
16    inner_failures: usize
17    frees: usize
18    live: usize
19end counted
20
21--- Wrap an allocator with an allocation-attempt budget. Inner failures
22--- consume the budget too; injected failures never call the inner provider.
23---
24--- allowed_attempts is the number of future allocation calls allowed to
25--- reach inner.alloc. An inner failure consumes one such attempt. Injected
26--- failures increment attempts/injected_failures only and never call inner.
27public new: (inner_provider: type is mem.allocator,
28             inner: ptr mut inner_provider,
29             allowed_attempts: usize)
30            -> (state: counted(inner_provider) from inner) =
31    state = (inner: inner, remaining: allowed_attempts,
32             attempts: 0, delegated: 0, successes: 0,
33             injected_failures: 0, inner_failures: 0,
34             frees: 0, live: 0)
35end new
36
37--- Replace the future delegation budget without resetting accumulated
38--- counters.
39---
40--- Replace only the future delegation budget. Evidence counters accumulate
41--- across retries so the failed and successful calls remain distinguishable.
42public permit: (inner_provider: type,
43                inout state: counted(inner_provider),
44                allowed_attempts: usize) -> none =
45    state.remaining = allowed_attempts
46end permit
47
48counted_alloc: (inner_provider: type,
49                inout state: counted(inner_provider),
50                size: usize, alignment: usize)
51               -> (block: ptr mut u8) ! mem.out_of_memory =
52    inc state.attempts
53    if state.remaining == 0 then
54        inc state.injected_failures
55        fail mem.out_of_memory
56    end if
57
58    dec state.remaining
59    inc state.delegated
60    block = mem.allocate(state.inner.val, size, alignment)
61        else (problem)
62        _ = problem
63        inc state.inner_failures
64        fail mem.out_of_memory
65    end
66    inc state.successes
67    inc state.live
68end counted_alloc
69
70counted_free: (inner_provider: type,
71               inout state: counted(inner_provider),
72               block: ptr mut u8, size: usize) -> none =
73    mem.free(state.inner.val, block, size)
74    --  Allocator free has no result. These two counters are therefore exact
75    --  under the allocator contract: the caller frees each successful block
76    --  once with its original extent. A malformed or duplicate free is still
77    --  delegated, but an inner rejection cannot be reflected in live.
78    inc state.frees
79    if state.live > 0 then
80        dec state.live
81    end if
82end counted_free
83
84counted_grow: (inner_provider: type,
85               inout state: counted(inner_provider), block: ptr mut u8,
86               old_size: usize, new_size: usize, alignment: usize)
87              -> (grown: bool) =
88    _ = state.live
89    _ = block
90    _ = old_size
91    _ = new_size
92    _ = alignment
93    grown = false
94end counted_grow
95
96(inner_provider: type is mem.allocator) counted(inner_provider)
97    is mem.allocator (alloc: counted_alloc, grow: counted_grow,
98                      free: counted_free)
99
100--- Return how many allocation attempts may still reach the inner provider.
101public remaining: (inner_provider: type,
102                   state: counted(inner_provider)) -> (count: usize) =
103    count = state.remaining
104end remaining
105
106--- Return all allocation attempts, including injected and inner failures.
107public attempts: (inner_provider: type,
108                  state: counted(inner_provider)) -> (count: usize) =
109    count = state.attempts
110end attempts
111
112--- Return allocation attempts passed through to the inner provider.
113public delegated: (inner_provider: type,
114                   state: counted(inner_provider)) -> (count: usize) =
115    count = state.delegated
116end delegated
117
118--- Return the number of successful delegated allocations.
119public successes: (inner_provider: type,
120                   state: counted(inner_provider)) -> (count: usize) =
121    count = state.successes
122end successes
123
124--- Return attempts rejected by the wrapper before calling the inner provider.
125public injected_failures: (inner_provider: type,
126                           state: counted(inner_provider))
127                          -> (count: usize) =
128    count = state.injected_failures
129end injected_failures
130
131--- Return allocation failures reported by the inner provider.
132public inner_failures: (inner_provider: type,
133                        state: counted(inner_provider)) -> (count: usize) =
134    count = state.inner_failures
135end inner_failures
136
137--- Return the number of delegated free calls.
138public frees: (inner_provider: type,
139               state: counted(inner_provider)) -> (count: usize) =
140    count = state.frees
141end frees
142
143--- Return successful allocations minus free calls, bounded below by zero.
144--- Exact under the allocator contract; malformed or duplicate frees are not
145--- validated here.
146public live: (inner_provider: type,
147              state: counted(inner_provider)) -> (count: usize) =
148    --  Successful allocations less delegated frees under valid-free use.
149    count = state.live
150end live