{"id":13803836,"url":"https://github.com/dtenny/clj-con","last_synced_at":"2025-05-13T16:32:36.522Z","repository":{"id":77291734,"uuid":"379102625","full_name":"dtenny/clj-con","owner":"dtenny","description":"Clojure-style concurrency operations in Common Lisp","archived":false,"fork":false,"pushed_at":"2024-06-01T22:03:31.000Z","size":56,"stargazers_count":35,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-18T20:49:08.306Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Common Lisp","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/dtenny.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2021-06-22T01:00:58.000Z","updated_at":"2024-10-23T04:02:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"b692d10f-342a-4c1c-a358-6e55f915f5a7","html_url":"https://github.com/dtenny/clj-con","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtenny%2Fclj-con","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtenny%2Fclj-con/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtenny%2Fclj-con/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dtenny%2Fclj-con/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dtenny","download_url":"https://codeload.github.com/dtenny/clj-con/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253981953,"owners_count":21994364,"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-08-04T01:00:38.295Z","updated_at":"2025-05-13T16:32:31.514Z","avatar_url":"https://github.com/dtenny.png","language":"Common Lisp","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# About\n\n`clj-con` defines a set of concurrency operations modeled after their Clojure\ncounterparts. Sample operators include `future`, `promise`, `deref`,\n`deliver`, and `atom`. See the exported symbols in package.lisp for the full list.\n\nOr, if you're familiar with the Clojure Cheatsheet, the project implements the following:\n\n![Cheatsheet Screenshot](https://github.com/dtenny/clj-con/blob/main/Screenshot_20210621_205249.png?raw=true)\n\nalong with `promise` and `deliver`.\n\n## Usage\n\nMar-03-2024: Added to Ultralisp because quicklisp hasn't been updated in 5\nmonths and there are updates I really wanted to get out.\n\nIf you didn't get this via quickload using a quicklisp/ultralisp repo, add it to\nyour `~/quicklisp/localprojects/` directory and update/wipe the\n`system-index.txt` file accordingly, and then you can quickload it.\n\n    ;; See 'local-projects' note in preceding paragraph\n    (ql:quickload :clj-con) ; to use the code\n\nor\n\n    ;; To run the tests\n    (ql:quickload :clj-con-test)\n    (clj-con-test:run-tests)\n\n## Supported Lisps\n\nThis package will wishfully run on any lisp supporting `bordeaux-threads`,\nwhich is most of them. \n\nLisps supporting the `atomics` package will use `compare-and-swap` behavior\nvia `atomics:cas` for the `atom` implementation. At the time\nof this writing (DEC 2023), that includes:\n\n- Allegro\n- CCL\n- ECL\n- LispWorks\n- Mezzano\n- SBCL\n- CMUCL\n- CLASP (*)\n\n*CLASP isn't on the atomics README but does seem to be supported in the code.\n\nIf the atomics-provided `:atomics-cas-svref` isn't in `*features*` then the\nimplementation defaults to a locking behavior to emulate compare-and-swap\nin the various `atom` functions that require it.\n\n## Tested Lisps\n\nHere are my experiences so far with Fedora 38 running on an Intel machine\nand tests run on some of the lisps.  Note that I am primarily an SBCL user\nand am not particularly familiar with the other lisps, their heap\nconfigurations, or even how to debug them since I didn't bother to enable\nthem for SLIME, I just ran them from the command line.\n\nAll tests were run with default memory configurations. I have no explanation\nfor why some of them seem to be running out of memory, though some may be\nrunning with overly conservative heap sizes by default.\nThe test suite allocates fewer than 50 threads, but the CLJ-CON package\ndoes expect threads to be (eventually) reclaimed when the lisp code running\non them returns.\n\nGiven that the test suite does deliberately signal conditions in the bodies\nof many thread tests I suppose it's possible threads are hung and locks\nare not being released.  There are caveats about unpredicable unwind\nbehavior w.r.t. locks in some of the tools used.\n\nAll locking is done via `bt2:with-lock-held`.  If you want lisps that seem\nto keep chugging along even with many allocations, look for the ones I've\nlabelled \"GOOD\".\n\n- SBCL                                                    GOOD\n\n  RUN-TESTS passes. \n  `(dotimes (i 1000) (debug! 'test-suite))` passes.\n\n- CCL                                                     GOOD\n\n  RUN-TESTS passes. \n  `(dotimes (i 1000) (debug! 'test-suite))` passes.\n\n  The test seemed to slow a bit toward the end of the 1000 iterations, as\n  if perhaps a lot of gc activity were happening, but it did finish.\n\n- ABCL 1.9.0, OpenJDK 17.0.9                              GOOD (No CAS)\n\n  RUN-TESTS passes. \n  `(dotimes (i 1000) (debug! 'test-suite))` passes.\n\n  This now works but required special timeout logic because\n  BT2:CONDITION-WAIT _always_ returns T on ABCL.  Unfortunatey the logic\n  which fixes ABCL breaks tests on CCL, and perhaps others.\n\n- LispWorks 8.0.1 Personal Edition.                      UNRELIABLE\n\n  RUN-TESTS passes. \n  `(dotimes (i 1000) (debug! 'test-suite))` runs out of memory.\n  Lots of messages on the console \"Hanging Unknown thread 5612\"\n\n  On a personal note. No init file with personal edition. Really?\n\n- Allegro CL Express 11.0 (`alisp` executable)           UNRELIABLE\n\n  RUN-TESTS passes. \n  `(dotimes (i 1000) (debug! 'test-suite))` gets the following \n  error after a number of iterations:\n\n    Running test suite TEST-SUITE\n     Running test PROMISE-DELIVERY ....\n     Running test NO-TIMEOUT-WAITS Allegro CL(pid 1082089): System Error (gsgc) Object already pointing to target newspace half: 0x1000c8a1a68\n    The internal data structures in the running Lisp image have been\n    corrupted and execution cannot continue.  Check all foreign functions\n    and any Lisp code that was compiled with high speed and/or low safety,\n    as these are two common sources of this failure.  If you cannot find\n    anything incorrect in your code you should contact technical support\n    for Allegro Common Lisp, and we will try to help determine whether\n    this is a coding error or an internal bug.\n\n  The message suggests a gc bug, but maybe that's just a symptom of running\n  out of memory.\n\n- ECL 21.2.1                                            UNRELIABLE\n\n  Works for the minimal (run once) `clj-con-test:run-tests` case, but\n  runs out of memory if the test suite is run repeatedly.\n\n\n## V1.0.0, possible breaking changes\n\n1. `compare-and-set!` now returns NIL and non-NIL, instead of\n   strict NIL and T values. \n2. `deliver` no longer returns the value delivered, it returns the input promise\n   or nil according to clojure semantics, see the doc string for `deliver`.\n\n## Changelog\n\n### v1.0.0 \n\n#### Tested and fixed for multiple platforms.\n\nSee \"Tested Lisps\" above.\n\n#### Add support for compare-and-swap\n\nAdded conditional use of the `atomics` package for a real compare-and-swap\nbehavior in the `atom` implementation.\n\n#### Eliminate use of recursive locks used with condition-variables (ECL fix)\n\nRecent testing with ECL found that ECL doesn't like condition broadcasts with\nrecursive locks. The recursive locks were changed to non-recursive locks,\nhopefully without loss of functionality or introduction of bugs.\n\n#### Migration to Bordeaux-Threads APIV2\n\nThe motiviation was to use `CONDITION-BROADCAST` which is not in APIV1\nand was forcing CLJ-CON code to loop on `CONDITION-NOTIFY`.\n\n### v0.1.0 - initial bordeaux-threads implementation\n\nOnly tested with SBCL and ABCL, known to be broken on ECL.\n\n## Differences from Clojure\n\n### Uses of multiple value return\n\n`reset-vals!` and `swap-vals!` return vectors in Clojure but return\nmultiple values here. I couldn't see the point of returning vectors when CL\nhas no destructuring bind that works on vectors. At least you can use\nmultiple-value-bind if you want, though it doesn't destructure either.\n\nTip: [metabang-bind](https://github.com/hraban/metabang-bind) (available in\nquicklisp) provides a nice destructuring tool that also handles multiple\nvalues.\n\n### Character/number EQ is not identical to Java's `==` used by Clojure\n\nYMMV if you use the atomics-enabled `compare-and-swap` behavior on Common\nLisp characters and numbers, because it uses `EQ` semantics, not `EQL`, and \nEQ is not necessarily true for numbers and characters.\n\nIn SBCL, fixnums are usually EQ, and `(eq #\\a #\\a)` will likely return\ntrue, but have a care. \n\n## `atom` package conflict\n\nIf you're going to `(use :clj-con)` note that `atom` requires a\n`(:shadowing-import-from #:clj-con #:atom)`.\n\n## Use of `interrupt-thread` by `future-cancel`\n\nThe Java Virtual Machine's threading tools are really a marvelous thing.  If\nyou've been in that ecosystem a long time, going back to pthreads with some of\nits limitations (or lisp oddities built on them), will feel fragile, and\nreading the various SBCL source comments on `interrupt-thread` doesn't do much\nto prevent that feeling.\n\nThe test suite does test `future-cancel` and other ways of unwinding the\nthread stack, and seems to work on all tested platforms.  But it may still be a\nsource of bugs, such as the memory problems noted on some lisps.\n\nHave a care if you are repeatedly interrupting threads or using complicated mission\ncritical handlers in the threads unless you have taken to heart the use of\nSBCL's WITHOUT-INTERRUPTS and other appropriate implementation dependent\ntools. I didn't hit any problems with my simple tests but that isn't saying\nmuch.\n\n## Non-Goals\n\nThere is no attempt here to bring clojure syntax or persistent data structures to\nCommon Lisp.  Fortunately neither of those things is particularly prevalent in\nClojure's concurrency operator model, at least not in the clojure.core\nnamespace. \n\nSome enterprising person might want to make a readtable that maps `@` to\n`deref`, assuming it doesn't conflict with `,@`, but that hasn't been done\nhere so you'll just have to call `deref`.\n\n## Blocking Queues?\n\nIf you're missing clojure.core.async and want some blocking queues for producer/consumer\nsituations, take a look at the `lparallel.queue` package `(ql:quickload\n:lparallel)`. Unlike clojure.core.async it has a `peek` operator which I find useful\nwhen I need to speculatively try something on a queue element without losing FIFO ordering.\n\nThe Atomics maintainers were considering adding some queue capabilities in\n2023, so you may wish to check there as well. It isn't in the quicklisp\ndistribution as of June 2023 though.\n\n## Cautionary note for Clojure devs new to Common Lisp\n\nI recommend reading documentation on the bordeaux-threads \n[make-thread](https://sionescu.github.io/bordeaux-threads/threads/make-thread/) \nfunction for cautions about interactions between threads and dynamic variables.\n\nYou also need to mentally prepare yourself for how values you've closed\nover in the body of your `future` can mutate.  Consider this example:\n\n    (dotimes (i 20)\n      (future ... (print i) ...))\n\nYou may be expecting the first value of `i` printed by the first future created\nwould be zero because dotimes starts at zero.  However depending on your lisp\nimplementation it may actually print one, or some other value, depending on\ntime of evaluation and whether the reference to the location/register\nholding 'i' has been incremented or not by the time the future body is\nexecuted on the new thread.\n\nThe `clj-con-test` package has a test case where this exact issue was encountered on\nSBCL, and the workaround was to use something like this:\n\n    (dotimes (i 20)\n      (let ((i2 i))          ;of course I could have rebound 'i' as well\n        (future ... (print i2) ...)))\n\nThis way the future is referencing a binding that won't change.\n\nBinding semantics such as the above may vary by lisp implementation and has\nnothing to do with parallelism.  E.g., you _might_ get this:\n\n    (let ((funs nil)) \n      (dotimes (i 3) (push (lambda () i) funs))\n      (dotimes (j 3) (print (funcall (elt funs j)))))\n    3\n    3\n    3\n\nThe behavior is related to closing over bindings for mutable data.\nThe CL spec for [dotimes](http://clhs.lisp.se/Body/m_dotime.htm) says this:\n\n    \"It is implementation-dependent whether dotimes establishes a new binding\n    of var on each iteration or whether it establishes a binding for var once\n    at the beginning and then assigns it on any subsequent iterations.\"\n\nWhen in doubt, add a binding that won't change for use in your closed over\n`future` (or other) bodies.\n\n## Feedback welcome\n\n`(reverse \"moc.liamg@ynnet.evad\")`\n\nThis is a secondary address that isn't monitored every day.\nFeel free to submit Github issues if appropriate.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdtenny%2Fclj-con","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdtenny%2Fclj-con","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdtenny%2Fclj-con/lists"}