graph: support checked construction abandonment

14b282d4540529bcc35405a0f2b28f1c6c681bc650ae1be4553aaceb4f43c489
Alexis Sellier committed ago 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 +
}