{"id":19345919,"url":"https://github.com/tolitius/lasync","last_synced_at":"2025-04-23T04:36:36.430Z","repository":{"id":9676550,"uuid":"11619922","full_name":"tolitius/lasync","owner":"tolitius","description":"making executor service tougher","archived":false,"fork":false,"pushed_at":"2023-09-13T21:22:59.000Z","size":46,"stargazers_count":43,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-02T08:22:51.290Z","etag":null,"topics":["back-pressure","clojure","concurrency","java"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tolitius.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-07-23T21:39:43.000Z","updated_at":"2024-05-31T07:54:59.000Z","dependencies_parsed_at":"2022-08-25T23:32:19.483Z","dependency_job_id":null,"html_url":"https://github.com/tolitius/lasync","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Flasync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Flasync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Flasync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Flasync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tolitius","download_url":"https://codeload.github.com/tolitius/lasync/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250372496,"owners_count":21419719,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["back-pressure","clojure","concurrency","java"],"created_at":"2024-11-10T04:08:21.877Z","updated_at":"2025-04-23T04:36:36.094Z","avatar_url":"https://github.com/tolitius.png","language":"Clojure","readme":"## limited async\n\nan executor service (a.k.a. smart pool of threads) that is backed by an [ArrayLimitedQueue](src/java/lasync/limitq/ArrayLimitedQueue.java) or a [LinkedLimitedQueue](src/java/lasync/limitq/LinkedLimitedQueue.java).\n\n[![\u003c! release](https://img.shields.io/badge/dynamic/json.svg?label=release\u0026url=https%3A%2F%2Fclojars.org%2Ftolitius%2Flasync%2Flatest-version.json\u0026query=version\u0026colorB=blue)](https://github.com/tolitius/lasync/releases)\n[![\u003c! clojars](https://img.shields.io/clojars/v/tolitius/lasync.svg)](https://clojars.org/tolitius/lasync)\n\n- [why](#why)\n- [how to](#how-to)\n  - [number of threads](#number-of-threads)\n  - [queue size](#queue-size)\n  - [stats](#stats)\n- [show me](#show-me)\n- [tweaking other knobs](#tweaking-other-knobs)\n    - [queue implementation](#queue-implementation)\n    - [thread factory](#thread-factory)\n    - [rejected execution handler](#rejected-execution-handler)\n    - [unDefault it](#undefault-it)\n  - [shut it down](#shut-it-down)\n- [license](#license)\n\n## why\n\nthe purpose of this library is to be able to block on \"`.submit`\" / \"`.execute`\" whenever the q task limit is reached. Here is why..\n\nif a regular [BlockingQueue](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html) is used,\na ThreadPoolExecutor calls queue's \"[offer](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html#offer\\(E\\))\"\nmethod which does not block: inserts a task and returns true, or returns false in case a queue is \"capacity-restricted\" and its capacity was reached.\n\nwhile this behavior is useful, there are cases where we do need to _block_ and wait until a ThreadPoolExecutor has\na thread available to work on the task.\n\ndepending on a use case this back pressure can be very useful. One reason could be an off heap storage that is being read and processed\nby a ThreadPoolExecutor: e.g. there is no need, and sometimes completely undesired, to use JVM heap for something that is already available off heap.\nAnother good use is described in [\"Creating a NotifyingBlockingThreadPoolExecutor\"](https://web.archive.org/web/20130111220826/https://today.java.net/pub/a/today/2008/10/23/creating-a-notifying-blocking-thread-pool-executor.html).\n\n## how to\n\nto create a pool with limited number of threads and a backing q limit:\n\n```clojure\n(ns sample.project\n  (:require [lasync.core :as lasync]))\n\n(def pool (lasync/pool))\n```\n\nthat is pretty much it. The pool is a regular [ExecutorService](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html) that can have tasks submitted to it:\n\n```clojure\n(.submit pool #(+ 41 1))\n```\n\nthere is also a `submit` function that wraps this call and returns a future:\n\n```clojure\nshow=\u003e (lasync/submit pool #(+ 41 1))\n#object[java.util.concurrent.FutureTask 0x6d1ce6d3 \"java.util.concurrent.FutureTask@6d1ce6d3\"]\n```\n\nas well as an `execute` function that _does not return a future_, hence exeptions will be caught and reported by the default exception handler.\n\n### number of threads\n\nby default lasync will create `available cores * 2 + 42` number of threads:\n\n```clojure\n(defn- number-of-threads []\n  (+ (* 2 available-cores) 42))\n```\n\nbut the number can be changed by:\n\n```clojure\nuser=\u003e (def pool (lasync/pool {:threads 42}))\n#'user/pool\n```\n\n### queue size\n\nthe default queue that is backing lasync's pool is `ArrayLimitedQueue` with a default capacity of `1024` items. But all defaults are there to customize.\nA queue size is what limits the pool _enabling the back pressure_. Use `:limit` to tune that knob:\n\n```clojure\n(def pool (lasync/pool {:limit 65535}))\n```\n\n### stats\n\npool is no good when it is a black box. lasync let's you unbox those stats whenever you need it:\n\n```clojure\nuser=\u003e (lasync/stats pool)\n\n{:largestPoolSize 0,\n :queueCurrentSize 0,\n :activeCount 0,\n :terminating false,\n :poolSize 0,\n :taskCount 0,\n :completedTaskCount 0,\n :class java.util.concurrent.ThreadPoolExecutor,\n :terminated false,\n :keepAliveTimeMs 60000,\n :allowsCoreThreadTimeOut false,\n :corePoolSize 66,\n :maximumPoolSize 66,\n :shutdown false}\n```\n\n## show me\n\nto see lasync in action:\n\n```clojure\nlein repl\n```\n\n```clojure\nuser=\u003e (require '[show :refer [rock-on]])\n```\n\n```clojure\nuser=\u003e (rock-on 69)  ;; Woodstock'69\n```\n\n```\nINFO: pool q-size: 4, submitted: 1\nINFO: pool q-size: 4, submitted: 3\nINFO: pool q-size: 4, submitted: 2\nINFO: pool q-size: 4, submitted: 0\nINFO: pool q-size: 4, submitted: 4\nINFO: pool q-size: 4, submitted: 5\nINFO: pool q-size: 4, submitted: 6\nINFO: pool q-size: 4, submitted: 7\n...\n...\nINFO: pool q-size: 4, submitted: 62\nINFO: pool q-size: 3, submitted: 60\nINFO: pool q-size: 4, submitted: 63\nINFO: pool q-size: 3, submitted: 65\nINFO: pool q-size: 3, submitted: 64\nINFO: pool q-size: 2, submitted: 66\nINFO: pool q-size: 1, submitted: 67\nINFO: pool q-size: 0, submitted: 68\n```\n\nhere lasync show was rocking on 4 core box (which it picked up on), so regardless of how many tasks are being pushed to it,\nthe queue max size always stays at 4, and lasync creates that back pressure in case the task q limit is reached.\nIn fact the \"blocking\" can be seen in action, as each task is sleeping for a second,\nso the whole thing can be visually seen being processed by 4, pause, next 4, pause, etc..\n\nhere is [the code](dev/show.clj) behind the show\n\ndo check out the `(expire-core-threads 69)` and `(use-max-threads 69)` from the examples as well\n\n## tweaking other knobs\n\n#### queue implementation\n\nwhile `ArrayLimitedQueue` fits most of the use cases, a custom, or a different queue can be configured via `:queue`:\n\n```clojure\n(def pool (lasync/pool {:queue (LinkedLimitedQueue. 128)}))\n```\n\n#### thread factory\n\nby default lasync's thread factory tries to have reasonable defaults but if you want to make your it's simply a matter\nof reify'ing an interface.\n\n```clojure\n(def tpool (reify\n             ThreadFactory\n             (newThread [_ runnable] ...)))\n\n(def pool (lasync/pool {:threads 10 :thread-factory tpool}))\n```\n\n#### rejected execution handler\n\nlasync takes an optional `rejected-fn` that will be called on every `RejectedExecutionException`. The default function is:\n\n```clojure\n(defn default-rejected-fn [runnable _]\n  (throw (RejectedExecutionException.\n           (str \"rejected execution: \" runnable))))\n```\n\nbut it can be replaced with a custom one (the second param is an `executor`, it is ignored in this case):\n\n```clojure\n(defn log-rejected [runnable _]\n  (error runnable \"was rejected\"))\n\n(def pool (lasync/pool {:threads 10 :rejected-fn log-rejected}))\n```\n\n#### unDefault it\n\n```clojure\n(def tpool (reify ThreadFactory\n                 (newThread [_ runnable] ...)))\n\n(defn log-rejected [runnable _]\n  (error runnable \"was rejected\"))\n\n(def lp (lasync/pool {:threads 42\n                      :thread-factory tpool\n                      :limit 101010101\n                      :rejected-fn log-rejected}))\n```\n\n### shut it down\n\nwhen you done with a pool it is a good idea to shut it down:\n\n```clojure\n(lasync/shutdown pool)\n```\n\nalso wait for completion of tasks (timeout in ms):\n\n```clojure\n(lasync/await-termination pool 5000)\n```\n\n## license\n\ncopyright © 2021 tolitius\n\ndistributed under the Eclipse Public License, the same as Clojure.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Flasync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftolitius%2Flasync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Flasync/lists"}