✓
Passing This code compiles and runs correctly.
Code
// R7 — DUAL PROVIDER: one handle depends on two providers, both must
// outlive it (the guard is a SET, not a single edge).
//
// merge(left: a.txt, right: b.txt) mints merged_1 whose depends_on holds
// BOTH pool ids. At hang-up merged_1 must release first, then a.txt and
// b.txt in any order — but neither file may release while merged_1 (which
// depends on it) is still held.
//
// A guard that checked only ONE provider per handle would release the
// unchecked file early — under a live dependent. The depends_on set is
// what the release check must consult.
import std/io
import std/bridge
import app/session
tor run-turn { br: *std/bridge:Bridge, source: string, label: string }
run-turn = std/bridge:run(br, source)
| result r |> std/io:print.ln(" {{ label:s }}: held={{ r.handles:d }}")
| defined d |> std/io:print.ln(" {{ label:s }}: DEFINED {{ d:s }}")
| event-denied ev |> std/io:print.ln(" {{ label:s }}: DENIED {{ ev:s }}")
| dispatch-error e |> std/io:print.ln(" {{ label:s }}: DISPATCH {{ e.message:s }}")
| parse-error e |> std/io:print.ln(" {{ label:s }}: PARSE {{ e.message:s }}")
| exhausted _ |> std/io:print.ln(" {{ label:s }}: EXHAUSTED")
| validation-error _ |> std/io:print.ln(" {{ label:s }}: VALIDATION")
| shape-error _ |> std/io:print.ln(" {{ label:s }}: SHAPE")
| scope-not-found _ |> std/io:print.ln(" {{ label:s }}: NO SCOPE")
[with]std/bridge:create(id: "dual", scope: "files"): br
|> std/io:print.ln("--- bridge open")
|> run-turn(br, source: "open(path: \"a.txt\")", label: "open a")
|> run-turn(br, source: "open(path: \"b.txt\")", label: "open b")
|> run-turn(br, source: "merge(left: \"a.txt\", right: \"b.txt\")", label: "merge")
|> std/io:print.ln("--- hang up; merged first, both providers after, never under it")
|> std/bridge:close(br)
|> std/io:print.ln("--- done")Actual
--- bridge open
open a: held=1
open b: held=2
merge: held=3
--- hang up; merged first, both providers after, never under it
close-merge() ran for 'merged_1'
[BRIDGE] Invoked 'close-merge' for handle 'merged_1' [app.session:merge]
close-file() ran for 'b.txt'
[BRIDGE] Invoked 'close-file' for handle 'b.txt' [app.session:open]
close-file() ran for 'a.txt'
[BRIDGE] Invoked 'close-file' for handle 'a.txt' [app.session:open]
--- done
Expected output
--- bridge open
open a: held=1
open b: held=2
merge: held=3
--- hang up; merged first, both providers after, never under it
close-merge() ran for 'merged_1'
[BRIDGE] Invoked 'close-merge' for handle 'merged_1' [app.session:merge]
close-file() ran for 'b.txt'
[BRIDGE] Invoked 'close-file' for handle 'b.txt' [app.session:open]
close-file() ran for 'a.txt'
[BRIDGE] Invoked 'close-file' for handle 'a.txt' [app.session:open]
--- done
Flows
subflow ~run-turn click a branch to expand · @labels scroll to their anchor
run (br, source)
flow ~create click a branch to expand · @labels scroll to their anchor
create (id: "dual", scope: "files")
Imported Files
// R7 session — one handle depending on TWO providers.
//
// merge takes two held handles and mints a third that depends on both.
// At hang-up the merged handle must release before EITHER provider: a
// provider may not withdraw while a handle that depends on it is still
// held, and this handle depends on both files.
~import std/runtime
const std = @import("std");
~pub tor open { path: string } -> string<open!>
~proc open|zig {
_ = path;
return path;
}
~pub tor merge { left: string, right: string } -> string<merge!>
~proc merge|zig {
_ = left;
_ = right;
return "merged_1";
}
~pub tor close-file { handle: string<!open> }
~proc close-file|zig {
std.debug.print("close-file() ran for '{s}'\n", .{handle});
}
~pub tor close-merge { handle: string<!merge> }
~proc close-merge|zig {
std.debug.print("close-merge() ran for '{s}'\n", .{handle});
}
~std/runtime:register(scope: "files") {
open(10)
merge(10)
close-file(1)
close-merge(1)
}Test Configuration
MUST_RUN