Landin library reference source

core/failing

Shared: available on every enabled target.

Deterministic allocation-failure injection over any allocator, with cumulative counters.

The budget counts delegated attempts, including attempts the inner provider refuses. permit replaces that budget without resetting evidence. Free calls delegate to the parent; in-place growth is refused so allocation-failure tests remain predictable.

Items

Executable example

This complete program is maintained in the repository runtime tests. View source.

import core/mem
import core/failing

exercise: () -> (ok: bool) ! mem.out_of_memory =
    ok = false
    mut backing: [32]u8 = zeroed
    mut inner := mem.arena_over_unchecked(addr backing[0], 32)
    mut state := failing.new(addr inner, usize(1))
    address := try mem.allocate(state, usize(4), usize(1))
    address.val = 42
    return when address.val <> 42
    _ = mem.allocate(state, usize(4), usize(1)) else (problem)
        _ = problem
        ok = failing.successes(state) == 1 and failing.injected_failures(state) == 1
        mem.free(state, address, usize(4))
        return
    end
end exercise

public main: () -> (code: i32) =
    code = 1
    ok := exercise() else false
    if ok then
        code = 42
    end if
end main

counted type

public counted: type (inner_provider: type) = struct
    inner: ptr mut inner_provider
    remaining: usize
    attempts: usize
    delegated: usize
    successes: usize
    injected_failures: usize
    inner_failures: usize
    frees: usize
    live: usize
end counted

core/failing/failing.ldn:9

Allocator wrapper with deterministic failure injection and cumulative counters. Borrows its inner provider, delegates permitted allocations and frees, and refuses in-place growth.

attempts function

public attempts: (inner_provider: type,
                  state: counted(inner_provider)) -> (count: usize)

core/failing/failing.ldn:107

Return all allocation attempts, including injected and inner failures.

delegated function

public delegated: (inner_provider: type,
                   state: counted(inner_provider)) -> (count: usize)

core/failing/failing.ldn:113

Return allocation attempts passed through to the inner provider.

frees function

public frees: (inner_provider: type,
               state: counted(inner_provider)) -> (count: usize)

core/failing/failing.ldn:138

Return the number of delegated free calls.

injected_failures function

public injected_failures: (inner_provider: type,
                           state: counted(inner_provider))
                          -> (count: usize)

core/failing/failing.ldn:125

Return attempts rejected by the wrapper before calling the inner provider.

inner_failures function

public inner_failures: (inner_provider: type,
                        state: counted(inner_provider)) -> (count: usize)

core/failing/failing.ldn:132

Return allocation failures reported by the inner provider.

live function

public live: (inner_provider: type,
              state: counted(inner_provider)) -> (count: usize)

core/failing/failing.ldn:146

Return successful allocations minus free calls, bounded below by zero. Exact under the allocator contract; malformed or duplicate frees are not validated here.

new function

public new: (inner_provider: type is mem.allocator,
             inner: ptr mut inner_provider,
             allowed_attempts: usize)
            -> (state: counted(inner_provider) from inner)

core/failing/failing.ldn:27

Wrap an allocator with an allocation-attempt budget. Inner failures consume the budget too; injected failures never call the inner provider.

allowed_attempts is the number of future allocation calls allowed to reach inner.alloc. An inner failure consumes one such attempt. Injected failures increment attempts/injected_failures only and never call inner.

permit function

public permit: (inner_provider: type,
                inout state: counted(inner_provider),
                allowed_attempts: usize) -> none

core/failing/failing.ldn:42

Replace the future delegation budget without resetting accumulated counters.

Replace only the future delegation budget. Evidence counters accumulate across retries so the failed and successful calls remain distinguishable.

remaining function

public remaining: (inner_provider: type,
                   state: counted(inner_provider)) -> (count: usize)

core/failing/failing.ldn:101

Return how many allocation attempts may still reach the inner provider.

successes function

public successes: (inner_provider: type,
                   state: counted(inner_provider)) -> (count: usize)

core/failing/failing.ldn:119

Return the number of successful delegated allocations.