(std inbox)

Reference

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

Functions

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, and inbox-call tasks 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
#t if the item was enqueued or handed off to a waiting receiver; #f if closed, or full under DropNewest.

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 None after 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! or call-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! or call-fail!.

Cancellation handling depends on the state of the call:

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; None if closed or full under DropNewest.

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; None if closed or full under DropNewest.
(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
#t if this response resolved the call; #f if 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
#t if this failure completed the call; #f if 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!