nx_resource_arbiter.nx source
↩ module page · 317 lines · 11092 B
1// nx_resource_arbiter.nx -- allocation under contention with priority + treaty.
2//
3// Per CARDINAL [[feedback-conductor-heterogeneous-compute-no-second-class-
4// resources]] + [[feedback-parallel-companion-multimodal-dnd-real-time]]:
5// when multiple cells want the same scarce resource (VRAM bytes,
6// CPU microseconds, NVMe iops), the arbiter decides who gets it based
7// on priority + active treaty + current allocation share. REFUSES the
8// "first-come-first-served leads to starvation of low-rate streams" and
9// "highest-priority-always-wins leads to background-cell death" failure
10// modes.
11//
12// V1 ships sealed resource enum + request enum + grant verdict +
13// per-cell allocation tracking + max-share fairness floor (no cell gets
14// >X% in a single epoch) + per-resource budget pool.
15
16import "nx_syscalls.nx"
17import "nx_tier.nx"
18
19// ===== Sealed enum: NxResourceKind ================================
20
21const NX_RA_KIND_VRAM_BYTES: nx_int = 0
22const NX_RA_KIND_RAM_BYTES: nx_int = 1
23const NX_RA_KIND_DISK_BYTES: nx_int = 2
24const NX_RA_KIND_CPU_MICROSECONDS: nx_int = 3
25const NX_RA_KIND_GPU_MICROSECONDS: nx_int = 4
26const NX_RA_KIND_NETWORK_BYTES: nx_int = 5
27const NX_RA_KIND_FILE_HANDLES: nx_int = 6
28const NX_RA_KIND_N: nx_int = 7
29
30// ===== Sealed enum: NxGrantVerdict ================================
31
32const NX_RA_V_GRANTED_FULL: nx_int = 0
33const NX_RA_V_GRANTED_PARTIAL: nx_int = 1 // less than requested
34const NX_RA_V_DENIED_NO_BUDGET: nx_int = 2
35const NX_RA_V_DENIED_MAX_SHARE: nx_int = 3 // would exceed fairness cap
36const NX_RA_V_DENIED_PREEMPTED: nx_int = 4 // higher-pri cell took it back
37const NX_RA_V_INVALID: nx_int = 5
38const NX_RA_V_NULL: nx_int = 6
39const NX_RA_V_N: nx_int = 7
40
41// ===== Struct: NxAllocation =======================================
42
43struct NxAllocation {
44 cell_id: nx_int,
45 resource_kind: nx_int,
46 units_held: nx_size,
47 priority: nx_int,
48 granted_at_us: nx_size,
49 last_renewed_us: nx_size,
50 treaty_id: nx_int,
51}
52
53const NX_RA_A_BYTES: nx_int = 56
54
55struct NxResourceArbiter {
56 allocations: *u8,
57 n_allocations: nx_int,
58 capacity: nx_int,
59 // Per-resource budget (i64[NX_RA_KIND_N])
60 budgets: *u8,
61 // Per-resource max-share-q10 (e.g. 717 = 70%)
62 max_share_q10: *u8,
63 grants_full: nx_int,
64 grants_partial: nx_int,
65 denials: nx_int,
66 preemptions: nx_int,
67}
68
69const NX_RA_BYTES: nx_int = 56
70
71// ===== Validators =================================================
72
73func nx_ra_kind_is_valid(k: nx_int) -> nx_int {
74 if k < 0 { return 0 }
75 if k >= NX_RA_KIND_N { return 0 }
76 return 1
77}
78
79func nx_ra_v_is_valid(v: nx_int) -> nx_int {
80 if v < 0 { return 0 }
81 if v >= NX_RA_V_N { return 0 }
82 return 1
83}
84
85// ===== Constructor ================================================
86
87func nx_ra_new(capacity: nx_int) -> *NxResourceArbiter {
88 if capacity <= 0 { return 0 as *NxResourceArbiter }
89 let raw: *u8 = sys_mmap(NX_RA_BYTES)
90 let a: *NxResourceArbiter = raw as *NxResourceArbiter
91 a.allocations = sys_mmap(capacity * NX_RA_A_BYTES)
92 a.n_allocations = 0
93 a.capacity = capacity
94 a.budgets = sys_mmap(NX_RA_KIND_N * 8)
95 a.max_share_q10 = sys_mmap(NX_RA_KIND_N * 8)
96 let bud: *i64 = a.budgets as *i64
97 let shr: *i64 = a.max_share_q10 as *i64
98 var i: nx_int = 0
99 while i < NX_RA_KIND_N {
100 bud[i] = 0
101 shr[i] = 717 // 70% default fairness ceiling
102 i = i + 1
103 }
104 a.grants_full = 0
105 a.grants_partial = 0
106 a.denials = 0
107 a.preemptions = 0
108 return a
109}
110
111func _ra_alloc_at(a: *NxResourceArbiter, idx: nx_int) -> *NxAllocation {
112 if idx < 0 { return 0 as *NxAllocation }
113 if idx >= a.n_allocations { return 0 as *NxAllocation }
114 let off: nx_int = idx * NX_RA_A_BYTES
115 return (a.allocations + off) as *NxAllocation
116}
117
118// ===== Budget config ==============================================
119
120func nx_ra_set_budget(a: *NxResourceArbiter,
121 resource_kind: nx_int,
122 budget: nx_size) -> nx_int {
123 if (a as i64) == 0 { return NX_RA_V_NULL }
124 if nx_ra_kind_is_valid(resource_kind) == 0 { return NX_RA_V_INVALID }
125 let bud: *i64 = a.budgets as *i64
126 bud[resource_kind] = budget as i64
127 return NX_RA_V_GRANTED_FULL
128}
129
130func nx_ra_set_max_share(a: *NxResourceArbiter,
131 resource_kind: nx_int,
132 max_share_q10: nx_int) -> nx_int {
133 if (a as i64) == 0 { return NX_RA_V_NULL }
134 if nx_ra_kind_is_valid(resource_kind) == 0 { return NX_RA_V_INVALID }
135 if max_share_q10 <= 0 { return NX_RA_V_INVALID }
136 if max_share_q10 > 1024 { return NX_RA_V_INVALID }
137 let shr: *i64 = a.max_share_q10 as *i64
138 shr[resource_kind] = max_share_q10 as i64
139 return NX_RA_V_GRANTED_FULL
140}
141
142func nx_ra_get_budget(a: *NxResourceArbiter, resource_kind: nx_int) -> nx_size {
143 if (a as i64) == 0 { return 0 }
144 if nx_ra_kind_is_valid(resource_kind) == 0 { return 0 }
145 let bud: *i64 = a.budgets as *i64
146 return bud[resource_kind] as nx_size
147}
148
149// ===== Aggregations ===============================================
150
151func nx_ra_total_held(a: *NxResourceArbiter, resource_kind: nx_int) -> nx_size {
152 if (a as i64) == 0 { return 0 }
153 var sum: nx_size = 0
154 var i: nx_int = 0
155 while i < a.n_allocations {
156 let al: *NxAllocation = _ra_alloc_at(a, i)
157 if al.resource_kind == resource_kind { sum = sum + al.units_held }
158 i = i + 1
159 }
160 return sum
161}
162
163func nx_ra_held_by_cell(a: *NxResourceArbiter,
164 cell_id: nx_int,
165 resource_kind: nx_int) -> nx_size {
166 if (a as i64) == 0 { return 0 }
167 var sum: nx_size = 0
168 var i: nx_int = 0
169 while i < a.n_allocations {
170 let al: *NxAllocation = _ra_alloc_at(a, i)
171 if al.cell_id == cell_id {
172 if al.resource_kind == resource_kind { sum = sum + al.units_held }
173 }
174 i = i + 1
175 }
176 return sum
177}
178
179// ===== Request resource ===========================================
180//
181// Returns verdict + writes actual units_granted via *out_granted (0 on
182// deny / null). Granted_partial when budget allows < requested.
183
184func nx_ra_request(a: *NxResourceArbiter,
185 cell_id: nx_int,
186 resource_kind: nx_int,
187 units: nx_size,
188 priority: nx_int,
189 treaty_id: nx_int,
190 out_granted: *i64,
191 now_us: nx_size) -> nx_int {
192 if (a as i64) == 0 { return NX_RA_V_NULL }
193 if (out_granted as i64) == 0 { return NX_RA_V_NULL }
194 if nx_ra_kind_is_valid(resource_kind) == 0 { return NX_RA_V_INVALID }
195 if cell_id == 0 { return NX_RA_V_INVALID }
196 if units == 0 { return NX_RA_V_INVALID }
197 out_granted[0] = 0
198 let bud: *i64 = a.budgets as *i64
199 let shr: *i64 = a.max_share_q10 as *i64
200 let budget: nx_size = bud[resource_kind] as nx_size
201 if budget == 0 {
202 a.denials = a.denials + 1
203 return NX_RA_V_DENIED_NO_BUDGET
204 }
205 let used: nx_size = nx_ra_total_held(a, resource_kind)
206 let free_pool: nx_size = budget - used
207 if used >= budget {
208 a.denials = a.denials + 1
209 return NX_RA_V_DENIED_NO_BUDGET
210 }
211 // Max-share check: cell already holds X, request would bring to Y;
212 // Y / budget must be <= max_share_q10 / 1024.
213 let cell_held: nx_size = nx_ra_held_by_cell(a, cell_id, resource_kind)
214 let new_total_for_cell: nx_size = cell_held + units
215 let cell_q10: nx_int = (((new_total_for_cell as nx_int) * 1024) / (budget as nx_int))
216 let max_share: nx_int = shr[resource_kind] as nx_int
217 if cell_q10 > max_share {
218 a.denials = a.denials + 1
219 return NX_RA_V_DENIED_MAX_SHARE
220 }
221 // Grant what we can (full if free_pool >= units, else partial)
222 var to_grant: nx_size = units
223 if free_pool < units { to_grant = free_pool }
224 if a.n_allocations >= a.capacity {
225 a.denials = a.denials + 1
226 return NX_RA_V_DENIED_NO_BUDGET
227 }
228 let off: nx_int = a.n_allocations * NX_RA_A_BYTES
229 let al: *NxAllocation = (a.allocations + off) as *NxAllocation
230 al.cell_id = cell_id
231 al.resource_kind = resource_kind
232 al.units_held = to_grant
233 al.priority = priority
234 al.granted_at_us = now_us
235 al.last_renewed_us = now_us
236 al.treaty_id = treaty_id
237 a.n_allocations = a.n_allocations + 1
238 out_granted[0] = to_grant as i64
239 if to_grant == units {
240 a.grants_full = a.grants_full + 1
241 return NX_RA_V_GRANTED_FULL
242 }
243 a.grants_partial = a.grants_partial + 1
244 return NX_RA_V_GRANTED_PARTIAL
245}
246
247// ===== Release resource ===========================================
248
249func nx_ra_release(a: *NxResourceArbiter,
250 cell_id: nx_int,
251 resource_kind: nx_int) -> nx_size {
252 if (a as i64) == 0 { return 0 }
253 var released: nx_size = 0
254 var i: nx_int = 0
255 while i < a.n_allocations {
256 let al: *NxAllocation = _ra_alloc_at(a, i)
257 if al.cell_id == cell_id {
258 if al.resource_kind == resource_kind {
259 released = released + al.units_held
260 // Mark zero -- compaction deferred
261 al.units_held = 0
262 }
263 }
264 i = i + 1
265 }
266 return released
267}
268
269// ===== Preempt: take from lower-priority cell =====================
270
271func nx_ra_preempt(a: *NxResourceArbiter,
272 resource_kind: nx_int,
273 requesting_priority: nx_int,
274 units_needed: nx_size) -> nx_size {
275 if (a as i64) == 0 { return 0 }
276 if nx_ra_kind_is_valid(resource_kind) == 0 { return 0 }
277 var reclaimed: nx_size = 0
278 var i: nx_int = 0
279 while i < a.n_allocations {
280 if reclaimed < units_needed {
281 let al: *NxAllocation = _ra_alloc_at(a, i)
282 if al.resource_kind == resource_kind {
283 if al.priority < requesting_priority {
284 if al.units_held > 0 {
285 reclaimed = reclaimed + al.units_held
286 al.units_held = 0
287 a.preemptions = a.preemptions + 1
288 }
289 }
290 }
291 }
292 i = i + 1
293 }
294 return reclaimed
295}
296
297// ===== Stats accessors ============================================
298
299func nx_ra_grants_full(a: *NxResourceArbiter) -> nx_int {
300 if (a as i64) == 0 { return 0 }
301 return a.grants_full
302}
303
304func nx_ra_grants_partial(a: *NxResourceArbiter) -> nx_int {
305 if (a as i64) == 0 { return 0 }
306 return a.grants_partial
307}
308
309func nx_ra_denials(a: *NxResourceArbiter) -> nx_int {
310 if (a as i64) == 0 { return 0 }
311 return a.denials
312}
313
314func nx_ra_preemptions(a: *NxResourceArbiter) -> nx_int {
315 if (a as i64) == 0 { return 0 }
316 return a.preemptions
317}