{"id":36212600,"url":"https://github.com/multiplyco/machine-latch","last_synced_at":"2026-01-11T04:04:24.728Z","repository":{"id":331249574,"uuid":"1123940885","full_name":"multiplyco/machine-latch","owner":"multiplyco","description":"Low-level synchronization primitive with state machine semantics","archived":false,"fork":false,"pushed_at":"2026-01-03T01:45:13.000Z","size":18,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-05T00:26:11.095Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/multiplyco.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-28T00:53:58.000Z","updated_at":"2026-01-04T13:10:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/multiplyco/machine-latch","commit_stats":null,"previous_names":["multiplyco/machine-latch"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/multiplyco/machine-latch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multiplyco%2Fmachine-latch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multiplyco%2Fmachine-latch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multiplyco%2Fmachine-latch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multiplyco%2Fmachine-latch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/multiplyco","download_url":"https://codeload.github.com/multiplyco/machine-latch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multiplyco%2Fmachine-latch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28280449,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T03:48:11.750Z","status":"ssl_error","status_checked_at":"2026-01-11T03:48:02.765Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-11T04:04:24.624Z","updated_at":"2026-01-11T04:04:24.720Z","avatar_url":"https://github.com/multiplyco.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Machine Latch\n\n[![Clojars Project](https://img.shields.io/clojars/v/co.multiply/machine-latch.svg)](https://clojars.org/co.multiply/machine-latch)\n[![cljdoc](https://cljdoc.org/badge/co.multiply/machine-latch)](https://cljdoc.org/d/co.multiply/machine-latch)\n\nA Clojure library providing a state machine latch with declarative transitions and await semantics. Designed for\ncoordinating concurrent operations on virtual threads.\n\n## Requirements\n\n- JDK 21+ (virtual threads require JDK 21)\n- JDK 25+ recommended (for native `ScopedValue` support)\n- Clojure 1.12+\n\nThis library depends on [co.multiply/scoped](https://github.com/multiplyco/scoped) which uses Java's `ScopedValue` API\non JDK 25+, with a fallback for older versions.\n\n## Installation\n\n```clojure\n;; deps.edn\nco.multiply/machine-latch {:mvn/version \"0.1.12\"}\n```\n\n## Why machine-latch?\n\nMachine Latch came together while hunting for (and failing to find) a synchronization primitive that would succinctly\nexpress a simple state machine.\n\nThe goal was to be able to declare a number of phases, specify valid transitions between them (and so exclude invalid\nones), while having the assurance that exactly one caller out of multiple can \"win\" right to perform a transition. \n\n- **Targeted wake-ups**: Threads waiting for e.g. phase 5 stay parked when you transition from phase 1 to phase 2.\n  Only threads whose target state has been reached are woken.\n- **Strict state machine contract**: Define valid states and transitions upfront. Only declared transitions are\n  allowed, and exactly one caller wins each transition via CAS.\n- **Small footprint**: Compiles down to a couple of `case` forms and some integer comparisons.\n\n```clojure\n(require '[co.multiply.machine-latch :as ml])\n\n(def make-job-latch\n  (ml/machine-latch-factory\n    {:states      [:queued :running :complete]\n     :transitions {:start  {:queued :running}\n                   :finish {:running :complete}\n                   :cancel {:queued  :complete\n                            :running :complete}}}))\n```\n\n## API\n\n### `machine-latch-factory`\n\nCreates a factory function from a machine spec. The factory produces latch instances that share compiled transition\nlogic.\n\n```clojure\n(require '[co.multiply.machine-latch :as ml])\n\n(def make-latch\n  (ml/machine-latch-factory\n    {:states      [:pending :running :done]\n     :transitions {:start  {:pending :running}\n                   :finish {:running :done}}}))\n\n(def latch (make-latch))\n```\n\nActions can have multiple source states:\n\n```clojure\n(def make-latch\n  (ml/machine-latch-factory\n    {:states      [:pending :running :done :cancelled]\n     :transitions {:start  {:pending :running}\n                   :finish {:running :done}\n                   :cancel {:pending :cancelled\n                            :running :cancelled}}}))\n```\n\n### `transition!`\n\nAtomically attempt a state transition. Returns `true` if this thread won the transition, `false` otherwise.\n\n```clojure\n(def latch (make-latch))\n\n(ml/transition! latch :start)  ; =\u003e true (won the transition)\n(ml/transition! latch :start)  ; =\u003e false (already past :pending)\n\n(ml/get-state latch)           ; =\u003e :running\n```\n\nWhen `transition!` returns `true`, the calling thread \"owns\" that phase and should proceed with the associated work.\nWhen it returns `false`, another thread won (or the action isn't valid from the current state).\n\n### `get-state`\n\nReturns the current state keyword. Non-blocking.\n\n```clojure\n(ml/get-state latch)  ; =\u003e :running\n```\n\n### `at-or-past?`\n\nNon-blocking check: has the latch reached or passed a given state?\n\n```clojure\n(ml/at-or-past? latch :pending)  ; =\u003e true (we're past it)\n(ml/at-or-past? latch :running)  ; =\u003e true (we're at it)\n(ml/at-or-past? latch :done)     ; =\u003e false (not yet)\n```\n\n### `await`\n\nBlock until the latch reaches or passes a target state. Returns `true` when reached.\n\n```clojure\n;; On a virtual thread:\n(ml/await latch :done)  ; blocks until :done, returns true\n```\n\nMust be called from a virtual thread by default (see [Platform Thread Protection](#platform-thread-protection)).\n\n### `await-millis` / `await-dur`\n\nBlock with a timeout. Returns `true` if the state was reached, `false` if timed out.\n\n```clojure\n(require '[co.multiply.machine-latch :as ml])\n(import '[java.time Duration])\n\n(ml/await-millis latch :done 5000)              ; 5 second timeout\n(ml/await-dur latch :done (Duration/ofSeconds 5))  ; same, with Duration\n```\n\n## Constraints\n\nValidated at compile time:\n\n- States must be declared in order from initial to terminal\n- Transitions must only go forward in this order\n- The `:states` vector defines both valid states and their ordering\n- The machine must have exactly one terminal state\n\n## Execution Ownership\n\n`transition!` grants execution ownership via CAS. When it returns `true`:\n\n- The caller \"owns\" the current phase's work\n- No other caller can win the same transition\n- The caller may proceed with phase-specific logic\n\nWhen it returns `false`, another thread won (or the transition isn't valid from the current state). The losing thread\nshould not proceed with that phase's work.\n\n## Platform Thread Protection\n\nBy default, `await` throws `IllegalStateException` if called from a platform thread. Parking platform threads is a bad\nidea, and is by default discouraged by this library. By throwing, you have an increased chance of discovering cases that\nmight be heading toward threadpool starvation.\n\nDisable via:\n\n- JVM property: `-Dco.multiply.machine-latch.assert-virtual=false`\n- At runtime: `(ml/throw-on-platform-park! false)`\n- Per-call: `(scoping [ml/*assert-virtual* false] (ml/await latch :state))` \\*\n\n\\* See [scoping](https://github.com/multiplyco/scoped#scoping-clj--cljs)\n\n## Example: Job Coordination\n\nA job that can be started, finished, or cancelled:\n\n```clojure\n(require '[co.multiply.machine-latch :as ml])\n\n(def make-job-latch\n  (ml/machine-latch-factory\n    {:states      [:queued :running :complete]\n     :transitions {:start  {:queued :running}\n                   :finish {:running :complete}\n                   :cancel {:queued  :complete\n                            :running :complete}}}))\n\n(let [latch (make-job-latch)]\n  ;; Spawn worker\n  (Thread/startVirtualThread\n    (fn []\n      (when (ml/transition! latch :start)\n        (Thread/sleep 5000)  ; simulate work\n        (ml/transition! latch :finish))))\n\n  ;; Wait for completion (on a virtual thread)\n  (Thread/startVirtualThread\n    (fn []\n      (ml/await latch :complete)\n      (println \"Job complete!\")))\n\n  ;; Or cancel it\n  (ml/transition! latch :cancel))\n```\n\n## License\n\nEclipse Public License 2.0. Copyright (c) 2025 Multiply. See [LICENSE](LICENSE).\n\nAuthored by [@eneroth](https://github.com/eneroth)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmultiplyco%2Fmachine-latch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmultiplyco%2Fmachine-latch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmultiplyco%2Fmachine-latch/lists"}