Concurrency
concurrent: starts child work and joins it before control continues.
Concurrent:
Each binding inside a concurrent: block runs as a child task.
concurrent:
left = slow_left()
right = slow_right()
Task Results
After the block, each binding is Result[T, ConcurrencyError]. The outer error is Timeout, Cancelled, or TaskFailed(message), not a returned application error. These are task-boundary outcomes, not exceptions.
import:
channel: ConcurrencyError(Timeout, Cancelled, TaskFailed)
pure func task_error_message(error: ConcurrencyError) -> String:
match error:
Timeout: "task timed out"
Cancelled: "task cancelled"
TaskFailed(message): message
func report_task_result(result: Result[Int, ConcurrencyError]) -> Void:
match result:
Ok(value): print(value.to_string())
Err(error): print_error(task_error_message(error))
func main(args: List[String]) -> Void:
report_task_result(Ok(42)) -- stdout: 42
Application Errors
A task that returns Result[T, E] produces two layers: Ok(Err(error)) means it completed with an application error; outer Err means a concurrency failure. An application Err does not cancel siblings. Inspect both layers and choose your application's failure policy.
import:
channel: ConcurrencyError(Timeout, Cancelled, TaskFailed), sleep_for
units: milliseconds
pure func application_error() -> Result[Int, String]:
Err("invalid input")
func successful_sibling() -> Int:
sleep_for(milliseconds(1))
42
func main(args: List[String]) -> Int:
concurrent:
application = application_error()
sibling = successful_sibling()
-- Outer Ok means the task completed; inner Err is application data.
application_failed = match application:
Ok(Err(message)):
print("application error: ${message}") -- stdout: application error: invalid input
message == "invalid input"
_:
False
-- Returning an application Err did not cancel this sibling.
sibling_finished = match sibling:
Ok(value):
print("sibling: ${value}") -- stdout: sibling: 42
value == 42
Err(_):
False
if application_failed and sibling_finished:
0
else:
1
Timeouts
A typed Duration deadline is shared by the block. Unfinished tasks return Err(Timeout). Cancellation is cooperative: the block waits for cleanup before continuing, which can take longer than the deadline.
import:
units: milliseconds
concurrent(timeout: milliseconds(500)):
value = slow_operation()
Cancellation Points
Sleep, channel waits, and joins observe cancellation. The compiler also inserts budgeted checkpoints in while, for, and lowered tail recursion. Cancelling a parent cancels and joins its structured children, closing scoped resources. Blocking native calls can delay that cleanup.
import:
channel: sleep_for
units: milliseconds
func slow() -> Int:
sleep_for(milliseconds(100))
42
pure func count_steps(limit: Int) -> Int:
var steps: Int = 0
while steps < limit:
steps += 1
steps
Bounded Fan-Out
for ... concurrently(limit: 2) bounds active children and joins them. It is statement-only; use List.concurrent to inspect task results and failures.
import:
channel: sleep_for
units: milliseconds
func rebuild_page(page: String) -> Void:
sleep_for(milliseconds(10))
print("rebuilt ${page}")
func main(args: List[String]) -> Void:
pages: List[String] = ["intro", "concurrency", "resources"]
for page in pages concurrently(limit: 2):
rebuild_page(page)
Detached Work
detach has no result handle or implicit join. Detached work is not guaranteed to finish after main returns; process shutdown can discard parked tasks. Use structured concurrency when completion matters. This example waits for a bounded channel acknowledgement, not proof of task termination.
import:
channel:
RecvAttempt(RecvSealed, RecvTimedOut, RecvValue, RecvWouldBlock),
recv_timeout_attempt_for,
wait_send
units: seconds
func publish_ready(done: Channel[String]) -> Void:
match wait_send(done, "ready"):
Ok(_):
void
Err(_):
print_error("ready channel was sealed")
func main(args: List[String]) -> Int:
done: Channel[String] = channel(1)
detach publish_ready(done)
-- Wait for an acknowledgement; detach itself provides no join or result.
match recv_timeout_attempt_for(done, seconds(5)):
RecvValue(message):
print(message) -- stdout: ready
0
RecvSealed:
print_error("ready channel was sealed")
1
RecvTimedOut:
print_error("background task did not report")
1
RecvWouldBlock:
print_error("background task was not ready")
1
Example
import:
channel: sleep_for
units: milliseconds, seconds
func slow_left() -> Int:
sleep_for(milliseconds(100))
20
func slow_right() -> Int:
sleep_for(milliseconds(100))
22
func report_task_error(error: ConcurrencyError) -> Void:
match error:
Timeout: print_error("task timed out")
Cancelled: print_error("task cancelled")
TaskFailed(message): print_error(message)
func main(args: List[String]) -> Int:
concurrent(timeout: seconds(5)):
left = slow_left()
right = slow_right()
match left:
Ok(a):
match right:
Ok(b):
print(a + b) -- prints: 42
0
Err(error):
report_task_error(error)
1
Err(error):
report_task_error(error)
1
Try it
blorp run two-tasks.brp