{"id":18850131,"url":"https://github.com/clj-commons/dirigiste","last_synced_at":"2025-12-12T01:33:16.667Z","repository":{"id":19502796,"uuid":"22749260","full_name":"clj-commons/dirigiste","owner":"clj-commons","description":"centrally-planned object and thread pools","archived":false,"fork":false,"pushed_at":"2024-10-16T05:58:53.000Z","size":3357,"stargazers_count":207,"open_issues_count":2,"forks_count":17,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-14T03:11:23.506Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","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/clj-commons.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.adoc","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-08-08T07:14:45.000Z","updated_at":"2025-01-21T09:54:21.000Z","dependencies_parsed_at":"2024-06-21T14:14:28.340Z","dependency_job_id":"8808c924-013e-40d0-b279-edbc3bb14671","html_url":"https://github.com/clj-commons/dirigiste","commit_stats":{"total_commits":92,"total_committers":15,"mean_commits":6.133333333333334,"dds":0.3695652173913043,"last_synced_commit":"e18f94e50f286c6614ffacc25607164bcbba57a7"},"previous_names":["ztellman/dirigiste"],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fdirigiste","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fdirigiste/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fdirigiste/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fdirigiste/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clj-commons","download_url":"https://codeload.github.com/clj-commons/dirigiste/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254264769,"owners_count":22041794,"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":[],"created_at":"2024-11-08T03:27:55.357Z","updated_at":"2025-12-12T01:33:16.618Z","avatar_url":"https://github.com/clj-commons.png","language":"Java","funding_links":[],"categories":["并发编程"],"sub_categories":[],"readme":"[![Clojars Project](https://img.shields.io/clojars/v/org.clj-commons/dirigiste.svg)](https://clojars.org/org.clj-commons/dirigiste)\n[![CircleCI](https://circleci.com/gh/clj-commons/dirigiste.svg?style=svg)](https://circleci.com/gh/clj-commons/dirigiste)\n![](docs/cybersyn.jpg)\n\n(pronounced deer-eh-jeest)\n\nIn the default JVM thread pools, once a thread is created it will only be retired when it hasn't performed a task in the last minute.  In practice, this means that there are as many threads as the peak historical number of concurrent tasks handled by the pool, forever.  These thread pools are also poorly instrumented, making it difficult to tune their latency or throughput.\n\nDirigiste provides a fast, richly instrumented version of a `java.util.concurrent.ExecutorService`, and provides a means to feed that instrumentation into a control mechanism that can grow or shrink the pool as needed.  Default implementations that optimize the pool size for thread utilization are provided.\n\nIt also provides an object pool mechanism that uses a similar feedback mechanism to resize itself, and is significantly simpler than the [Apache Commons object pool implementation](http://commons.apache.org/proper/commons-pool/).\n\nJavadocs can be found [here](https://clj-commons.github.io/dirigiste/).\n\n### usage\n\nIn Leiningen:\n\n```clojure\n[org.clj-commons/dirigiste \"1.0.3\"]\n```\n\nIn Maven:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eio.aleph\u003c/groupId\u003e\n  \u003cartifactId\u003edirigiste\u003c/artifactId\u003e\n  \u003cversion\u003e0.1.5\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### executors\n\nUsing the default utilization executor is simple, via [`Executors.utilizationExecutor(...)`](https://clj-commons.org/dirigiste/io/aleph/dirigiste/Executors.html#utilizationExecutor\\(double,int\\)):\n\n```java\nimport io.aleph.dirigiste.Executors;\n\n...\n\nExecutorService e = Executors.utilizationExecutor(0.9, 64);\n```\n\nThis will create an executor which will try to size the pool such that 90% of the threads are active, but will not grow beyond 64 threads.\n\nThis executor exposes [`getStats`](https://clj-commons.org/dirigiste/io/aleph/dirigiste/Executor.html#getStats\\(\\)) and [`getLastStats`](https://clj-commons.org/dirigiste/io/aleph/dirigiste/Executor.html#getLastStats\\(\\)) methods, which can be used to examine the performance characteristics of the executor.  `getLastStats` uses the last value passed to the control loop, so can return immediately.  `getStats` returns the statistics gathered since the last control update, and so may contain 0 or more samples, and requires some amount of computation.\n\nSince instrumentation will cause some small overhead, you may specify which dimensions you wish to collect, via the [`Metric`](https://clj-commons.org/dirigiste/io/aleph/dirigiste/Stats.Metric.html) class.  The possible fields are as follows:\n\n| metric | description |\n|-------|-------------|\n| `QUEUE_LATENCY` | the time spent on the queue for each task, in nanoseconds |\n| `TASK_LATENCY` | the time taken to complete a task, including time spent on the queue, in nanoseconds |\n| `QUEUE_LENGTH` | the length of the queue |\n| `TASK_ARRIVAL_RATE` | the rate of incoming tasks per second |\n| `TASK_COMPLETION_RATE` | the rate of completed tasks per second |\n| `TASK_REJECTION_RATE` | the rate of rejected tasks per second |\n| `UTILIZATION` | the portion of threads which are active, from 0 to 1 |\n\nThese metrics are surfaced via the [`Stats`](https://clj-commons.org/dirigiste/io/aleph/dirigiste/Stats.html) class, which provides `getMean...()` and `get...(double quantile)` for each metric.  By default, the utilization executor will only measure utilization, but if we want to get the full range of metrics, we can instantiate it like this:\n\n```java\nExecutors.utilizationExecutor(0.9, 64, EnumSet.allOf(Stats.Metric));\n```\n\nThis will allow us to track metrics which aren't required for the control loop, but are useful elsewhere.\n\n### pools\n\nAll pools are defined via their generator, which is used to create and destroy the pooled objects:\n\n```java\npublic interface Pool.Generator\u003cK,V\u003e {\n  V generate(K key) throws Exception;\n  void destroy(K key, V val);\n}\n```\n\nAll pooled objects have an associated key.  If objects have no external resources that must be explicitly disposed, `destroy` can be a no-op.\n\nObject pools have three major functions, `acquire`, `release`, and `dispose`.  Typically, objects will be taken out of the pool via `acquire`, and returned back via `release` once they've served their purpose:\n\n```java\npool = Pools.utilizationPool(generator, 0.9, 4, 1024);\nObject obj = pool.acquire(\"foo\");\nuseObject(obj);\npool.release(\"foo\", obj);\n```\n\nHowever, if the object has expired, we can `dispose` of it:\n\n```java\npool.dispose(\"foo\", obj);\n```\n\nA pooled object can be disposed of at any time, without having first been acquired.\n\nTo support non-blocking code, we may also acquire an object via a callback mechanism:\n\n```java\npool.acquire(\"foo\",\n  new AcquireCallback() {\n        public void handleObject(Object obj) {\n                useObject(obj);\n                pool.release(\"foo\", obj);\n        }\n});\n```\n\n### creating a custom controller\n\nThe [`Executor.Controller`](https://clj-commons.org/dirigiste/io/aleph/dirigiste/Executor.Controller.html) interface is fairly straightforward:\n\n```java\npublic interface Executor.Controller {\n    boolean shouldIncrement(int currThreads);\n    int adjustment(Stats stats);\n}\n```\n\nThe first method, `shouldIncrement`, controls whether a new thread should be spun up.  This means that the thread limit can be dynamic, for instance dependent on the available memory.  This method will be called whenever `adjustment` calls for more threads, or when a task is unable to be added to the queue.\n\nThe second method, `adjustment`, takes a `Stats` object, and returns a number representing how the pool size should be adjusted.  The frequency with which `adjustment` is called is dictated by the `controlPeriod` parameter to the [`Executor`](https://clj-commons.org/dirigiste/io/aleph/dirigiste/Executor.html#%3Cinit%3E\\(java.util.concurrent.ThreadFactory,java.util.concurrent.BlockingQueue,io.aleph.dirigiste.Executor.Controller,int,java.util.EnumSet,long,long,java.util.concurrent.TimeUnit\\)) constructor, and the number of samples in the `Stats` object is controlled by the `samplePeriod` parameter.\n\nThe utilization controller is quite simple:\n\n```java\nExecutor.Controller utilizationController(final double targetUtilization, final int maxThreadCount) {\n  return new Controller() {\n    public boolean shouldIncrement(int numWorkers) {\n      return numWorkers \u003c maxThreadCount;\n    }\n\n    public int adjustment(Stats stats) {\n      double correction = stats.getUtilization(0.9) / targetUtilization;\n      return (int) Math.ceil(stats.getNumWorkers() * correction) - stats.getNumWorkers();\n    }\n  };\n}\n```\n\nIt adjusts the number of threads using the `targetUtilization` compared against the 90th percentile measured utilization over the last `controlPeriod`.  Obviously more sophisticated methods are possible, but they're left as an exercise for the reader.\n\n[`IPool.Controller`](https://clj-commons.org/dirigiste/io/aleph/dirigiste/IPool.Controller.html) works much the same, except that `adjustment` takes a `Map` of keys onto `Stats` objects, and returns a `Map` of keys onto `Integer` objects.  The utilization controller is otherwise much the same:\n\n```java\npublic IPool.Controller utilizationController(final double targetUtilization, final int maxObjectsPerKey, final int maxTotalObjects) {\n\n  return new IPool.Controller() {\n    public boolean shouldIncrement(Objec t key, int objectsForKey, int totalObjects) {\n      return (objectsForKey \u003c maxObjectsPerKey) \u0026\u0026 (totalObjects \u003c maxTotalObjects);\n    }\n\n    public Map adjustment(Map\u003cK, Stats\u003e stats) {\n      Map adj = new HashMap();\n\n      for ( e : stats.entrySet()) {\n        Map.Entry entry = (Map.Entry) e;\n        Stats s = (Stats) entry.getValue();\n        int numWorkers = s.getNumWorkers();\n        double correction = s.getUtilization(0.9) / targetUtilization;\n        int n = (int) Math.ceil(s.getNumWorkers() * correction) - numWorkers;\n\n        adj.put(entry.getKey(), new Integer(n));\n      }\n\n      return adj;\n    }\n  };\n}\n```\n\n### license\n\nCopyright © 2015 Zachary Tellman\n\nDistributed under the MIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclj-commons%2Fdirigiste","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclj-commons%2Fdirigiste","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclj-commons%2Fdirigiste/lists"}