Overview
Thread-safe bounded queue bridging external .NET threads and Bjolang fibers with backpressure and request/reply support.
(import (std inbox))
(: changes (Inbox string))
(def changes (make-inbox 1000 DropOldest))
;; External callback executed on a .NET thread: posts and returns immediately.
(: on-change (-> string void))
(defun (on-change path)
(ignore (inbox-post! changes path)))
;; Fiber consumes incoming items within its own execution context.
(: watch (-bjo-> (Inbox string) int))
(defbjo (watch ib)
(let go ((n 0))
(match (sync (choose (wrap (inbox-recv ib) Some)
(wrap (inbox-closed ib) (fun (_) None))))
((Some path) (log (str "changed: " path)) (go (+ n 1)))
(None n))))
;; Request/reply pattern: a .NET caller awaits a Task, handled by a fiber.
(: requests (Inbox (Call int int)))
(def requests (make-inbox 64 DropNewest))
;; On the .NET thread: (inbox-call requests 21) returns a (Task int).
;; On the fiber:
(: serve (-bjo-> (Inbox (Call int int)) void))
(defbjo (serve ib)
(let ((c (sync (inbox-recv ib))))
(try (ignore (call-reply! c (* 2 (call-request c))))
;; Ensure the caller receives a fault if an exception occurs.
#:finally (ignore (call-fail! c (Exception. "no answer"))))
(serve ib)))
An inbox provides a thread-safe communication boundary between external
.NET threads and Bjolang fibers. Threads outside the fiber scheduler
(such as Kestrel workers, timer threads, or FileSystemWatcher
callbacks) enqueue items without blocking. Receiving fibers consume
items within their own dynamic execution environment, maintaining
dedicated effect handlers, dynamic parameters, and cancellation scopes.
External callbacks. Callbacks invoked by external .NET runtimes
should interact with Bjolang by posting to an inbox. These foreign
callbacks execute as synchronous functions rather than fibers; they do
not share active fiber parameters or effect handlers. Calling
(log ...) within foreign callbacks routes directly to the default
global logger. Post items to an inbox and perform domain processing
within the receiving fiber.
Bounded capacity and backpressure. Every inbox requires an
explicit capacity limit to prevent unbounded memory growth when
producers outpace consumers. make-inbox requires a
WhenFull policy governing inbox-post! and
inbox-call. Synchronous operations like inbox-send and
inbox-send-evt block until space is available rather than
dropping items.
Concurrent ML events. inbox-recv, inbox-send-evt,
and inbox-closed are first-class CML events. If an alternative
branch in choose synchronizes first, no items are enqueued or
dequeued. Items preserve FIFO ordering. Senders waiting for available
space are queued in FIFO order across both tasks and events.
Thread hopping and fiber scheduling. Posts from external threads do not immediately run receiving fibers on the caller’s thread, preventing deadlocks when foreign threads hold external locks. Instead, the scheduler queues the receiver on the Bjolang fiber pool. Posts originating from fibers wake receivers immediately.
Termination and lifecycle. inbox-close! and
inbox-fail! initiate graceful shutdown, allowing consumers to
drain queued items. inbox-abort! immediately drains and discards
all pending items. inbox-closed triggers once an inbox is closed
and all remaining items have been consumed.
Request/reply workflows. Inboxes handling Call instances
support structured request/reply workflows. Fibers receiving a
Call should ensure a response is provided (typically within a
#:finally block) via call-reply! or call-fail!,
preventing external callers from waiting indefinitely.
See BjolangRuntime/Cml/Inbox.cs for implementation details.
See also: make-inbox, inbox-post!, inbox-recv, inbox-closed, inbox-call
Reference
Types
WhenFull
union ↑ Types
Overflow policy determining how a full inbox handles items submitted via inbox-post! or inbox-call.
DropNewest- Rejects the incoming item.
inbox-post!returns#f, andinbox-calltasks fault. DropOldest- Evicts the oldest queued item to accept the incoming item. The evicted item's task faults if it was a call.
This policy does not apply to inbox-send, inbox-send/token,
or inbox-send-evt, which wait until space is available.
Faulted tasks fail with Bjoml.InboxFullException. Discarded items
increment inbox-dropped-count.
See also: make-inbox, inbox-dropped-count
IObserver
type alias ↑ Types
The .NET System.IObserver interface used to subscribe to IObservable event streams.
%a- Type of emitted elements.
See also: make-observer, inbox-observer
CancellationToken
type alias ↑ Types
The .NET System.Threading.CancellationToken structure passed to inbox operations.
See also: inbox-call/token, inbox-send/token, inbox-try-call/token
Functions
make-inbox
function ↑ Functions
(: make-inbox (-> int WhenFull (Inbox %a)))
(make-inbox capacity policy)
Creates an empty inbox with the specified maximum capacity and overflow policy.
capacity- Maximum number of queued items. Must be at least 1.
policy- Overflow policy applied when the inbox is full.
- returns
- A new, open inbox instance.
- raises
System.ArgumentOutOfRangeException - When capacity is less than 1.
(def events (make-inbox 1000 DropOldest))
Inboxes require an explicit capacity limit to enforce backpressure and prevent unbounded memory growth from rapid event sources (such as file system notifications or high-throughput HTTP connections).
See also: WhenFull, inbox-post!, inbox-recv
inbox-post!
function ↑ Functions
(: inbox-post! (-> (Inbox %a) %a bool))
(inbox-post! ib x)
Enqueues an item without blocking. Safe to call from any thread.
ib- The inbox.
x- The item to enqueue.
- returns
#tif the item was enqueued or handed off to a waiting receiver;#fif closed, or full underDropNewest.
Designed for foreign threads without a fiber context. When capacity is
reached, behavior follows the configured WhenFull policy. Under
DropOldest, the oldest item is evicted and #t is returned.
If an evicted item is a Call, its associated task faults with
Bjoml.InboxFullException.
See also: inbox-send, inbox-send-evt, WhenFull, inbox-dropped-count
inbox-send
function ↑ Functions
(: inbox-send (-> (Inbox %a) %a (Task Unit)))
(inbox-send ib x)
Enqueues an item, returning a Task that completes when queued. Safe from any thread.
ib- The inbox.
x- The item to enqueue.
- returns
- A Task that completes when the item is enqueued or delivered.
When the inbox is full, the task remains pending until space is freed.
Does not drop items; ignores WhenFull. Cancels immediately if the
inbox is closed or aborted. Fibers should prefer inbox-send-evt.
See also: inbox-send/token, inbox-send-evt, inbox-post!
inbox-send/token
function ↑ Functions
(: inbox-send/token (-> (Inbox %a) %a CancellationToken (Task Unit)))
(inbox-send/token ib x token)
Enqueues an item with an external CancellationToken, returning a completion task.
ib- The inbox.
x- The item to enqueue.
token- Cancellation token to monitor (e.g. ASP.NET
RequestAborted). - returns
- A Task that completes when the item is enqueued or delivered.
If cancellation is triggered while waiting for capacity, the item is not enqueued and the task is cancelled.
See also: inbox-send, inbox-call/token
inbox-send-evt
function ↑ Functions
(: inbox-send-evt (-> (Inbox %a) %a (Event Unit)))
(inbox-send-evt ib x)
Returns a CML event that enqueues an item when capacity is available.
ib- The inbox.
x- The item to enqueue.
- returns
- An event that synchronizes when the item is enqueued.
(sync (choose (inbox-send-evt ib job)
(wrap (inbox-closed ib) (fun (_) (log "inbox closed")))))
The event synchronizes once space is available. If an alternative branch
in choose synchronizes first, the item is not enqueued. Never
synchronizes on a closed inbox.
See also: inbox-send, inbox-closed
inbox-recv
function ↑ Functions
(: inbox-recv (-> (Inbox %a) (Event %a)))
(inbox-recv ib)
Returns a CML event that dequeues and yields the oldest item in the inbox.
ib- The inbox.
- returns
- An event yielding the dequeued item.
First-class CML event. If another branch in choose synchronizes
first, no item is dequeued. Never synchronizes if the inbox is closed and
empty; use inbox-closed to monitor shutdown.
See also: inbox-closed, inbox-post!
inbox-closed
function ↑ Functions
(: inbox-closed (-> (Inbox %a) (Event (Option Exception))))
(inbox-closed ib)
Returns a CML event that triggers when the inbox is closed and all items have been consumed.
ib- The inbox.
- returns
- An event yielding
Noneafter normal closure or abort, or(Some e)after failure.
(defbjo (drain ib)
(let go ()
(match (sync (choose (wrap (inbox-recv ib) Some)
(wrap (inbox-closed ib) (fun (_) None))))
((Some x) (handle x) (go))
(None 'done))))
Triggers once the inbox is closed, all queued items have been dequeued, and all pending send operations have committed. Subsequent synchronizations complete immediately.
See also: inbox-recv, inbox-close!, inbox-fail!, inbox-abort!
inbox-close!
function ↑ Functions
(: inbox-close! (-> (Inbox %a) void))
(inbox-close! ib)
Initiates graceful shutdown of the inbox, allowing remaining queued items to be consumed.
ib- The inbox.
Subsequent calls to inbox-post! return #f, and new send or
call tasks are cancelled immediately. Queued items remain accessible to
receivers. Pending send operations waiting for space are permitted to
complete. inbox-closed triggers once all items are consumed.
Idempotent.
See also: inbox-fail!, inbox-abort!, inbox-closed
inbox-fail!
function ↑ Functions
(: inbox-fail! (-> (Inbox %a) Exception void))
(inbox-fail! ib e)
Initiates graceful shutdown with an exception, causing inbox-closed to yield (Some e).
ib- The inbox.
e- The exception indicating failure.
The failure exception is stored separately from queued items so it cannot be dropped by overflow policies. The first failure is preserved; later failures are ignored.
See also: inbox-close!, inbox-closed
inbox-abort!
function ↑ Functions
(: inbox-abort! (-> (Inbox %a) void))
(inbox-abort! ib)
Abruptly closes the inbox and discards all pending items.
ib- The inbox.
All queued items are discarded immediately. Pending call and send tasks
are cancelled. inbox-closed triggers with None or an
earlier failure exception. Discarded items do not increment
inbox-dropped-count.
See also: inbox-close!, inbox-closed
inbox-dropped-count
function ↑ Functions
(: inbox-dropped-count (-> (Inbox %a) int))
(inbox-dropped-count ib)
Returns the total number of items discarded by the overflow policy.
ib- The inbox.
- returns
- Number of dropped items.
Enables monitoring of dropped message metrics. Items discarded by
inbox-abort! are not included in this count.
See also: WhenFull, inbox-post!
inbox-observer
function ↑ Functions
(: inbox-observer (-> (Inbox %a) (IObserver %a)))
(inbox-observer ib)
Adapts the inbox into a .NET IObserver instance for IObservable subscriptions.
ib- The inbox.
- returns
- An IObserver forwarding OnNext to inbox-post!, OnError to inbox-fail!, and OnCompleted to inbox-close!.
(.Subscribe observable (inbox-observer ib))
OnNext invokes inbox-post!, adhering to the inbox’s
configured WhenFull overflow policy.
See also: make-observer, inbox-post!, inbox-fail!, inbox-close!
make-observer
function ↑ Functions
(: make-observer (-> (-> %a void) (-> Exception void) (-> void) (IObserver %a)))
(make-observer on-next on-error on-completed)
Constructs an IObserver from three callback delegates.
on-next- Delegate invoked for each emitted item.
on-error- Delegate invoked when the stream terminates with an error.
on-completed- Delegate invoked when the stream terminates successfully.
- returns
- An IObserver delegating to the provided callbacks.
Provides an adapter for IObservable.Subscribe. The callback
delegates execute on the thread of the observable and should adhere to
standard foreign callback rules: post to an inbox and avoid blocking.
See also: inbox-observer
inbox-call
function ↑ Functions
(: inbox-call (-> (Inbox (Call %q %r)) %q (Task %r)))
(inbox-call ib request)
Enqueues a request as a Call and returns a task representing the eventual response. Safe from any thread.
ib- The request/reply inbox.
request- The request payload.
- returns
- A Task completing when a fiber responds via
call-reply!orcall-fail!.
Enqueues the request without blocking, adhering to WhenFull. If
rejected or evicted, the task faults with Bjoml.InboxFullException.
On a closed inbox, the task is cancelled immediately.
See also: inbox-call/token, inbox-try-call, call-request, call-reply!
inbox-call/token
function ↑ Functions
(: inbox-call/token (-> (Inbox (Call %q %r)) %q CancellationToken (Task %r)))
(inbox-call/token ib request token)
Enqueues a request as a Call with a cancellation token, returning a response task.
ib- The request/reply inbox.
request- The request payload.
token- Cancellation token to monitor (e.g. ASP.NET
RequestAborted). - returns
- A Task completing when a fiber responds via
call-reply!orcall-fail!.
Cancellation handling depends on the state of the call:
- Queued: The call is evicted from the queue and its task is cancelled immediately.
- Dequeued by a fiber: The task is not cancelled
automatically. Instead,
call-abandonedtriggers on the fiber, allowing the worker to handle abandonment safely.
See also: inbox-call, call-abandoned, inbox-try-call/token
inbox-try-call
function ↑ Functions
(: inbox-try-call (-> (Inbox (Call %q %r)) %q (Option (Task %r))))
(inbox-try-call ib request)
Attempts to post a request as a Call, returning None instead of faulting if full or closed.
ib- The request/reply inbox.
request- The request payload.
- returns
(Some task)if enqueued successfully;Noneif closed or full underDropNewest.
Useful in HTTP middleware where returning a faulted task would trigger generic 500 error handlers rather than returning an explicit 503 Service Unavailable response.
See also: inbox-call, inbox-try-call/token, inbox-post!
inbox-try-call/token
function ↑ Functions
(: inbox-try-call/token (-> (Inbox (Call %q %r)) %q CancellationToken (Option (Task %r))))
(inbox-try-call/token ib request token)
Attempts to post a request as a Call with a cancellation token, returning None if rejected.
ib- The request/reply inbox.
request- The request payload.
token- Cancellation token to monitor.
- returns
(Some task)if enqueued successfully;Noneif closed or full underDropNewest.
(match (inbox-try-call/token requests ctx (request-aborted ctx))
((Some answering) (cast Task answering))
(None (set-status! (response-of ctx) 503) completed-task))
See also: inbox-try-call, inbox-call/token, call-abandoned
call-request
function ↑ Functions
(: call-request (-> (Call %q %r) %q))
(call-request c)
Returns the request payload associated with a Call.
c- The call instance.
- returns
- The request value.
See also: call-reply!, call-fail!
call-reply!
function ↑ Functions
(: call-reply! (-> (Call %q %r) %r bool))
(call-reply! c value)
Resolves a Call successfully with the provided response value.
c- The call instance.
value- The response value.
- returns
#tif this response resolved the call;#fif already completed or cancelled.
The first response or failure wins; subsequent resolutions are ignored. Callers awaiting the task resume on their original synchronization context or thread pool, not on the responding fiber’s thread.
See also: call-fail!, call-abandoned
call-fail!
function ↑ Functions
(: call-fail! (-> (Call %q %r) Exception bool))
(call-fail! c e)
Faults a Call with the provided exception.
c- The call instance.
e- The exception indicating failure.
- returns
#tif this failure completed the call;#fif already completed or cancelled.
See also: call-reply!
call-abandoned
function ↑ Functions
(: call-abandoned (-> (Call %q %r) (Event Unit)))
(call-abandoned c)
Returns a CML event that triggers if the caller’s cancellation token cancels after dequeuing.
c- The call instance.
- returns
- An event enabled once cancellation is requested.
(sync (choose (wrap (work-evt (call-request c))
(fun (v) (ignore (call-reply! c v))))
(wrap (call-abandoned c)
(fun (_) (ignore (call-fail! c (System.OperationCanceledException.)))))))
Does not trigger for calls created without a cancellation token, nor for calls cancelled while still waiting in the queue.
See also: inbox-call/token, inbox-try-call/token, call-reply!