@@ -33,9 +33,10 @@ to catch and handle."
3333 )
3434 (:import [java.util.concurrent.atomic AtomicLong]
3535 [java.util.concurrent.locks Lock]
36- [java.util.concurrent Executors Executor ThreadLocalRandom]
36+ [java.util.concurrent Executors Executor ThreadLocalRandom ExecutorService ]
3737 [java.util Arrays ArrayList]
38- [clojure.lang Var]))
38+ [clojure.lang Var]
39+ [java.lang Thread$Builder]))
3940
4041(alias 'core 'clojure.core)
4142
@@ -465,6 +466,37 @@ to catch and handle."
465466(defonce ^:private ^Executor thread-macro-executor
466467 (Executors/newCachedThreadPool (conc/counted-thread-factory " async-thread-macro-%d" true )))
467468
469+ (def ^ExecutorService io-thread-exec
470+ (if (= " 21" (System/getProperty " java.vm.specification.version" ))
471+ (eval '(Executors/newThreadPerTaskExecutor (-> (Thread/ofVirtual )
472+ (Thread$Builder/.name " io-thread-" 0 )
473+ .factory)))
474+ thread-macro-executor))
475+
476+ (defmacro io-thread
477+ " Asynchronously executes the body in a virtual thread, returning immediately
478+ to the calling thread.
479+
480+ io-thread blocks should not (either directly or indirectly) perform operations
481+ that may block indefinitely. Doing so risks pinning the virtual thread
482+ to its carrier thread.
483+
484+ Returns a channel which will receive the result of the body when
485+ completed"
486+ [& body]
487+ `(let [c# (chan 1 )
488+ captured-bindings# (Var/getThreadBindingFrame )]
489+ (.execute
490+ io-thread-exec
491+ (^:once fn* []
492+ (Var/resetThreadBindingFrame captured-bindings#)
493+ (try
494+ (let [result# (do ~@body)]
495+ (>!! c# result#))
496+ (finally
497+ (close! c#)))))
498+ c#))
499+
468500(defn thread-call
469501 " Executes f in another thread, returning immediately to the calling
470502 thread. Returns a channel which will receive the result of calling
0 commit comments