@@ -13,11 +13,11 @@ go blocks are dispatched over an internal thread pool, which
13
13
defaults to 8 threads. The size of this pool can be modified using
14
14
the Java system property `clojure.core.async.pool-size`.
15
15
16
- Set Java system property `clojure.core.async.go -checking` to true
17
- to validate go blocks do not invoke core.async blocking operations.
18
- Property is read once, at namespace load time. Recommended for use
19
- primarily during development. Invalid blocking calls will throw in
20
- go block threads - use Thread.setDefaultUncaughtExceptionHandler()
16
+ Set Java system property `clojure.core.async.vthread -checking` to true
17
+ to validate that core.async parking operations are not called outside of
18
+ task blocks. Property is read once, at namespace load time. Recommended
19
+ for use primarily during development. Invalid parking calls will throw in
20
+ virtual threads - use Thread.setDefaultUncaughtExceptionHandler()
21
21
to catch and handle."
22
22
(:refer-clojure :exclude [reduce transduce into merge map take partition
23
23
partition-by bounded-count])
@@ -117,6 +117,23 @@ to catch and handle."
117
117
[^long msecs]
118
118
(timers/timeout msecs))
119
119
120
+ ; ; task
121
+
122
+ (defn- check-not-in-vthread [op]
123
+ (when (not (Thread/.isVirtual (Thread/currentThread )))
124
+ (assert nil (str op " used not in virtual thread" ))))
125
+
126
+ (defmacro defvthreadcheckingop
127
+ [op doc arglist & body]
128
+ (let [as (mapv #(list 'quote %) arglist)]
129
+ `(def ~(with-meta op {:arglists `(list ~as) :doc doc})
130
+ (if (Boolean/getBoolean " clojure.core.async.vthread-checking" )
131
+ (fn ~arglist
132
+ (check-not-in-vthread ~op)
133
+ ~@body)
134
+ (fn ~arglist
135
+ ~@body)))))
136
+
120
137
(defmacro defblockingop
121
138
[op doc arglist & body]
122
139
(let [as (mapv #(list 'quote %) arglist)]
@@ -130,22 +147,21 @@ to catch and handle."
130
147
131
148
(defblockingop <!!
132
149
" takes a val from port. Will return nil if closed. Will block
133
- if nothing is available.
134
- Not intended for use in direct or transitive calls from (go ...) blocks.
135
- Use the clojure.core.async.go-checking flag to detect invalid use (see
136
- namespace docs)."
150
+ if nothing is available."
137
151
[port]
138
152
(let [p (promise )
139
153
ret (impl/take! port (fn-handler (on-caller #(deliver p %))))]
140
154
(if ret
141
155
@ret
142
156
(deref p))))
143
157
144
- (defn <!
145
- " takes a val from port. Must be called inside a (go ...) block. Will
146
- return nil if closed. Will park if nothing is available."
158
+ (defvthreadcheckingop <!
159
+ " takes a val from port. Must be called inside a (task ...) block. Will
160
+ return nil if closed. Will park if nothing is available. Should be used
161
+ inside of a (task ...) block. Use the clojure.core.async.vthread-checking
162
+ flag to detect invalid use (see namespace docs)."
147
163
[port]
148
- (assert nil " <! used not in (go ...) block " ))
164
+ (<!! port ))
149
165
150
166
(defn take!
151
167
" Asynchronously takes a val from port, passing to fn1. Will pass nil
@@ -169,23 +185,22 @@ to catch and handle."
169
185
170
186
(defblockingop >!!
171
187
" puts a val into port. nil values are not allowed. Will block if no
172
- buffer space is available. Returns true unless port is already closed.
173
- Not intended for use in direct or transitive calls from (go ...) blocks.
174
- Use the clojure.core.async.go-checking flag to detect invalid use (see
175
- namespace docs)."
188
+ buffer space is available. Returns true unless port is already closed."
176
189
[port val]
177
190
(let [p (promise )
178
191
ret (impl/put! port val (fn-handler (on-caller #(deliver p %))))]
179
192
(if ret
180
193
@ret
181
194
(deref p))))
182
195
183
- (defn >!
196
+ (defvthreadcheckingop >!
184
197
" puts a val into port. nil values are not allowed. Must be called
185
- inside a (go ...) block. Will park if no buffer space is available.
186
- Returns true unless port is already closed."
198
+ inside a (task ...) block. Will park if no buffer space is available.
199
+ Returns true unless port is already closed. Should be used
200
+ inside of a (task ...) block. Use the clojure.core.async.vthread-checking
201
+ flag to detect invalid use (see namespace docs)."
187
202
[port val]
188
- (assert nil " >! used not in (go ...) block " ))
203
+ (>!! port val ))
189
204
190
205
(defn- nop [_])
191
206
(def ^:private fhnop (fn-handler nop))
@@ -306,20 +321,17 @@ to catch and handle."
306
321
307
322
(defblockingop alts!!
308
323
" Like alts!, except takes will be made as if by <!!, and puts will
309
- be made as if by >!!, will block until completed.
310
- Not intended for use in direct or transitive calls from (go ...) blocks.
311
- Use the clojure.core.async.go-checking flag to detect invalid use (see
312
- namespace docs)."
324
+ be made as if by >!!, will block until completed."
313
325
[ports & opts]
314
326
(let [p (promise )
315
327
ret (do-alts (on-caller #(deliver p %)) ports (apply hash-map opts))]
316
328
(if ret
317
329
@ret
318
330
(deref p))))
319
331
320
- (defn alts!
321
- " Completes at most one of several channel operations. Must be called
322
- inside a (go ...) block. ports is a vector of channel endpoints,
332
+ (defvthreadcheckingop alts!
333
+ " Completes at most one of several channel operations. Should be called
334
+ inside a (task ...) block. ports is a vector of channel endpoints,
323
335
which can be either a channel to take from or a vector of
324
336
[channel-to-put-to val-to-put], in any combination. Takes will be
325
337
made as if by <!, and puts will be made as if by >!. Unless
@@ -341,7 +353,7 @@ to catch and handle."
341
353
depended upon for side effects."
342
354
343
355
[ports & {:as opts}]
344
- (assert nil " alts! used not in (go ...) block " ))
356
+ (alts!! ports opts ))
345
357
346
358
(defn do-alt [alts clauses]
347
359
(assert (even? (count clauses)) " unbalanced clauses" )
@@ -382,16 +394,17 @@ to catch and handle."
382
394
(= ~gch :default ) val#))))
383
395
384
396
(defmacro alt!!
385
- " Like alt!, except as if by alts!!, will block until completed, and
386
- not intended for use in (go ...) blocks."
397
+ " Like alt!, except as if by alts!!, will block until completed."
387
398
388
399
[& clauses]
389
400
(do-alt `alts!! clauses))
390
401
391
402
(defmacro alt!
392
403
" Makes a single choice between one of several channel operations,
393
404
as if by alts!, returning the value of the result expr corresponding
394
- to the operation completed. Must be called inside a (go ...) block.
405
+ to the operation completed. Should be used inside of a (task ...)
406
+ block. Use the clojure.core.async.vthread-checking flag to detect
407
+ invalid use (see namespace docs).
395
408
396
409
Each clause takes the form of:
397
410
@@ -444,23 +457,41 @@ to catch and handle."
444
457
(let [ret (impl/take! port (fn-handler nop false ))]
445
458
(when ret @ret)))
446
459
447
- (defmacro go
448
- " Asynchronously executes the body, returning immediately to the
449
- calling thread. Additionally, any visible calls to <!, >! and alt!/alts!
450
- channel operations within the body will block (if necessary) by
451
- 'parking' the calling thread rather than tying up an OS thread (or
452
- the only JS thread when in ClojureScript). Upon completion of the
453
- operation, the body will be resumed.
454
-
455
- go blocks should not (either directly or indirectly) perform operations
456
- that may block indefinitely. Doing so risks depleting the fixed pool of
457
- go block threads, causing all go block processing to stop. This includes
458
- core.async blocking ops (those ending in !!) and other blocking IO.
460
+ ; ; task
461
+
462
+ (def task-factory
463
+ (-> (Thread/ofVirtual )
464
+ (Thread$Builder/.name " task-" 0 )
465
+ .factory))
466
+
467
+ (defmacro task
468
+ " Asynchronously executes the body in a virtual thread, returning immediately
469
+ to the calling thread.
470
+
471
+ task blocks should not (either directly or indirectly) perform operations
472
+ that may block indefinitely. Doing so risks pinning the virtual thread
473
+ to its carrier thread.
459
474
460
475
Returns a channel which will receive the result of the body when
461
476
completed"
462
477
[& body]
463
- (#'clojure.core.async.impl.go/go-impl &env body))
478
+ `(let [c# (chan 1 )
479
+ captured-bindings# (Var/getThreadBindingFrame )]
480
+ (.execute
481
+ (Executors/newThreadPerTaskExecutor task-factory)
482
+ (^:once fn* []
483
+ (Var/resetThreadBindingFrame captured-bindings#)
484
+ (try
485
+ (let [result# (do ~@body)]
486
+ (>!! c# result#))
487
+ (finally
488
+ (close! c#)))))
489
+ c#))
490
+
491
+ (defmacro go
492
+ " Dispatches to task macro."
493
+ [& body]
494
+ `(task ~body))
464
495
465
496
(defonce ^:private ^Executor thread-macro-executor
466
497
(Executors/newCachedThreadPool (conc/counted-thread-factory " async-thread-macro-%d" true )))
0 commit comments