graph: support checked construction abandonment
14b282d4540529bcc35405a0f2b28f1c6c681bc650ae1be4553aaceb4f43c489
1 parent
1fe3802c
lib/std/graph.rad
+7 -0
| 92 | 92 | /// Return the number of reserved nodes that still need initialization. |
|
| 93 | 93 | export fn pending 'g (builder: &Builder 'g) -> u32 { |
|
| 94 | 94 | return builder.pending; |
|
| 95 | 95 | } |
|
| 96 | 96 | ||
| 97 | + | /// Consume construction authority without publishing the graph. |
|
| 98 | + | /// Node identity allocations remain owned by the allocation session. |
|
| 99 | + | export fn abandon 'g (builder: Builder 'g) { |
|
| 100 | + | let case Builder 'g { owner: _, count: _, pending: _ } = builder |
|
| 101 | + | else panic "abandon: invalid builder"; |
|
| 102 | + | } |
|
| 103 | + | ||
| 97 | 104 | /// Consume construction authority and publish the completed graph. |
|
| 98 | 105 | /// An incomplete graph returns its builder in the error for recovery. |
|
| 99 | 106 | export fn freeze 'g (builder: Builder 'g) -> Frozen 'g throws (FreezeError 'g) { |
|
| 100 | 107 | if builder.pending <> 0 { |
|
| 101 | 108 | throw FreezeError 'g::Incomplete(builder); |
lib/std/graph/tests.rad
+61 -0
| 1 | 1 | //! Graph ownership, publication, and failure recovery tests. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | 4 | use std::lang::alloc; |
|
| 5 | 5 | ||
| 6 | + | /// Non-copy module state retained when graph construction is abandoned. |
|
| 7 | + | record ModuleState { |
|
| 8 | + | /// Test value returned to the caller. |
|
| 9 | + | value: u32, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// Fallible construction state that owns module and graph state. |
|
| 13 | + | record Construction: 'g { |
|
| 14 | + | /// Module state retained after graph abandonment. |
|
| 15 | + | module: ModuleState, |
|
| 16 | + | /// Graph authority consumed during abandonment. |
|
| 17 | + | graph: super::Builder 'g, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Abandon graph construction and return the independent module state. |
|
| 21 | + | fn recoverModule 'g (state: Construction 'g) -> ModuleState { |
|
| 22 | + | let case Construction 'g { module, graph } = state else panic; |
|
| 23 | + | super::abandon(graph); |
|
| 24 | + | return module; |
|
| 25 | + | } |
|
| 26 | + | ||
| 6 | 27 | /// Empty graphs can be published without node allocations. |
|
| 7 | 28 | @test fn testEmptyGraph() throws (testing::TestError) { |
|
| 8 | 29 | static DATA: [u8; 64] = [0; 64]; |
|
| 9 | 30 | let mut arena = alloc::new(&mut DATA[..]); |
|
| 10 | 31 | use arena as storage in { |
| 101 | 122 | }; |
|
| 102 | 123 | throw testing::TestError::Failed; |
|
| 103 | 124 | } |
|
| 104 | 125 | } |
|
| 105 | 126 | ||
| 127 | + | /// Abandoning any construction state leaves its allocation session usable. |
|
| 128 | + | @test fn testAbandon() throws (testing::TestError) { |
|
| 129 | + | static DATA: [u8; 128] = [0; 128]; |
|
| 130 | + | let mut arena = alloc::new(&mut DATA[..]); |
|
| 131 | + | use arena as storage in { |
|
| 132 | + | let empty = try! super::new(&storage); |
|
| 133 | + | super::abandon(empty); |
|
| 134 | + | ||
| 135 | + | let mut incomplete = try! super::new(&storage); |
|
| 136 | + | let abandonedNode = try! super::reserve(&mut incomplete, &storage); |
|
| 137 | + | assert super::pending(&incomplete) == 1; |
|
| 138 | + | super::abandon(incomplete); |
|
| 139 | + | ||
| 140 | + | let fresh = try! super::new(&storage); |
|
| 141 | + | let mut rejected = false; |
|
| 142 | + | try super::check(&fresh, abandonedNode) catch error { |
|
| 143 | + | assert error == super::Error::ForeignNode; |
|
| 144 | + | set rejected = true; |
|
| 145 | + | }; |
|
| 146 | + | assert rejected; |
|
| 147 | + | super::abandon(fresh); |
|
| 148 | + | ||
| 149 | + | let mut complete = try! super::new(&storage); |
|
| 150 | + | let node = try! super::reserve(&mut complete, &storage); |
|
| 151 | + | try! super::complete(&mut complete, node); |
|
| 152 | + | assert super::pending(&complete) == 0; |
|
| 153 | + | super::abandon(complete); |
|
| 154 | + | ||
| 155 | + | let reusable = try! super::new(&storage); |
|
| 156 | + | let frozen = try! super::freeze(reusable); |
|
| 157 | + | assert super::len(&frozen) == 0; |
|
| 158 | + | ||
| 159 | + | let graph = try! super::new(&storage); |
|
| 160 | + | let module = recoverModule(Construction 'storage { |
|
| 161 | + | module: ModuleState { value: 7 }, graph, |
|
| 162 | + | }); |
|
| 163 | + | assert module.value == 7; |
|
| 164 | + | } |
|
| 165 | + | } |
|
| 166 | + | ||
| 106 | 167 | /// Exhausted identity storage does not add incomplete nodes to the builder. |
|
| 107 | 168 | @test fn testAllocationFailure() throws (testing::TestError) { |
|
| 108 | 169 | for capacity in 0..9 { |
|
| 109 | 170 | static DATA: [u8; 16] = [0; 16]; |
|
| 110 | 171 | let mut arena = alloc::new(&mut DATA[..capacity]); |
test/tests/graph.reject.abandon.rad
added
+10 -0
| 1 | + | //! session-support |
|
| 2 | + | //! rejects: affine value used after move: 'builder' |
|
| 3 | + | ||
| 4 | + | use std::graph; |
|
| 5 | + | ||
| 6 | + | /// Construction authority cannot be used after abandonment. |
|
| 7 | + | fn reuse 'g (builder: graph::Builder 'g) { |
|
| 8 | + | graph::abandon(builder); |
|
| 9 | + | graph::pending(&builder); |
|
| 10 | + | } |