- This is an experimentation
Structued concurrency
with JDK-21 preview featureStructured Task Scope
- The purpose is to arrive upon most usable abstraction for
Structured Task Scope
in Clojure - May be released as a library if the usability is established.
In deps.edn
, add following:
fr33m0nk/structured-task-scope {:git/url "https://github.com/fr33m0nk/structured-task-scope"
:sha "0af6062d15228f13a0ba0aa22e696da195dd81aa"}
- ScopedValues are introduced to create differing local bindings for Threads (Virtual or Platform)
- They differ from ThreadLocals as the extent of binding is limited to function invocation and the binding is immutable
- This is also a good alternate to
^:dynamic
Clojure vars - JEP Café #16: Java 20 - From ThreadLocal to ScopedValue is a brilliant reference
In REPL, require fr33m0nk.scoped-value
(require '[fr33m0nk.scoped-value :as sv])
(def a-scoped-value (sv/->scoped-value))
(defn function-using-scope-value
[]
(let [processed-string (str (sv/deref a-scoped-value) " Ok")]
(println processed-string)))
(->> (range 3)
(pmap #(future
(sv/run-where! a-scoped-value (str "Hello - " %) function-using-scope-value)))
(run! deref))
result
;;Obvious jumbling due to multithreading while printing to screen
Hello - 2 OkHello - 0 OkHello - 1 Ok
(defn function-using-scope-value
[]
(str (sv/deref a-scoped-value) " Ok"))
(->> (range 3)
(pmap #(future
(sv/apply-where a-scoped-value (str "Hello - " %) function-using-scope-value)))
(mapv deref))
result
["Hello - 0 Ok" "Hello - 1 Ok" "Hello - 2 Ok"]
In REPL, require fr33m0nk.structured-task-scope
namespace
(require '[fr33m0nk.structured-task-scope :as sts])
(let [success (atom [])
error (atom [])
success-handler #(swap! success conj %)
failure-handler #(swap! error conj %)
scope (sts/->structured-scope success-handler failure-handler)]
(sts/fork-task scope (throw (ex-info "boom" {})))
(sts/fork-task scope (Thread/sleep 5000) :turtle-wins)
(sts/fork-task scope (Thread/sleep 5000) :hare-wins)
(.join scope)
{:success @success
:failure @error})
result
{:success [:hare-wins :turtle-wins],
:failure [#error{:cause "boom",
:data {},
:via [{:type clojure.lang.ExceptionInfo,
:message "boom",
:data {},
:at [user$eval2546$fn__2551 invoke "form-init3588532553786133725.clj" 6]}],
:trace [[user$eval2546$fn__2551 invoke "form-init3588532553786133725.clj" 6]
[clojure.lang.AFn call "AFn.java" 18]
[java.util.concurrent.StructuredTaskScope$SubtaskImpl run "StructuredTaskScope.java" 889]
[java.lang.VirtualThread run "VirtualThread.java" 309]]}]}
JDK-21 currently also supports following refinements of StructuredTaskScope
:
(import '(java.time Instant))
(sts/with-shutdown-on-success
scope
{:deadline-instant (.plusMillis (Instant/now) 1500)}
(sts/fork-task scope (Thread/sleep 1000) :turtle-wins)
(sts/fork-task scope (Thread/sleep 5000) :hare-wins))
result
:turtle-wins
(import '(java.time Instant))
(sts/with-shutdown-on-failure
scope
;; Below are bindings to the tasks once forked
;; These bindings should only be of the forked task as
;; currently the macro isn't very intelligent
[turtle (sts/fork-task scope (Thread/sleep 5000) :turtle-wins)
hare (sts/fork-task scope (Thread/sleep 5000) :hare-wins)
;; forked task bindings can be used inside another forked task
;; using bindings `boomer (.get hare)` here will cause `IllegalStateException`
;; However, such bindings are fine in body of the macro as the body executes after `(.join scope)`
zoomba (sts/fork-task scope (->> [turtle hare]
(map #(name (.get %)))))]
;; Extra options
{:throw-on-failure? true
:deadline-instant (.plusMillis (Instant/now) 7000)}
;; do something with bindings after calling `.get` method on subtasks
(.get zoomba))
result
("turtle-wins" "hare-wins")
let-fork
Evaluates bindings in parallel and returns the result of
evaluating body in the context of those bindings.
Bindings have to be independent of each other
(sts/let-fork [a (let [sleep-ms 5000] (println "Sleep 1 ") (Thread/sleep sleep-ms) (println "Awake 1") :a)
b (let [sleep-ms 2000] (println "Sleep 2 ") (Thread/sleep sleep-ms) (println "Awake 2") 10)]
{a b})
result
Sleep 1
Sleep 2
Awake 2
Awake 1
{:a 10}
Copyright © 2024 Prashant Sinha
Distributed under the Eclipse Public License version 1.0.