{"id":32196145,"url":"https://github.com/teknql/tapestry","last_synced_at":"2026-02-24T04:02:55.157Z","repository":{"id":43200788,"uuid":"287807954","full_name":"teknql/tapestry","owner":"teknql","description":"Weave loom fibers into your Clojure","archived":false,"fork":false,"pushed_at":"2025-05-29T16:56:41.000Z","size":71,"stargazers_count":247,"open_issues_count":0,"forks_count":6,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-10-22T02:41:54.699Z","etag":null,"topics":["clojure","concurrency","loom"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teknql.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}},"created_at":"2020-08-15T19:00:58.000Z","updated_at":"2025-10-04T20:37:46.000Z","dependencies_parsed_at":"2024-02-26T17:53:59.942Z","dependency_job_id":"2005f95e-9d62-4e80-b6b7-30495daa7e3f","html_url":"https://github.com/teknql/tapestry","commit_stats":{"total_commits":30,"total_committers":2,"mean_commits":15.0,"dds":0.09999999999999998,"last_synced_commit":"a0b99d3e5a73b3b51075572b3f7452087b2da1b7"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/teknql/tapestry","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teknql%2Ftapestry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teknql%2Ftapestry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teknql%2Ftapestry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teknql%2Ftapestry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teknql","download_url":"https://codeload.github.com/teknql/tapestry/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teknql%2Ftapestry/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29771049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T04:01:02.180Z","status":"ssl_error","status_checked_at":"2026-02-24T03:59:49.901Z","response_time":75,"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":["clojure","concurrency","loom"],"created_at":"2025-10-22T02:27:53.341Z","updated_at":"2026-02-24T04:02:55.151Z","avatar_url":"https://github.com/teknql.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tapestry\n\n[![Build Status](https://github.com/teknql/tapestry/actions/workflows/ci.yml/badge.svg)](https://github.com/teknql/tapestry/actions)\n[![Clojars Project](https://img.shields.io/clojars/v/teknql/tapestry.svg?include_prereleases)](https://clojars.org/teknql/tapestry)\n[![cljdoc badge](https://cljdoc.org/badge/teknql/tapestry)](https://cljdoc.org/d/teknql/tapestry)\n\nNext generation concurrency primitives for Clojure built on top of Project Loom\n\n\n## About\n\n[Project Loom](https://wiki.openjdk.java.net/display/loom/Main) is bringing first-class fibers\nto the JVM! Tapestry seeks to bring ergonomic clojure APIs for working with Loom.\n\n### What are Fibers and Why do I care?\n\nFibers behave similarly to OS level threads, but are much lighter weight to spawn, allowing\npotentially millions of them to exist.\n\nClojure already has the wonderful [core.async](https://github.com/clojure/core.async)\nand [manifold](https://github.com/clj-commons/manifold) libraries but writing maximally performant code\nin either requires the abstraction (channels, or promises) to leak all over your code\n(as you return channels or promise chains) to avoid blocking. Furthermore you frequently have to\nthink about which executor will handle the blocking code.\n\nLoom moves handling parking to the JVM runtime level making it possible for \"blocking\" code to be\nexecuted in a highly parallel fashion without the developer having to explicitly opt into the\nbehavior. This is similar to how Golang and Haskell achieve their highly performant parallelism.\n\nSome great further reading on the topic:\n\n  - [What color is your function](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/) - A mental exploration of\n  the leaking of the abstraction.\n  - [Async Rust Book Async Intro](https://rust-lang.github.io/async-book/01_getting_started/02_why_async.html) - A\n  good explanation of why we want async. The rest of this book is fantastic in terms of\n  understanding how async execution works under the hood. In `manifold` and `core.async`\n  the JVM executor is mostly analogous to the rust's concept of the Executor. In a language like\n  Rust, without a runtime, being explicit and \"colorizing functions\" makes sense, but with a\n  run-time we can do better.\n  - [Project Loom Wiki](https://wiki.openjdk.java.net/display/loom/Main#Main-Continuations) - Internal design notes of Loom.\n\n### Project State\n\nTapestry is still pre-1.0. As the APIs in loom have stabilized so too has tapestry.\n\nTapestry is being used in production for several of Teknql's projects and has\nmore or less replaced both `clojure.core/future` and `manifold.deferred/future`.\n\nIt is the ambition of the project to eventually drop manifold entirely, at which\npoint it will likely hit 1.0.\n\n## Installation\n\nAdd to your deps.edn:\n\n```\nteknql/tapestry {:git/url \"https://github.com/teknql/tapestry\"\n                 :git/sha \"d4c09e1866ab9d4988f04fca83969043b4c857f8\"}\n```\n\n## Showcase\n\nHere is a demo of some of the basics.\n\n#### Spawning a Fiber\n\n```clojure\n(require '[tapestry.core :refer [fiber fiber-loop]])\n\n;; Spawning a Fiber behaves very similarly to `future` in standard clojure, but\n;; runs in a Loom Fiber and returns a tapestry.core.Fiber which implements IDeref.\n@(fiber (+ 1 2 3 4))\n;; =\u003e 10\n\n\n;; Or, Like `core.async`'s `go-loop'\n\n@(fiber-loop [i 0]\n   (if (= i 5)\n     (* 2 i)\n     (do (Thread/sleep 100)\n         (recur (inc i)))))\n;; =\u003e 10, after aprox 500ms of sleeping\n```\n\n#### Interrupting and introspecting a Fiber\n```clojure\n(require '[tapestry.core :refer [fiber interrupt! alive?]]')\n\n(let [f (fiber (Thread/sleep 10000))]\n  (alive? f) ;; true\n  (interrupt! f)\n  (alive? f) ;; false\n  @f ;; Raises java.lang.InterruptedException))\n```\n\n#### Timeouts\n\nTapestry supports setting timeouts on fibers which will cause them to be\ninterrupted (with a `java.lang.InterruptedException`) when the timeout is hit.\n\n```clojure\n(require '[tapestry.core :refer [fiber timeout! alive?]]')\n\n(let [f (fiber (Thread/sleep 10000))]\n  (timeout! f 100)\n  (alive? f) ;; true\n  (Thread/sleep 200)\n  (alive? f) ;; false\n  @f ;; Raises java.util.concurrent.TimeoutException))\n```\n\nYou can also specify a default value\n\n```clojure\n(require '[tapestry.core :refer [fiber timeout! alive?]]')\n\n(let [f (fiber (Thread/sleep 10000))]\n  (timeout! f 100 :default)\n  @f ;; =\u003e :default))\n```\n\n\nYou can use dynamic bindings to set a timeout on a bunch of fibers. Note that\neach fiber will have a timeout that starts from when the fiber was spawned.\n\n```clojure\n(require '[tapestry.core :refer [fiber alive? with-timeout]]')\n\n(with-timeout 100 ;; Accepts a duration or number of millis\n  (let [f (fiber (Thread/sleep 10000))]\n    @f ;; raises java.util.concurrent.TimeoutException\n    ))\n```\n\n#### Processing Sequences\n```clojure\n(require '[tapestry.core :refer [parallelly asyncly pfor]]\n         '[clj-http.client :as clj-http])\n\n(def urls\n  [\"https://google.com\"\n   \"https://bing.com\"\n   \"https://yahoo.com\"])\n\n;; We can also run a function over a sequence, spawning a fiber for each item.\n(-\u003e\u003e urls\n     (parallelly clj-http/get))\n\n;; We can using the built in `pfor` macro to evaluate a `for` expression in parallel. Note that unlike\n;; clojure.core/for, this is not lazy.\n(pfor [url urls]\n  (clj-http/get url))\n\n;; Similalry, if we don't care about the order of items being maintained, and instead just want\n;; to return results as quickly as possible\n\n(doseq [resp (asyncly clj-http/get urls)]\n  (println \"Got Response!\" (:status resp)))\n```\n\n#### Bounded Parallelism\n\n```clojure\n;; We can control max parallelism for fibers\n(require '[tapestry.core :refer [parallelly fiber]])\n\n;; Note that you can also use `with-max-parallelism` within a fiber body\n;; which will limit parallelism of all newly spawned fibers. Consider the following\n;; in which we process up to 3 orders simultaneously, and each order can process up to 2\n;; tasks in parallel.\n(defn process-order!\n  [order]\n  (with-max-parallelism 2\n    (let [internal-notification-success? (fiber (send-internal-notification! order))\n          shipping-success?     (fiber (ship-order! order))\n          receipt-success?      (fiber (send-receipt! order))]\n      {:is-notified @internal-notification-success?\n       :is-shipped  @shipping-success?\n       :has-receipt @receipt-success?})))\n(with-max-parallelism 3\n  (let [order-a-summary (process-order! order-a)\n        order-b-summary (process-order! order-b)\n        order-c-summary (process-order! order-c)\n        order-d-summary (process-order! order-d)]\n    {:a @order-a-summary\n     :b @order-b-summary\n     :c @order-c-summary\n     :d @order-d-summary})\n\n\n;; You can also bound the parallelism of sequence processing functions by specifying\n;; an optional bound:\n\n(asyncly 3 clj-http/get urls)\n\n(parallelly 3 clj-http/get urls)\n```\n\n\n#### Manifold Support\n\n```clojure\n(require '[manifold.stream :as s]\n         '[tick.alpha.api :as t]\n         '[tapestry.core :refer [periodically parallelly asyncly]])\n\n;; tapestry.core/periodically behaves very similar to manifold's built in periodically,\n;; but runs each task in a fiber. You can terminate it by closing the stream.\n(let [count     (atom 0)\n      generator (periodically (t/new-duration 1 :seconds) #(swap! count inc))]\n    (-\u003e\u003e generator\n         (s/consume #(println \"Count is now:\" %)))\n    (Thread/sleep 5000)\n    (s/close! generator))\n\n;; Also, `parallelly` and `asyncly` both support manifold streams, allowing you to describe parallel\n;; execution pipelines\n(-\u003e\u003e (s/stream)\n     (parallelly 5 some-operation)\n     (asyncly 5 some-other-operation)\n     (s/consume #(println \"Got Result\" %)))\n```\n\n#### Working with Agents\n\n``` clojure\n(let [counter (agent 0)]\n  (tapestry.core/send counter inc)\n  (await counter)\n  @a)\n  ;; =\u003e 1\n```\n\n## Experimental Features\n\nNote that you must run your JVM with `--enable-preview` for the following\n\n#### alts\n\n``` clojure\n(require '[tapestry.experimental :refer [alts]])\n\n;; Runs all expressions in parallel returning the first successful\n;; result. Will not forward errors from children expressions.\n(alts\n  (do (Thread/sleep 100)\n      :first)\n  (do (Thread/sleep 10)\n      :second))\n;; =\u003e :second after 10ms\n```\n\n#### Queues\n\nThe beginnings of potentially dropping manifold support. Lightweight wrapper around java's\nBlockingQueues with the notion of `closing` ala `manifold` and `core.async`.\n\n``` clojure\n(require '[tapestry.queue :as q]\n         '[tapestry.core :refer [fiber alive?]]')\n\n;; By default a queue has no buffer\n(let [q     (q/queue)\n      take* (fiber (q/take! q))]\n  (alive? take*)\n  (q/try-take! q) ;; =\u003e nil, no value available\n  (q/put! q :value) ;; =\u003e true, value put successfuly\n  @take* ;; =\u003e :value, resolved in the fiber above\n  (q/close! q) ;; Closing a queue will make it so no further items are accepted,\n               ;; but previously queued items will be delivered via `take!`\n  (q/put! q :value) ;; Returns false\n)\n```\n\n## Advisories\n\nNone at the moment\n\n## CLJ Kondo Config\n\nAdd the following to your `.clj-kondo/config.edn`\n\n```clojure\n{:lint-as {tapestry.core/fiber-loop clojure.core/loop\n           tapestry.core/pfor       clojure.core/for}}\n```\n\n## Long Term Wish List\n\n- [ ] Consider whether we can drop manifold. What do streams look like?\n- [ ] Implement structured concurrency using either java built-in APIs\n      (currently gated) or see if clojure affords us a nicer API.\n- [ ] Consider implement linking ala erlang\n- [ ] Consider implementing an OTP-like interface\n- [ ] Consider cljs support\n- [ ] `(parallelize ...)` macro to automatically re-write call graphs\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteknql%2Ftapestry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteknql%2Ftapestry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteknql%2Ftapestry/lists"}