kernel: Implement time-window budgets
5c2b051de63b63592331d579f3369daac64fa3cf58d24c4db5e3e2be368e3004
Issue one initial interval per hart, split and merge unconsumed CPU authority, transfer its unique capability, bind live contexts, and retire expired intervals. Reject overlapping context authority across harts and preserve resources on failed admission. Validate conservation, adjacency, ownership, expiration, capacity rollback, context generations, and exact half-open boundaries. Assisted-by: Codex:gpt-6
1 parent
5188225d
kernel/kernel.rad
+1 -0
| 18 | 18 | export mod backing; |
|
| 19 | 19 | export mod pages; |
|
| 20 | 20 | export mod loader; |
|
| 21 | 21 | export mod instances; |
|
| 22 | 22 | export mod domains; |
|
| 23 | + | export mod budgets; |
|
| 23 | 24 | export mod boot; |
|
| 24 | 25 | @test export mod tests; |
kernel/kernel/boot.rad
+2 -0
| 7 | 7 | use super::sync; |
|
| 8 | 8 | use super::trap; |
|
| 9 | 9 | use super::pages; |
|
| 10 | 10 | use super::registry; |
|
| 11 | 11 | use super::domains; |
|
| 12 | + | use super::budgets; |
|
| 12 | 13 | use std::arch::rv64::shared::catalog; |
|
| 13 | 14 | ||
| 14 | 15 | /// Platform data published by hart zero before secondary initialization. |
|
| 15 | 16 | export unsafe static PLATFORM: platform::Platform = undefined; |
|
| 16 | 17 | /// Release/acquire publication flag for PLATFORM. |
| 70 | 71 | let treeRange = range::new(treeAddress, size as u64) else panic "FDT range"; |
|
| 71 | 72 | assert platform::inRam(&PLATFORM, treeRange); |
|
| 72 | 73 | try! platform::protect(&mut PLATFORM, treeRange); |
|
| 73 | 74 | try! pages::initialize(&mut pages::STORE, &PLATFORM); |
|
| 74 | 75 | domains::initialize(&mut domains::STORE); |
|
| 76 | + | budgets::initialize(&mut budgets::STORE); |
|
| 75 | 77 | assert entryCount == 2; |
|
| 76 | 78 | registry::initialize(&mut registry::STORE); |
|
| 77 | 79 | let bootCatalog = @sliceOf(entries, entryCount); |
|
| 78 | 80 | try! registry::boot(&mut registry::STORE, &bootCatalog[..]); |
|
| 79 | 81 | let library = registry::find(®istry::STORE, &"std"[..]) else panic "boot std"; |
kernel/kernel/budgets.rad
added
+256 -0
| 1 | + | //! Exclusive half-open CPU intervals and their capability ownership. |
|
| 2 | + | ||
| 3 | + | use super::abi; |
|
| 4 | + | use super::limits; |
|
| 5 | + | use super::slots; |
|
| 6 | + | use super::capability; |
|
| 7 | + | use super::transactions; |
|
| 8 | + | use super::domains; |
|
| 9 | + | ||
| 10 | + | /// Rights on an initial CPU budget. |
|
| 11 | + | export constant DEFAULT_RIGHTS: u16 = abi::READ | abi::WRITE | abi::TRANSFER; |
|
| 12 | + | ||
| 13 | + | /// One retained interval on a physical hart. |
|
| 14 | + | export record Window: Copy { |
|
| 15 | + | /// Physical hart on which this interval grants CPU authority. |
|
| 16 | + | hart: u32, |
|
| 17 | + | /// Inclusive first authorized tick. |
|
| 18 | + | start: u64, |
|
| 19 | + | /// Exclusive end of CPU authority. |
|
| 20 | + | end: u64, |
|
| 21 | + | /// Domain holding the sole capability for this interval. |
|
| 22 | + | owner: abi::Ref, |
|
| 23 | + | /// Sole live handle in the owner's table. |
|
| 24 | + | handle: abi::Handle, |
|
| 25 | + | /// Selected execution context, if the interval is bound. |
|
| 26 | + | context: ?abi::Ref, |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// Bounded CPU authority derived from one initial interval per hart. |
|
| 30 | + | export record Store: Copy { |
|
| 31 | + | /// Budget object generations. |
|
| 32 | + | slots: [slots::Slot; limits::BUDGETS], |
|
| 33 | + | /// Payload for each published interval. |
|
| 34 | + | windows: [Window; limits::BUDGETS], |
|
| 35 | + | /// Harts whose initial interval has already been issued. |
|
| 36 | + | seeded: u64, |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Global exclusive CPU intervals. |
|
| 40 | + | export unsafe static STORE: Store = undefined; |
|
| 41 | + | ||
| 42 | + | /// Initialize fresh budget metadata before boot issues CPU authority. |
|
| 43 | + | export fn initialize(store: &mut Store) { |
|
| 44 | + | slots::initialize(&mut store.slots[..]); |
|
| 45 | + | set store.seeded = 0; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Read a retained budget object after checking its generation. |
|
| 49 | + | export fn get(store: &Store, object: abi::Ref) -> Window throws (abi::Error) { |
|
| 50 | + | if not slots::matches(&store.slots[..], object, slots::State::Live) { |
|
| 51 | + | throw abi::Error::BadHandle; |
|
| 52 | + | } |
|
| 53 | + | return store.windows[object.index]; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Validate the unique capability owner and return the budget object reference. |
|
| 57 | + | fn owned(store: &Store, table: &capability::Table, handle: abi::Handle, rights: u16) -> abi::Ref throws (abi::Error) { |
|
| 58 | + | let entry = try capability::lookup(table, handle, abi::Kind::Budget, abi::Rights(rights)); |
|
| 59 | + | let window = try get(store, entry.object); |
|
| 60 | + | if window.owner <> table.owner or window.handle <> handle { |
|
| 61 | + | throw abi::Error::BadHandle; |
|
| 62 | + | } |
|
| 63 | + | return entry.object; |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Publish a new interval after reserving both object and capability capacity. |
|
| 67 | + | fn install(store: &mut Store, table: &mut capability::Table, window: Window, rights: abi::Rights) -> abi::Handle throws (abi::Error) { |
|
| 68 | + | let handleSlot = try slots::reserve(&mut table.slots[..]); |
|
| 69 | + | let budgetSlot = try slots::reserve(&mut store.slots[..]) catch err { |
|
| 70 | + | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 71 | + | }; |
|
| 72 | + | let object = slots::reference(&budgetSlot); |
|
| 73 | + | let reference = slots::reference(&handleSlot); |
|
| 74 | + | let handle = try! abi::handle(abi::Kind::Budget, reference); |
|
| 75 | + | set store.windows[object.index] = window; |
|
| 76 | + | set store.windows[object.index].owner = table.owner; |
|
| 77 | + | set store.windows[object.index].handle = handle; |
|
| 78 | + | let live = try! slots::commit(&mut store.slots[..], budgetSlot); |
|
| 79 | + | return capability::publish(table, handleSlot, capability::Entry { kind: abi::Kind::Budget, object: live, rights }); |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | /// Issue one initial interval per hart during trusted boot initialization. |
|
| 83 | + | /// Only partition, merge, transfer, and consumption can change authority after this call. |
|
| 84 | + | export fn seed(store: &mut Store, table: &mut capability::Table, hart: u32, start: u64, end: u64) |
|
| 85 | + | -> abi::Handle throws (abi::Error) |
|
| 86 | + | { |
|
| 87 | + | if hart >= limits::HARTS or start >= end { |
|
| 88 | + | throw abi::Error::InvalidArg; |
|
| 89 | + | } |
|
| 90 | + | if (store.seeded & (1 << hart as u64)) <> 0 { |
|
| 91 | + | throw abi::Error::Busy; |
|
| 92 | + | } |
|
| 93 | + | let handle = try install(store, table, Window { |
|
| 94 | + | hart, start, end, owner: table.owner, handle: abi::Handle(0), context: nil, |
|
| 95 | + | }, abi::Rights(DEFAULT_RIGHTS)); |
|
| 96 | + | set store.seeded |= 1 << hart as u64; |
|
| 97 | + | return handle; |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | /// Read retained interval metadata through its unique capability. |
|
| 101 | + | export fn query(store: &Store, table: &capability::Table, handle: abi::Handle) -> Window throws (abi::Error) { |
|
| 102 | + | let object = try owned(store, table, handle, abi::READ); |
|
| 103 | + | return store.windows[object.index]; |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Discard the elapsed prefix and reject fully expired authority. |
|
| 107 | + | fn remaining(window: Window, now: u64) -> Window throws (abi::Error) { |
|
| 108 | + | if now >= window.end { |
|
| 109 | + | throw abi::Error::Busy; |
|
| 110 | + | } |
|
| 111 | + | let mut result = window; |
|
| 112 | + | if now > result.start { |
|
| 113 | + | set result.start = now; |
|
| 114 | + | } |
|
| 115 | + | return result; |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | /// Split unconsumed authority, retaining the left handle and returning the right. |
|
| 119 | + | export fn split(store: &mut Store, table: &mut capability::Table, handle: abi::Handle, at: u64, now: u64) |
|
| 120 | + | -> abi::Handle throws (abi::Error) |
|
| 121 | + | { |
|
| 122 | + | let object = try owned(store, table, handle, abi::WRITE); |
|
| 123 | + | let window = try remaining(store.windows[object.index], now); |
|
| 124 | + | if at <= window.start or at >= window.end { |
|
| 125 | + | throw abi::Error::InvalidArg; |
|
| 126 | + | } |
|
| 127 | + | let entry = try! capability::get(table, handle); |
|
| 128 | + | let mut right = window; |
|
| 129 | + | set right.start = at; |
|
| 130 | + | let result = try install(store, table, right, entry.rights); |
|
| 131 | + | set store.windows[object.index].start = window.start; |
|
| 132 | + | set store.windows[object.index].end = at; |
|
| 133 | + | return result; |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | /// Combine adjacent intervals on the same hart with the same context binding. |
|
| 137 | + | /// The first handle survives with the intersection of both capability rights. |
|
| 138 | + | export fn merge(store: &mut Store, table: &mut capability::Table, first: abi::Handle, second: abi::Handle, now: u64) |
|
| 139 | + | -> abi::Handle throws (abi::Error) |
|
| 140 | + | { |
|
| 141 | + | if first == second { |
|
| 142 | + | throw abi::Error::InvalidArg; |
|
| 143 | + | } |
|
| 144 | + | let a = try owned(store, table, first, abi::WRITE); |
|
| 145 | + | let b = try owned(store, table, second, abi::WRITE); |
|
| 146 | + | let left = try remaining(store.windows[a.index], now); |
|
| 147 | + | let right = try remaining(store.windows[b.index], now); |
|
| 148 | + | if left.hart <> right.hart or left.context <> right.context { |
|
| 149 | + | throw abi::Error::InvalidArg; |
|
| 150 | + | } |
|
| 151 | + | let mut start = left.start; |
|
| 152 | + | let mut end = right.end; |
|
| 153 | + | if left.end <> right.start { |
|
| 154 | + | if right.end <> left.start { |
|
| 155 | + | throw abi::Error::InvalidArg; |
|
| 156 | + | } |
|
| 157 | + | set start = right.start; set end = left.end; |
|
| 158 | + | } |
|
| 159 | + | let firstEntry = try! capability::get(table, first); |
|
| 160 | + | let secondEntry = try! capability::get(table, second); |
|
| 161 | + | let firstSlot = try! abi::decode(first); |
|
| 162 | + | set table.entries[firstSlot.object.index].rights = abi::Rights(*firstEntry.rights & *secondEntry.rights); |
|
| 163 | + | set store.windows[a.index].start = start; |
|
| 164 | + | set store.windows[a.index].end = end; |
|
| 165 | + | let removed = try! capability::invalidate(table, second); |
|
| 166 | + | try! slots::release(&mut store.slots[..], b); |
|
| 167 | + | return first; |
|
| 168 | + | } |
|
| 169 | + | ||
| 170 | + | /// Retire an expired interval and its sole handle. |
|
| 171 | + | export fn expire(store: &mut Store, table: &mut capability::Table, handle: abi::Handle, now: u64) -> bool throws (abi::Error) { |
|
| 172 | + | let object = try owned(store, table, handle, 0); |
|
| 173 | + | if now < store.windows[object.index].end { |
|
| 174 | + | return false; |
|
| 175 | + | } |
|
| 176 | + | let removed = try! capability::invalidate(table, handle); |
|
| 177 | + | try! slots::release(&mut store.slots[..], object); |
|
| 178 | + | return true; |
|
| 179 | + | } |
|
| 180 | + | ||
| 181 | + | /// Transfer unconsumed authority, clear its context binding, and attenuate rights. |
|
| 182 | + | /// The caller keeps both domains alive and serializes both capability tables. |
|
| 183 | + | export fn transfer(store: &mut Store, source: &mut capability::Table, target: &mut capability::Table, |
|
| 184 | + | handle: abi::Handle, rights: u64, now: u64) -> abi::Handle throws (abi::Error) |
|
| 185 | + | { |
|
| 186 | + | let object = try owned(store, source, handle, abi::TRANSFER); |
|
| 187 | + | let mut window = try remaining(store.windows[object.index], now); |
|
| 188 | + | let entry = try! capability::get(source, handle); |
|
| 189 | + | let owner = source.owner; |
|
| 190 | + | let pending = try transactions::reserve(target, owner, handle, entry, rights, true); |
|
| 191 | + | let result = transactions::transfer(source, target, pending); |
|
| 192 | + | set window.owner = target.owner; |
|
| 193 | + | set window.handle = result; |
|
| 194 | + | set window.context = nil; |
|
| 195 | + | set store.windows[object.index] = window; |
|
| 196 | + | return result; |
|
| 197 | + | } |
|
| 198 | + | ||
| 199 | + | /// Capability and context selected by a budget-binding request. |
|
| 200 | + | export record Binding: Copy { |
|
| 201 | + | /// Exclusive CPU authority to bind or transfer. |
|
| 202 | + | budget: abi::Handle, |
|
| 203 | + | /// Domain capability carrying Wake authority for the receiver. |
|
| 204 | + | domain: abi::Handle, |
|
| 205 | + | /// Live execution context owned by the receiver. |
|
| 206 | + | context: abi::Ref, |
|
| 207 | + | } |
|
| 208 | + | ||
| 209 | + | /// Reject overlapping execution authority for one context across all harts. |
|
| 210 | + | fn available(store: &Store, object: abi::Ref, candidate: Window, context: abi::Ref) -> bool { |
|
| 211 | + | for i in 0..limits::BUDGETS { |
|
| 212 | + | if i == object.index or store.slots[i].state <> slots::State::Live { |
|
| 213 | + | continue; |
|
| 214 | + | } |
|
| 215 | + | let other = store.windows[i]; |
|
| 216 | + | if other.context == context and candidate.start < other.end and other.start < candidate.end { |
|
| 217 | + | return false; |
|
| 218 | + | } |
|
| 219 | + | } |
|
| 220 | + | return true; |
|
| 221 | + | } |
|
| 222 | + | ||
| 223 | + | /// Select a context and transfer the unique capability when its domain differs. |
|
| 224 | + | /// The caller serializes the budget, domain lifetimes, and both capability tables. |
|
| 225 | + | export unsafe fn bind(store: &mut Store, domainStore: &domains::Store, table: &mut capability::Table, |
|
| 226 | + | request: Binding, now: u64) -> abi::Handle throws (abi::Error) |
|
| 227 | + | { |
|
| 228 | + | let object = try owned(store, table, request.budget, abi::TRANSFER); |
|
| 229 | + | let candidate = try remaining(store.windows[object.index], now); |
|
| 230 | + | let permission = try capability::lookup(table, request.domain, abi::Kind::Domain, abi::Rights(abi::WAKE)); |
|
| 231 | + | let mut target = try domains::get(domainStore, permission.object); |
|
| 232 | + | if target.state == domains::Lifecycle::Dead { |
|
| 233 | + | throw abi::Error::BadHandle; |
|
| 234 | + | } |
|
| 235 | + | if not slots::matches(&domainStore.contextSlots[..], request.context, slots::State::Live) |
|
| 236 | + | or domainStore.contexts[request.context.index].owner <> permission.object { |
|
| 237 | + | throw abi::Error::BadHandle; |
|
| 238 | + | } |
|
| 239 | + | if not available(store, object, candidate, request.context) { |
|
| 240 | + | throw abi::Error::Busy; |
|
| 241 | + | } |
|
| 242 | + | let mut result = request.budget; |
|
| 243 | + | if permission.object <> table.owner { |
|
| 244 | + | let entry = try! capability::get(table, request.budget); |
|
| 245 | + | set result = try transfer(store, table, &mut target.memory.table, request.budget, *entry.rights as u64, now); |
|
| 246 | + | } |
|
| 247 | + | set store.windows[object.index].start = candidate.start; |
|
| 248 | + | set store.windows[object.index].context = request.context; |
|
| 249 | + | return result; |
|
| 250 | + | } |
|
| 251 | + | ||
| 252 | + | /// Test half-open execution authority on the selected hart at the current tick. |
|
| 253 | + | export fn active(store: &Store, object: abi::Ref, hart: u32, now: u64) -> bool throws (abi::Error) { |
|
| 254 | + | let window = try get(store, object); |
|
| 255 | + | return window.context <> nil and window.hart == hart and window.start <= now and now < window.end; |
|
| 256 | + | } |
kernel/kernel/tests.rad
+1 -0
| 14 | 14 | export mod events; |
|
| 15 | 15 | export mod registry; |
|
| 16 | 16 | export mod loader; |
|
| 17 | 17 | export mod instances; |
|
| 18 | 18 | export mod domains; |
|
| 19 | + | export mod budgets; |
kernel/kernel/tests/budgets.rad
added
+216 -0
| 1 | + | //! Conservation and expiration of exclusive CPU windows. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::abi; |
|
| 5 | + | use kernel::capability; |
|
| 6 | + | use kernel::budgets; |
|
| 7 | + | use kernel::slots; |
|
| 8 | + | use kernel::domains; |
|
| 9 | + | use kernel::frames; |
|
| 10 | + | use kernel::instances; |
|
| 11 | + | use kernel::trap; |
|
| 12 | + | ||
| 13 | + | /// Exclusive CPU-window metadata. |
|
| 14 | + | unsafe static STORE: budgets::Store = undefined; |
|
| 15 | + | /// Capability owner for the test windows. |
|
| 16 | + | unsafe static TABLE: capability::Table = undefined; |
|
| 17 | + | /// Receiving domain's capability table. |
|
| 18 | + | unsafe static TARGET: capability::Table = undefined; |
|
| 19 | + | /// Domain and context lifetimes used for binding admission. |
|
| 20 | + | unsafe static DOMAINS: domains::Store = undefined; |
|
| 21 | + | /// Capability storage for a pending receiver domain. |
|
| 22 | + | unsafe static MEMORY: domains::Memory = undefined; |
|
| 23 | + | ||
| 24 | + | /// Start with one live owner and no issued hart intervals. |
|
| 25 | + | unsafe fn initialize() { |
|
| 26 | + | budgets::initialize(&mut STORE); |
|
| 27 | + | capability::initialize(&mut TABLE, abi::Ref { index: 0, generation: 1 }); |
|
| 28 | + | capability::initialize(&mut TARGET, abi::Ref { index: 1, generation: 1 }); |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// Split and merge conserve the unconsumed half-open interval. |
|
| 32 | + | @test unsafe fn conservation() throws (testing::TestError) { |
|
| 33 | + | initialize(); |
|
| 34 | + | let left = try! budgets::seed(&mut STORE, &mut TABLE, 0, 10, 100); |
|
| 35 | + | let right = try! budgets::split(&mut STORE, &mut TABLE, left, 60, 20); |
|
| 36 | + | let a = try! budgets::query(&STORE, &TABLE, left); |
|
| 37 | + | let b = try! budgets::query(&STORE, &TABLE, right); |
|
| 38 | + | try testing::expect(a.start == 20 and a.end == 60 and b.start == 60 and b.end == 100); |
|
| 39 | + | let joined = try! budgets::merge(&mut STORE, &mut TABLE, right, left, 30); |
|
| 40 | + | let result = try! budgets::query(&STORE, &TABLE, joined); |
|
| 41 | + | try testing::expect(joined == right and result.start == 30 and result.end == 100); |
|
| 42 | + | let mut stale = false; |
|
| 43 | + | try budgets::query(&STORE, &TABLE, left) catch err { |
|
| 44 | + | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 45 | + | }; |
|
| 46 | + | try testing::expect(stale); |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// Expiration cannot recreate consumed time or issue a second initial interval. |
|
| 50 | + | @test unsafe fn expiration() throws (testing::TestError) { |
|
| 51 | + | initialize(); |
|
| 52 | + | let handle = try! budgets::seed(&mut STORE, &mut TABLE, 0, 10, 100); |
|
| 53 | + | let mut failures: u32 = 0; |
|
| 54 | + | try budgets::split(&mut STORE, &mut TABLE, handle, 50, 50) catch err { |
|
| 55 | + | try testing::expect(err == abi::Error::InvalidArg); set failures += 1; |
|
| 56 | + | }; |
|
| 57 | + | try budgets::split(&mut STORE, &mut TABLE, handle, 90, 100) catch err { |
|
| 58 | + | try testing::expect(err == abi::Error::Busy); set failures += 1; |
|
| 59 | + | }; |
|
| 60 | + | try testing::expect(not try! budgets::expire(&mut STORE, &mut TABLE, handle, 99)); |
|
| 61 | + | try testing::expect(try! budgets::expire(&mut STORE, &mut TABLE, handle, 100)); |
|
| 62 | + | try budgets::query(&STORE, &TABLE, handle) catch err { |
|
| 63 | + | try testing::expect(err == abi::Error::BadHandle); set failures += 1; |
|
| 64 | + | }; |
|
| 65 | + | try budgets::seed(&mut STORE, &mut TABLE, 0, 100, 200) catch err { |
|
| 66 | + | try testing::expect(err == abi::Error::Busy); set failures += 1; |
|
| 67 | + | }; |
|
| 68 | + | try testing::expect(failures == 4); |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | /// A full receiver preserves source authority; successful transfer leaves one owner. |
|
| 72 | + | @test unsafe fn transfer() throws (testing::TestError) { |
|
| 73 | + | initialize(); |
|
| 74 | + | let handle = try! budgets::seed(&mut STORE, &mut TABLE, 0, 10, 100); |
|
| 75 | + | for i in 0..TARGET.slots.len { |
|
| 76 | + | set TARGET.slots[i].state = slots::State::Retired; |
|
| 77 | + | } |
|
| 78 | + | let mut failed = false; |
|
| 79 | + | try budgets::transfer(&mut STORE, &mut TABLE, &mut TARGET, handle, abi::READ as u64, 20) catch err { |
|
| 80 | + | try testing::expect(err == abi::Error::InvalidArg); set failed = true; |
|
| 81 | + | }; |
|
| 82 | + | let unchanged = try! budgets::query(&STORE, &TABLE, handle); |
|
| 83 | + | try testing::expect(failed and unchanged.start == 10 and unchanged.end == 100); |
|
| 84 | + | set TARGET.slots[0].state = slots::State::Free; |
|
| 85 | + | let received = try! budgets::transfer(&mut STORE, &mut TABLE, &mut TARGET, handle, abi::READ as u64, 20); |
|
| 86 | + | let window = try! budgets::query(&STORE, &TARGET, received); |
|
| 87 | + | try testing::expect(window.owner == TARGET.owner and window.start == 20 and window.end == 100); |
|
| 88 | + | let entry = try! capability::get(&TARGET, received); |
|
| 89 | + | try testing::expect(*entry.rights == abi::READ and window.context == nil); |
|
| 90 | + | let mut stale = false; |
|
| 91 | + | try budgets::query(&STORE, &TABLE, handle) catch err { |
|
| 92 | + | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 93 | + | }; |
|
| 94 | + | try testing::expect(stale); |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | /// Split exhaustion leaves the original interval intact and merge requires adjacency. |
|
| 98 | + | @test unsafe fn rejection() throws (testing::TestError) { |
|
| 99 | + | initialize(); |
|
| 100 | + | let first = try! budgets::seed(&mut STORE, &mut TABLE, 0, 10, 100); |
|
| 101 | + | for i in 1..STORE.slots.len { |
|
| 102 | + | set STORE.slots[i].state = slots::State::Retired; |
|
| 103 | + | } |
|
| 104 | + | let mut failures: u32 = 0; |
|
| 105 | + | try budgets::split(&mut STORE, &mut TABLE, first, 50, 20) catch err { |
|
| 106 | + | try testing::expect(err == abi::Error::Exhausted); set failures += 1; |
|
| 107 | + | }; |
|
| 108 | + | let unchanged = try! budgets::query(&STORE, &TABLE, first); |
|
| 109 | + | try testing::expect(unchanged.start == 10 and unchanged.end == 100 and TABLE.slots[1].state == slots::State::Free); |
|
| 110 | + | for i in 1..STORE.slots.len { |
|
| 111 | + | set STORE.slots[i].state = slots::State::Free; |
|
| 112 | + | } |
|
| 113 | + | let middle = try! budgets::split(&mut STORE, &mut TABLE, first, 40, 20); |
|
| 114 | + | let last = try! budgets::split(&mut STORE, &mut TABLE, middle, 70, 20); |
|
| 115 | + | try budgets::merge(&mut STORE, &mut TABLE, first, last, 20) catch err { |
|
| 116 | + | try testing::expect(err == abi::Error::InvalidArg); set failures += 1; |
|
| 117 | + | }; |
|
| 118 | + | let otherHart = try! budgets::seed(&mut STORE, &mut TABLE, 1, 40, 70); |
|
| 119 | + | try budgets::merge(&mut STORE, &mut TABLE, first, otherHart, 20) catch err { |
|
| 120 | + | try testing::expect(err == abi::Error::InvalidArg); set failures += 1; |
|
| 121 | + | }; |
|
| 122 | + | try testing::expect(failures == 3); |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | /// Install a pending receiver and one live context for budget admission tests. |
|
| 126 | + | unsafe fn receiver() -> abi::Handle { |
|
| 127 | + | domains::initialize(&mut DOMAINS); |
|
| 128 | + | let rootSlot = try! slots::reserve(&mut DOMAINS.slots[..]); |
|
| 129 | + | let root = try! slots::commit(&mut DOMAINS.slots[..], rootSlot); |
|
| 130 | + | let childSlot = try! slots::reserve(&mut DOMAINS.slots[..]); |
|
| 131 | + | let child = slots::reference(&childSlot); |
|
| 132 | + | capability::initialize(&mut MEMORY.table, child); |
|
| 133 | + | let contextSlot = try! slots::reserve(&mut DOMAINS.contextSlots[..]); |
|
| 134 | + | let context = slots::reference(&contextSlot); |
|
| 135 | + | set DOMAINS.contexts[context.index] = domains::Context { |
|
| 136 | + | owner: child, frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 }, |
|
| 137 | + | }; |
|
| 138 | + | set DOMAINS.records[child.index] = domains::Domain { |
|
| 139 | + | state: domains::Lifecycle::Pending, creator: root, parent: root, |
|
| 140 | + | image: abi::Ref { index: 0, generation: 1 }, initial: context, |
|
| 141 | + | allocation: frames::Run { first: 0, count: 0 }, memory: &mut MEMORY, |
|
| 142 | + | graph: instances::Instance { frames: frames::Run { first: 0, count: 0 }, table: &[] }, |
|
| 143 | + | events: abi::Handle(0), |
|
| 144 | + | }; |
|
| 145 | + | let liveContext = try! slots::commit(&mut DOMAINS.contextSlots[..], contextSlot); |
|
| 146 | + | let liveDomain = try! slots::commit(&mut DOMAINS.slots[..], childSlot); |
|
| 147 | + | return try! capability::install(&mut TABLE, capability::Entry { |
|
| 148 | + | kind: abi::Kind::Domain, object: child, rights: abi::Rights(abi::WAKE), |
|
| 149 | + | }); |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | /// One context cannot hold overlapping windows on separate harts. |
|
| 153 | + | @test unsafe fn binding() throws (testing::TestError) { |
|
| 154 | + | initialize(); |
|
| 155 | + | let domain = receiver(); |
|
| 156 | + | let context = abi::Ref { index: 0, generation: 1 }; |
|
| 157 | + | let first = try! budgets::seed(&mut STORE, &mut TABLE, 0, 10, 100); |
|
| 158 | + | let other = try! budgets::seed(&mut STORE, &mut TABLE, 1, 10, 200); |
|
| 159 | + | let future = try! budgets::split(&mut STORE, &mut TABLE, other, 100, 10); |
|
| 160 | + | let received = try! budgets::bind(&mut STORE, &DOMAINS, &mut TABLE, |
|
| 161 | + | budgets::Binding { budget: first, domain, context }, 10); |
|
| 162 | + | let entry = try! capability::get(&MEMORY.table, received); |
|
| 163 | + | try testing::expect(not try! budgets::active(&STORE, entry.object, 0, 9)); |
|
| 164 | + | try testing::expect(try! budgets::active(&STORE, entry.object, 0, 10)); |
|
| 165 | + | try testing::expect(not try! budgets::active(&STORE, entry.object, 1, 10)); |
|
| 166 | + | try testing::expect(not try! budgets::active(&STORE, entry.object, 0, 100)); |
|
| 167 | + | let mut overlap = false; |
|
| 168 | + | try budgets::bind(&mut STORE, &DOMAINS, &mut TABLE, |
|
| 169 | + | budgets::Binding { budget: other, domain, context }, 10) catch err { |
|
| 170 | + | try testing::expect(err == abi::Error::Busy); set overlap = true; |
|
| 171 | + | }; |
|
| 172 | + | let unchanged = try! budgets::query(&STORE, &TABLE, other); |
|
| 173 | + | try testing::expect(overlap and unchanged.context == nil and unchanged.start == 10); |
|
| 174 | + | let next = try! budgets::bind(&mut STORE, &DOMAINS, &mut TABLE, |
|
| 175 | + | budgets::Binding { budget: future, domain, context }, 10); |
|
| 176 | + | let following = try! capability::get(&MEMORY.table, next); |
|
| 177 | + | try testing::expect(try! budgets::active(&STORE, following.object, 1, 100)); |
|
| 178 | + | let self = try! capability::install(&mut MEMORY.table, capability::Entry { |
|
| 179 | + | kind: abi::Kind::Domain, object: MEMORY.table.owner, rights: abi::Rights(abi::WAKE), |
|
| 180 | + | }); |
|
| 181 | + | let same = try! budgets::bind(&mut STORE, &DOMAINS, &mut MEMORY.table, |
|
| 182 | + | budgets::Binding { budget: received, domain: self, context }, 20); |
|
| 183 | + | let rebound = try! budgets::query(&STORE, &MEMORY.table, same); |
|
| 184 | + | try testing::expect(same == received and rebound.start == 20 and rebound.end == 100); |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | /// Binding validates context generations, receiver capacity, and expiration atomically. |
|
| 188 | + | @test unsafe fn bindingRejection() throws (testing::TestError) { |
|
| 189 | + | initialize(); |
|
| 190 | + | let domain = receiver(); |
|
| 191 | + | let handle = try! budgets::seed(&mut STORE, &mut TABLE, 0, 10, 100); |
|
| 192 | + | let mut request = budgets::Binding { budget: handle, domain, context: abi::Ref { index: 0, generation: 2 } }; |
|
| 193 | + | let mut failures: u32 = 0; |
|
| 194 | + | try budgets::bind(&mut STORE, &DOMAINS, &mut TABLE, request, 20) catch err { |
|
| 195 | + | try testing::expect(err == abi::Error::BadHandle); set failures += 1; |
|
| 196 | + | }; |
|
| 197 | + | set request.context.generation = 1; |
|
| 198 | + | for i in 0..MEMORY.table.slots.len { |
|
| 199 | + | set MEMORY.table.slots[i].state = slots::State::Retired; |
|
| 200 | + | } |
|
| 201 | + | try budgets::bind(&mut STORE, &DOMAINS, &mut TABLE, request, 20) catch err { |
|
| 202 | + | try testing::expect(err == abi::Error::InvalidArg); set failures += 1; |
|
| 203 | + | }; |
|
| 204 | + | let untouched = try! budgets::query(&STORE, &TABLE, handle); |
|
| 205 | + | try testing::expect(untouched.start == 10 and untouched.context == nil); |
|
| 206 | + | try budgets::bind(&mut STORE, &DOMAINS, &mut TABLE, request, 100) catch err { |
|
| 207 | + | try testing::expect(err == abi::Error::Busy); set failures += 1; |
|
| 208 | + | }; |
|
| 209 | + | set MEMORY.table.slots[0].state = slots::State::Free; |
|
| 210 | + | let domainSlot = try! abi::decode(domain); |
|
| 211 | + | set TABLE.entries[domainSlot.object.index].rights = abi::Rights(0); |
|
| 212 | + | try budgets::bind(&mut STORE, &DOMAINS, &mut TABLE, request, 20) catch err { |
|
| 213 | + | try testing::expect(err == abi::Error::Denied); set failures += 1; |
|
| 214 | + | }; |
|
| 215 | + | try testing::expect(failures == 4); |
|
| 216 | + | } |