Skip to content

Commit 78add58

Browse files
committed
Merge branch '1.7-dev'
2 parents 88a7971 + d292e0e commit 78add58

File tree

13 files changed

+167
-104
lines changed

13 files changed

+167
-104
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ Copyright © Rich Hickey and contributors
6565

6666
## Changelog
6767

68+
* next
69+
* [ASYNC-256](https://clojure.atlassian.net/browse/ASYNC-256) (CLJ) Add io-thread and System property clojure.core.async.executor-factory
70+
* [ASYNC-255](https://clojure.atlassian.net/browse/ASYNC-255) (CLJ) alts guards against put of nil message on entry
71+
* Update tools.analyzer.jvm to 1.3.2
6872
* Release 1.7.701 on 2024.12.17
6973
* [ASYNC-254](https://clojure.atlassian.net/browse/ASYNC-254) (CLJ) Completions for blocking ops can be directly delivered without enqueuing for dispatch
7074
* [ASYNC-252](https://clojure.atlassian.net/browse/ASYNC-252) (CLJ) Move go expander from ioc-macros to impl.go namespace

VERSION_TEMPLATE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.7.GENERATED_VERSION
1+
1.8.GENERATED_VERSION-beta1

build.clj

+1-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
(b/delete {:path "target"})
1111
(b/compile-clj {:basis basis, :src-dirs ["src/main/clojure"], :class-dir class-dir,
1212
:filter-nses '[clojure.core.async]
13-
:ns-compile '[clojure.core.async.impl.exec.threadpool
14-
clojure.core.async.impl.protocols
13+
:ns-compile '[clojure.core.async.impl.protocols
1514
clojure.core.async.impl.mutex
16-
clojure.core.async.impl.concurrent
1715
clojure.core.async.impl.dispatch
1816
clojure.core.async.impl.ioc-macros
1917
clojure.core.async.impl.buffers

deps.edn

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{:paths ["src/main/clojure"]
22
:deps
3-
{org.clojure/tools.analyzer.jvm {:mvn/version "1.3.1"}}
3+
{org.clojure/tools.analyzer.jvm {:mvn/version "1.3.2"}}
44
:aliases
55
{:cljs-test {:extra-deps {org.clojure/clojurescript {:mvn/version "1.11.132"}}
66
:extra-paths ["src/main/clojure/cljs" "src/test/cljs"]}

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
<dependency>
4545
<groupId>org.clojure</groupId>
4646
<artifactId>tools.analyzer.jvm</artifactId>
47-
<version>1.3.1</version>
47+
<version>1.3.2</version>
4848
</dependency>
4949
</dependencies>
5050

project.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
:url "http://www.eclipse.org/legal/epl-v10.html"}
66
:parent [org.clojure/pom.contrib "1.2.0"]
77
:dependencies [[org.clojure/clojure "1.11.4"]
8-
[org.clojure/tools.analyzer.jvm "1.3.1"]
8+
[org.clojure/tools.analyzer.jvm "1.3.2"]
99
[org.clojure/clojurescript "1.11.132" :scope "provided"]]
1010
:global-vars {*warn-on-reflection* true}
1111
:source-paths ["src/main/clojure"]

src/main/clojure/cljs/core/async.cljs

+6
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@
180180
(let [flag (alt-flag)
181181
ports (vec ports) ;; ensure vector for indexed nth
182182
n (count ports)
183+
_ (loop [i 0] ;; check for invalid write op
184+
(when (< i n)
185+
(let [port (nth ports i)]
186+
(when (vector? port)
187+
(assert (some? (port 1)) "can't put nil on channel")))
188+
(recur (unchecked-inc i))))
183189
idxs (random-array n)
184190
priority (:priority opts)
185191
ret

src/main/clojure/clojure/core/async.clj

+60-20
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,33 @@ to validate go blocks do not invoke core.async blocking operations.
1818
Property is read once, at namespace load time. Recommended for use
1919
primarily during development. Invalid blocking calls will throw in
2020
go block threads - use Thread.setDefaultUncaughtExceptionHandler()
21-
to catch and handle."
21+
to catch and handle.
22+
23+
Use the Java system property `clojure.core.async.executor-factory`
24+
to specify a function that will provide ExecutorServices for
25+
application-wide use by core.async in lieu of its defaults. The
26+
property value should name a fully qualified var. The function
27+
will be passed a keyword indicating the context of use of the
28+
executor, and should return either an ExecutorService, or nil to
29+
use the default. Results per keyword will be cached and used for
30+
the remainder of the application. Possible context arguments are:
31+
32+
:io - used in async/io-thread, for :io workloads in flow/process,
33+
and for dispatch handling if no explicit dispatch handler is
34+
provided (see below)
35+
36+
:mixed - used by async/thread and for :mixed workloads in
37+
flow/process
38+
39+
:compute - used for :compute workloads in flow/process
40+
41+
:core-async-dispatch - used for completion fn handling (e.g. in put!
42+
and take!, as well as go block IOC thunk processing) throughout
43+
core.async. If not supplied the ExecutorService for :io will be
44+
used instead.
45+
46+
The set of contexts may grow in the future so the function should
47+
return nil for unexpected contexts."
2248
(:refer-clojure :exclude [reduce transduce into merge map take partition
2349
partition-by bounded-count])
2450
(:require [clojure.core.async.impl.protocols :as impl]
@@ -29,7 +55,6 @@ to catch and handle."
2955
[clojure.core.async.impl.ioc-macros :as ioc]
3056
clojure.core.async.impl.go ;; TODO: make conditional
3157
[clojure.core.async.impl.mutex :as mutex]
32-
[clojure.core.async.impl.concurrent :as conc]
3358
)
3459
(:import [java.util.concurrent.atomic AtomicLong]
3560
[java.util.concurrent.locks Lock]
@@ -281,6 +306,12 @@ to catch and handle."
281306
(let [flag (alt-flag)
282307
ports (vec ports) ;; ensure vector for indexed nth
283308
n (count ports)
309+
_ (loop [i 0] ;; check for invalid write op
310+
(when (< i n)
311+
(let [port (nth ports i)]
312+
(when (vector? port)
313+
(assert (some? (port 1)) "can't put nil on channel")))
314+
(recur (unchecked-inc i))))
284315
^ints idxs (random-array n)
285316
priority (:priority opts)
286317
ret
@@ -463,33 +494,42 @@ to catch and handle."
463494
[& body]
464495
(#'clojure.core.async.impl.go/go-impl &env body))
465496

466-
(defonce ^:private ^Executor thread-macro-executor
467-
(Executors/newCachedThreadPool (conc/counted-thread-factory "async-thread-macro-%d" true)))
468-
469497
(defn thread-call
470498
"Executes f in another thread, returning immediately to the calling
471499
thread. Returns a channel which will receive the result of calling
472-
f when completed, then close."
473-
[f]
474-
(let [c (chan 1)]
475-
(let [binds (Var/getThreadBindingFrame)]
476-
(.execute thread-macro-executor
477-
(fn []
478-
(Var/resetThreadBindingFrame binds)
479-
(try
480-
(let [ret (f)]
481-
(when-not (nil? ret)
482-
(>!! c ret)))
483-
(finally
484-
(close! c))))))
485-
c))
500+
f when completed, then close. workload is a keyword that describes
501+
the work performed by f, where:
502+
503+
:io - may do blocking I/O but must not do extended computation
504+
:compute - must not ever block
505+
:mixed - anything else (default)
506+
507+
when workload not supplied, defaults to :mixed"
508+
([f] (thread-call f :mixed))
509+
([f workload]
510+
(let [c (chan 1)
511+
returning-to-chan (fn [bf]
512+
#(try
513+
(when-some [ret (bf)]
514+
(>!! c ret))
515+
(finally (close! c))))]
516+
(-> f bound-fn* returning-to-chan (dispatch/exec workload))
517+
c)))
486518

487519
(defmacro thread
488520
"Executes the body in another thread, returning immediately to the
489521
calling thread. Returns a channel which will receive the result of
490522
the body when completed, then close."
491523
[& body]
492-
`(thread-call (^:once fn* [] ~@body)))
524+
`(thread-call (^:once fn* [] ~@body) :mixed))
525+
526+
(defmacro io-thread
527+
"Executes the body in a thread, returning immediately to the calling
528+
thread. The body may do blocking I/O but must not do extended computation.
529+
Returns a channel which will receive the result of the body when completed,
530+
then close."
531+
[& body]
532+
`(thread-call (^:once fn* [] ~@body) :io))
493533

494534
;;;;;;;;;;;;;;;;;;;; ops ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
495535

src/main/clojure/clojure/core/async/impl/concurrent.clj

-38
This file was deleted.

src/main/clojure/clojure/core/async/impl/dispatch.clj

+66-5
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,45 @@
88

99
(ns ^{:skip-wiki true}
1010
clojure.core.async.impl.dispatch
11-
(:require [clojure.core.async.impl.protocols :as impl]
12-
[clojure.core.async.impl.exec.threadpool :as tp]))
11+
(:require [clojure.core.async.impl.protocols :as impl])
12+
(:import [java.util.concurrent Executors ExecutorService ThreadFactory]))
1313

1414
(set! *warn-on-reflection* true)
1515

1616
(defonce ^:private in-dispatch (ThreadLocal.))
1717

18-
(defonce executor
19-
(delay (tp/thread-pool-executor #(.set ^ThreadLocal in-dispatch true))))
18+
(defonce executor nil)
19+
20+
(defn counted-thread-factory
21+
"Create a ThreadFactory that maintains a counter for naming Threads.
22+
name-format specifies thread names - use %d to include counter
23+
daemon is a flag for whether threads are daemons or not
24+
opts is an options map:
25+
init-fn - function to run when thread is created"
26+
([name-format daemon]
27+
(counted-thread-factory name-format daemon nil))
28+
([name-format daemon {:keys [init-fn] :as opts}]
29+
(let [counter (atom 0)]
30+
(reify
31+
ThreadFactory
32+
(newThread [_this runnable]
33+
(let [body (if init-fn
34+
(fn [] (init-fn) (.run ^Runnable runnable))
35+
runnable)
36+
t (Thread. ^Runnable body)]
37+
(doto t
38+
(.setName (format name-format (swap! counter inc)))
39+
(.setDaemon daemon))))))))
40+
41+
(defonce
42+
^{:doc "Number of processors reported by the JVM"}
43+
processors (.availableProcessors (Runtime/getRuntime)))
44+
45+
(def ^:private pool-size
46+
"Value is set via clojure.core.async.pool-size system property; defaults to 8; uses a
47+
delay so property can be set from code after core.async namespace is loaded but before
48+
any use of the async thread pool."
49+
(delay (or (Long/getLong "clojure.core.async.pool-size") 8)))
2050

2151
(defn in-dispatch-thread?
2252
"Returns true if the current thread is a go block dispatch pool thread"
@@ -37,9 +67,40 @@
3767
(.uncaughtException (Thread/currentThread) ex))
3868
nil)
3969

70+
(defn- make-ctp-named
71+
[workflow]
72+
(Executors/newCachedThreadPool (counted-thread-factory (str "async-" (name workflow) "-%d") true)))
73+
74+
(defn ^:private create-default-executor
75+
[workload]
76+
(case workload
77+
:compute (make-ctp-named :compute)
78+
:io (make-ctp-named :io)
79+
:mixed (make-ctp-named :mixed)))
80+
81+
(def executor-for
82+
"Given a workload tag, returns an ExecutorService instance and memoizes the result. By
83+
default, core.async will defer to a user factory (if provided via sys prop) or construct
84+
a specialized ExecutorService instance for each tag :io, :compute, and :mixed. When
85+
given the tag :core-async-dispatch it will default to the executor service for :io."
86+
(memoize
87+
(fn ^ExecutorService [workload]
88+
(let [sysprop-factory (when-let [esf (System/getProperty "clojure.core.async.executor-factory")]
89+
(requiring-resolve (symbol esf)))
90+
sp-exec (and sysprop-factory (sysprop-factory workload))]
91+
(or sp-exec
92+
(if (= workload :core-async-dispatch)
93+
(executor-for :io)
94+
(create-default-executor workload)))))))
95+
96+
(defn exec
97+
[^Runnable r workload]
98+
(let [^ExecutorService e (executor-for workload)]
99+
(.execute e r)))
100+
40101
(defn run
41102
"Runs Runnable r on current thread when :on-caller? meta true, else in a thread pool thread."
42103
[^Runnable r]
43104
(if (-> r meta :on-caller?)
44105
(try (.run r) (catch Throwable t (ex-handler t)))
45-
(impl/exec @executor r)))
106+
(exec r :core-async-dispatch)))

src/main/clojure/clojure/core/async/impl/exec/threadpool.clj

-32
This file was deleted.

src/test/clojure/clojure/core/async/concurrent_test.clj

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88

99
(ns clojure.core.async.concurrent-test
1010
(:require [clojure.test :refer :all]
11-
[clojure.core.async.impl.concurrent :as conc])
11+
[clojure.core.async.impl.dispatch :as dispatch])
1212
(:import [java.util.concurrent ThreadFactory]))
1313

1414
(deftest test-counted-thread-factory
1515
(testing "Creates numbered threads"
16-
(let [^ThreadFactory factory (conc/counted-thread-factory "foo-%d" true)
16+
(let [^ThreadFactory factory (dispatch/counted-thread-factory "foo-%d" true)
1717
threads (repeatedly 3 #(.newThread factory (constantly nil)))]
1818
(is (= ["foo-1" "foo-2" "foo-3"] (map #(.getName ^Thread %) threads))))))
1919

src/test/clojure/clojure/core/async_test.clj

+24
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,23 @@
185185
(binding [test-dyn true]
186186
(is (<!! (thread test-dyn))))))
187187

188+
(deftest io-thread-tests
189+
(testing "io-thread blocking ops"
190+
(let [c1 (chan)
191+
c2 (chan)
192+
c3 (chan)]
193+
(io-thread (>!! c2 (clojure.string/upper-case (<!! c1))))
194+
(io-thread (>!! c3 (clojure.string/reverse (<!! c2))))
195+
(>!! c1 "loop")
196+
(is (= "POOL" (<!! c3)))))
197+
(testing "io-thread parking op should fail"
198+
(let [c1 (chan)]
199+
(io-thread
200+
(try
201+
(>! c1 :no)
202+
(catch AssertionError _
203+
(>!! c1 :yes))))
204+
(is (= :yes (<!! c1))))))
188205

189206
(deftest ops-tests
190207
(testing "map<"
@@ -465,3 +482,10 @@
465482
:ok
466483
(catch AssertionError e
467484
:ko))))))
485+
486+
(deftest test-alts-put-nil-invalid
487+
(is
488+
(thrown? AssertionError
489+
(let [c1 (a/chan)
490+
c2 (a/chan)]
491+
(a/alts!! [c1 [c2 nil]])))))

0 commit comments

Comments
 (0)