Repository navigation
Read & Write interfaces #4476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read & Write interfaces #4476
Changes from all commits
b33fda5
cdfb375
23d306b
14444d1
9dc57b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -238,3 +238,44 @@ function any<T, Err>( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| winners[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Runs `body` under a deadline, throwing `baml.errors.Timeout` if `limit` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// elapses first. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// The work runs under a cancel token that a sleeping task fires, so a body | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// parked in a sys-op (a socket read, a child's stdout) is unparked rather | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// than left running — this is the general form of a deadline in BAML, since | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `read` and friends take no timeout of their own. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function with_timeout<T, E>( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: baml.time.Duration, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: () -> T throws E, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> T throws E | baml.errors.Timeout { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let token = baml.spawn.CancelToken.new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let work = spawn with baml.spawn.options(cancel = token) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let deadline = spawn { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| baml.sys.sleep(limit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token.cancel() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let out = (await work) catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The token fires only from the timer above, so a cancelled body is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // an expired deadline — anything the body itself threw is `E` and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // propagates untouched. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| baml.panics.Cancelled => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let limit_ms = limit.to_milliseconds(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+261
to
+266
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Inspect how thread/future cancellation surfaces at `await` and whether the cause is distinguishable.
set -eu
fd -e rs -e baml . baml_language --exec rg -n -C 6 'panics\.Cancelled|Cancelled\b' {} \
| rg -v 'snapshots|target/' | head -200
echo '--- CancelToken surface ---'
fd . baml_language --glob '*spawn*.baml' --exec cat -n {}Repository: BoundaryML/baml Length of output: 6936 🏁 Script executed: #!/bin/bash
set -eu
echo '--- target function and nearby cancellation helpers ---'
fd -H -t f 'future\.baml$|.*spawn.*\.baml$|.*cancel.*\.baml$' baml_language \
| sort \
| while read -r f; do
echo "### $f"
rg -n -C 8 'with_timeout|CancelToken|token\.cancel|await |panics\.Cancelled|spawn' "$f" || true
done
echo '--- all BAML cancellation and await references ---'
rg -n -C 5 --glob '*.baml' \
'baml\.panics\.Cancelled|CancelToken|\.cancel\(\)|\bawait\b' baml_language \
| head -500
echo '--- Rust runtime cancellation/await implementation references ---'
rg -n -C 6 \
'CancelToken|Cancelled|cancel_token|Cancellation|await.*future|Future' \
baml_language --glob '*.rs' \
| head -600Repository: BoundaryML/baml Length of output: 50372 🏁 Script executed: #!/bin/bash
set -eu
echo '--- runtime files defining spawn, effective tokens, and await cancellation ---'
rg -l --glob '*.rs' \
'effective.*token|parent.*cancel|cancel.*cascade|Await.*cancel|await.*cancel|CancelToken|cancel_token' \
baml_language/crates \
| sort
echo '--- focused implementation excerpts ---'
rg -n -C 12 --glob '*.rs' \
'parent.*cancel|cancel.*cascade|effective_token|effective.*token|cancel_token' \
baml_language/crates \
| head -350
echo '--- await cancellation race excerpts ---'
rg -n -C 16 --glob '*.rs' \
'future_ready|AwaitAny|await.*cancel|cancel.*await|Cancellation.*await|is_cancelled' \
baml_language/crates \
| head -450Repository: BoundaryML/baml Length of output: 50371 🏁 Script executed: #!/bin/bash
set -eu
echo '--- spawn implementation ---'
rg -n -C 18 \
'effective|parent|detach|cancel|CancellationToken' \
baml_language/crates/bex_vm/src/package_baml/spawn.rs
echo '--- VM await implementation ---'
rg -n -C 20 \
'Await|await|CancellationToken|cancel_token|Cancelled' \
baml_language/crates/bex_vm/src/vm.rs \
baml_language/crates/bex_vm_types/src/types/future.rs \
baml_language/crates/bex_engine/src/lib.rs \
baml_language/crates/bex_engine/src/function_call_context.rs
echo '--- direct parent-cascade tests ---'
rg -n -C 18 \
'parent.*cancel|cancel.*parent|cascade|await.*Cancelled|Cancelled.*await' \
baml_language/crates/bex_engine/tests/cancellation.rs \
baml_language/crates/bex_engine/tests/fire_and_forget.rs \
baml_language/crates/baml_tests/tests/spawn_semantics.rsRepository: BoundaryML/baml Length of output: 50372 🏁 Script executed: #!/bin/bash
set -eu
echo '--- effective-token construction and user-token watcher ---'
sed -n '4810,4975p' baml_language/crates/bex_engine/src/lib.rs
echo '--- spawn dispatch and parent/detach selection ---'
rg -n -C 25 \
'spawn_thread\(|detach|child_cancel|CancellationToken::child_token|user_cancel' \
baml_language/crates/bex_engine/src/lib.rs \
| awk 'NR <= 500'
echo '--- VM await branches ---'
rg -n \
'Terminator::Await|AwaitAny|future_ready|future_id|vm_thread_cancel' \
baml_language/crates/bex_engine/src/lib.rs \
| head -120Repository: BoundaryML/baml Length of output: 38433 🏁 Script executed: #!/bin/bash
set -eu
echo '--- spawn dispatch token construction ---'
sed -n '5680,5765p' baml_language/crates/bex_engine/src/lib.rs
echo '--- ordinary await cancellation race ---'
sed -n '5740,5835p' baml_language/crates/bex_engine/src/lib.rs
echo '--- parent cancellation tests ---'
rg -n -C 12 \
'parent_throw_cancels_running_children|cancel.*await|await.*cancel|cascade' \
baml_language/crates/baml_tests/tests/spawn_semantics.rs \
baml_language/crates/bex_engine/tests/cancellation.rsRepository: BoundaryML/baml Length of output: 20058 Preserve caller cancellation in
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // `duration_ms` is an `int`, so a limit too large to fit is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // reported by message alone rather than failing the throw itself. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let reported = limit_ms.to_int() catch (conversion) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| baml.errors.InvalidArgument => null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw baml.errors.Timeout { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: `operation timed out after ${limit_ms}ms`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| duration_ms: reported, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The body won; stop the timer rather than leaving it to fire later. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deadline.cancel(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+261
to
+281
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Cancel Line 279 runs only when the body succeeds or the timeout arm rethrows. If the body throws Cancel the timer before any exit from ♻️ Proposed fix let out = (await work) catch (e) {
// The token fires only from the timer above, so a cancelled body is
// an expired deadline — anything the body itself threw is `E` and
// propagates untouched.
baml.panics.Cancelled => {
let limit_ms = limit.to_milliseconds();
// `duration_ms` is an `int`, so a limit too large to fit is
// reported by message alone rather than failing the throw itself.
let reported = limit_ms.to_int() catch (conversion) {
baml.errors.InvalidArgument => null,
};
throw baml.errors.Timeout {
message: `operation timed out after ${limit_ms}ms`,
duration_ms: reported,
}
}
+ let other => {
+ // Stop the timer before the body's own error leaves the function.
+ deadline.cancel();
+ throw other
+ }
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| interface Read { | ||
| /// Read some bytes from the source. | ||
| /// When the end of the source has already been reached, returns `null`. | ||
| /// | ||
| /// The `limit` parameter specifies the maximum number of bytes to read. | ||
| /// | ||
| /// This is the only method that implementors are required to provide. | ||
| function read(self, limit: int) -> uint8array? throws root.errors.Io | ||
|
|
||
| /// Reads all remaining bytes from the source until EOF is reached. | ||
| /// | ||
| /// Drains in fixed-size chunks rather than asking for everything at once: | ||
| /// an implementor is free to size a buffer from `limit`, so a limit of | ||
| /// `int.max_value()` is a request to allocate more memory than exists. | ||
| function read_to_end(self) -> uint8array throws root.errors.Io { | ||
| let buf = b""; | ||
| while let chunk: uint8array = self.read(65536) { | ||
| buf = buf.concat(chunk); | ||
| } | ||
| buf | ||
| } | ||
|
|
||
| /// Reads all remaining bytes as a UTF-8 string until EOF is reached. | ||
| /// If the source is not valid UTF-8, an error is thrown. | ||
| function read_to_string(self) -> string throws root.errors.Io | root.errors.ParseError { | ||
| let buf = self.read_to_end(); | ||
| string.from_utf8(buf) catch (_) { | ||
| _ => throw root.errors.ParseError { message: "invalid utf-8" }, | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| interface Write { | ||
| /// Write some bytes to the destination. | ||
| /// | ||
| /// Returns the number of bytes successfully written. | ||
| function write(self, data: uint8array) -> int throws root.errors.Io | ||
|
|
||
| /// Flush any buffered data to the destination. | ||
| function flush(self) -> void throws root.errors.Io | ||
|
|
||
| /// Keep writing data until all bytes have been written. | ||
| function write_all(self, data: uint8array) -> void throws root.errors.Io { | ||
| while (data.length() > 0) { | ||
| let written = self.write(data); | ||
| if (written == 0) { | ||
| throw root.errors.Io { message: "stream closed without writing all data" }; | ||
| } | ||
| data = data.slice(written, data.length()); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.