Skip to content

Repository files navigation

Chasma

A tiny transactional actor runtime in Clojure, inspired by David McClain's Lisp Actors.

Note: This library is in active early development, highly experimental, and subject to change.

Problem

Coordinating concurrent workflows typically requires explicit thread management, shared-state locks, or callback graphs. These approaches are brittle under contention and make it difficult to reason about when effects become visible. Chasma provides per-actor behaviors, transactional turns, and automatic retries so message handlers can stay pure and deterministic.

Quick Example

(require '[tailrecursion.chasma :as ch])
(defncounter [n]
(fn [cmd]
(case cmd
:inc (ch/become! ch/*self* (counter (inc n)))
:get (ch/*reply* n)
nil)))
(defndemo []
(let [u (ch/start! (ch/universe))
c (ch/lane (ch/spawn u (counter0)))]
(try
(ch/send! c :inc)
(ch/send! c :inc)
;; print 2
(println (deref (ch/ask c :get)))
(finally
(ch/stop! u)))))
;; Mutually recursive even/odd predicates (SICP-style)
(defu (ch/start! (ch/universe)))
(defevenA (ch/spawn u))
(defoddA (ch/spawn u))
(defneven-beh
([n] (even-beh n ch/*sender*))
([n cust]
(if (zero? n)
(ch/send! cust true)
(ch/send! oddA (dec n) cust))))
(defnodd-beh
([n] (odd-beh n ch/*sender*))
([n cust]
(if (zero? n)
(ch/send! cust false)
(ch/send! evenA (dec n) cust))))
(ch/become! evenA even-beh oddA odd-beh)
(println @(ch/ask evenA 42)) ;; => true
(println @(ch/ask oddA 17)) ;; => true
(ch/stop! u)

API Overview

VarDescription
universeCreates a stopped runtime universe. Accepts {:threads n :pump-poll-ms n :retry-base-ms n :retry-max-ms n :effect-error-handler f}.
start! universeStarts a universe; idempotent and returns the universe.
stop! universeStops a universe and shuts down its worker pool; idempotent and returns the universe.
spawn universe behavior-fnCreates an actor whose behavior is a variadic fn. Returns an Actor record.
spawn universeCreates a no-op actor in a universe.
spawn behavior-fnInside a turn only, creates an actor in *universe*.
lane actorCreates a private serialized target for an actor in the actor's universe.
send! target & msgEnqueues a message to an actor or lane target.
become! actor new-beh ...Schedules one or more behavior changes. Inside a turn it buffers until commit; outside it applies atomically.
ask target & msgSends a request to an actor or lane target and returns a java.util.concurrent.CompletableFuture (supports deref).
on-commit! & bodyDefers the body so it runs once, after the current turn commits successfully (must be called inside a turn).

Execution Model

  • A Universe owns a queue, worker pool, retry settings, and lifecycle. Actors and lanes are bound to one universe.
  • Each delivery runs inside an implicit transaction buffer. All outbound send! and ask deliveries and any number of same-universe become! updates made during a behavior execute only after the behavior returns successfully.
  • A thrown exception or compare-and-set failure during commit discards the buffered effects and re-enqueues the message with bounded exponential backoff.
  • on-commit! schedules irreversible effects (logging, IO, etc.) and can only be invoked inside a turn; failures before commit drop the effect entirely. Once state has committed, on-commit! failures are reported and do not retry the turn.
  • lane creates a serialized target/capability for an actor. Mail sent through one lane is processed FIFO, one full delivery turn at a time; separate lanes for the same actor remain independent.
  • Messages sent while an actor is executing through a lane preserve that lane as the reply capability, so callees can reply through the same serialized target.
  • send! and ask infer delivery ownership from the target. Stopped universes reject new sends and asks.

Dynamic Vars

Available inside every behavior:

VarMeaning
*universe*The universe currently executing.
*self*The actor envelope currently executing.
*sender*Reply target/capability for the current message, such as an actor, lane, or nil.
*reply*Convenience fn (fn [v]) that sends a reply back to *sender*.
*tx*Internal transaction buffer (implementation detail, provided for completeness).

Development

The project is built with deps.edn. Run the test suite with:

clojure -M:test

Run the WebSocket chat demo with:

clojure -M:chat

Then open http://localhost:8080/. The demo uses Undertow through the :chat alias only; Undertow is not a core library dependency.

License

MIT

About

A tiny transactional actor runtime in Clojure

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages