1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! The live-byte bound on one evaluation: `max_alloc_bytes` (`docs/SAFETY.md`
//! row 12).
//!
//! An evaluation is charged the bytes its thread allocates and has not freed
//! ([`crate::alloc::Account`]), and past its bound it is refused with a typed
//! `memory-exhausted` refusal, never an abort. Each door says how much it
//! holds an evaluation to ([`crate::ExecutionBounds`]):
//!
//! | door | bound |
//! |---|---|
//! | `blue run`, an appliance, `profile`, `trace`, `blue test` (each test) | `max_alloc_bytes`, unbounded by default |
//! | sessions (`eval`, `repl`, `serve`), live evaluation and the editor's test witness, project rules, a Bluefile | `guest_max_alloc_bytes`, [`GUEST_MAX_ALLOC_BYTES`] by default |
//!
//! The defaults differ for the reason the step bound's do: a run of code its
//! author wrote that holds a lot of memory worked before this bound existed,
//! so bounding it by default would remove working behaviour; a door that runs
//! code it did not write is where a runaway must not take the machine down.
//!
//! **How it refuses.** The root of an evaluation runs on the tree-walker,
//! which checks its interrupt flag at every step. The account is armed with
//! that flag ([`held`]): the allocation that passes the bound sets it, the
//! next step halts, and the door reads the halt as the refusal ([`named`]),
//! fatal so no `rescue` sees it. Each blue process has its own account,
//! inherited bound and all ([`METER`], `blue_lang_proc::Meter`); one that
//! passes it exits a runaway (`ExitReason::memory_exhausted`) at the end of
//! its slice, which its links, monitors and supervisor see.
//!
//! **What it does not bound.** A single native call that asks for more than
//! the machine has still aborts inside the allocator: the bound is checked
//! between steps and between slices, and a refused allocation is an abort
//! (`std::alloc::handle_alloc_error`). The tree-walker's own stack grows by
//! `mmap`, outside the allocator; `max_call_depth` bounds it. A binary that
//! does not install `alloc::Counting` counts nothing, so no bound applies.
use ;
use Arc;
use ;
pub use MEMORY_EXHAUSTED;
/// The live bytes a door that runs code it did not write holds one evaluation
/// to by default: 1 GiB, a runaway guard rather than a quota.
pub const GUEST_MAX_ALLOC_BYTES: u64 = 1 << 30;
/// The allocator meter every node holds its processes to their bounds
/// through.
pub const METER: Meter = Meter ;
/// `f`, held to `bound` live bytes through `halt`, the interrupt flag of the
/// interpreter it runs on; beside its answer, the bound when it passed it.
///
/// Past the bound the flag is set, so the tree-walker halts at its next step,
/// and cleared again once `f` returns, so the interpreter can run another
/// evaluation.
/// [`held`] on `interp`, under an interrupt flag of its own: a fork shares
/// its base's flag, and a refusal must halt this program and no other.
/// The refusal an evaluation past `bound` ends on. Fatal: no `rescue`
/// catches it, as no `rescue` can spend past exhausted fuel.
/// `e`, read as the memory refusal when the evaluation running now has
/// passed its bound: whatever stopped it after that (the halt the bound set,
/// usually) is the refusal's doing.
/// Is `e` the memory refusal?