A simple, fast, monitoring profiler for Clojure/Script.
Usage: wrap+name interesting body exprs with the p
macro. Then activate
profiling of these wrapped exprs using the profiled
or profile
macros:
(profiled {} (p :my-fn (my-fn))) ; Returns [<body-result> <?pstats>] (profile {} (p :my-fn (my-fn))) ; Returns <body-result>, dispatches ; ?pstats to any registered handlers.
Extensive facilities are provided for compile-time elision and runtime filtering.
See the relevant docstrings for more info:
p
, profiled
, profile
, add-handler!
; Core API
(p [opts & body] [id & body]) ; e.g. (p ::my-pid (do-work))
(profiled [opts & body]) ; e.g. (profiled {:level 2} (my-fn))
(profile [opts & body]) ; e.g. (profiled {:level 2} (my-fn))
(add-handler! [handler-id ns-pattern handler-fn])
How/where to use this library: Tufte profiling is highly optimized: even without elision, you can usually leave profiling enabled in production (e.g. for sampled profiling, or to detect unusual performance behaviour). Tufte's stats maps are well suited to programmatic monitoring.
Abbreviations, etc.
p
profile
A simple, fast, monitoring profiler for Clojure/Script. Usage: wrap+name interesting body exprs with the `p` macro. Then activate profiling of these wrapped exprs using the `profiled` or `profile` macros: (profiled {} (p :my-fn (my-fn))) ; Returns [<body-result> <?pstats>] (profile {} (p :my-fn (my-fn))) ; Returns <body-result>, dispatches ; ?pstats to any registered handlers. Extensive facilities are provided for compile-time elision and runtime filtering. See the relevant docstrings for more info: `p`, `profiled`, `profile`, `add-handler!` ; Core API (p [opts & body] [id & body]) ; e.g. `(p ::my-pid (do-work))` (profiled [opts & body]) ; e.g. `(profiled {:level 2} (my-fn))` (profile [opts & body]) ; e.g. `(profiled {:level 2} (my-fn))` (add-handler! [handler-id ns-pattern handler-fn]) How/where to use this library: Tufte profiling is highly optimized: even without elision, you can usually leave profiling enabled in production (e.g. for sampled profiling, or to detect unusual performance behaviour). Tufte's stats maps are well suited to programmatic monitoring. Abbreviations, etc. - form id = pid = id given in `p` - group id = gid = id given in `profile`
Integer e/o #{0 1 2 3 4 5 6}, or vector mapping ns-patterns to min-levels: [[<ns-pattern> <min-level>] ... ["*" <default-min-level>]]
See *ns-filter*
for example patterns.
Integer e/o #{0 1 2 3 4 5 6}, or vector mapping ns-patterns to min-levels: [[<ns-pattern> <min-level>] ... ["*" <default-min-level>]] See `*ns-filter*` for example patterns.
(fn may-profile-ns? [ns]) predicate, or ns-pattern. Example ns-patterns: #{}, "", "foo.bar", "foo.bar.", #{"foo" "bar."}, {:allow #{"foo" "bar."} :deny #{"foo..bar."}}
(fn may-profile-ns? [ns]) predicate, or ns-pattern. Example ns-patterns: #{}, "*", "foo.bar", "foo.bar.*", #{"foo" "bar.*"}, {:allow #{"foo" "bar.*"} :deny #{"foo.*.bar.*"}}
(-elide? level-form ns-str-form)
Returns true iff level or ns are compile-time filtered. Called only at macro-expansiom time.
Returns true iff level or ns are compile-time filtered. Called only at macro-expansiom time.
(add-accumulating-handler! {:keys [ns-pattern handler-id runner-opts]
:or {ns-pattern "*" handler-id :accumulating}})
Alpha, subject to change.
Creates a new StatsAccumulator and registeres a handler to
accumulate profile
output to the StatsAccumulator.
Returns the StatsAccumulator. Deref it to drain the accumulator and return {<group-id> <merged-pstats>}.
One common pattern is to deref the accumulator every n minutes/etc. to get a view of total-system performance over the period, e.g.:
(defonce my-sacc (add-accumulating-handler! {:ns-pattern "*"})) (defonce my-sacc-drainer ;; Drain and print formatted stats every minute (future (while true (when-let [m (not-empty @my-sacc)] (println (format-grouped-pstats m))) (Thread/sleep 60000))))
(profile ...) ; Used elsewhere in your application, e.g. ; wrapping relevant Ring routes in a web application.
See also format-grouped-pstats
, example clj project.
Alpha, subject to change. Creates a new StatsAccumulator and registeres a handler to accumulate `profile` output to the StatsAccumulator. Returns the StatsAccumulator. Deref it to drain the accumulator and return {<group-id> <merged-pstats>}. One common pattern is to deref the accumulator every n minutes/etc. to get a view of total-system performance over the period, e.g.: (defonce my-sacc (add-accumulating-handler! {:ns-pattern "*"})) (defonce my-sacc-drainer ;; Drain and print formatted stats every minute (future (while true (when-let [m (not-empty @my-sacc)] (println (format-grouped-pstats m))) (Thread/sleep 60000)))) (profile ...) ; Used elsewhere in your application, e.g. ; wrapping relevant Ring routes in a web application. See also `format-grouped-pstats`, example clj project.
(add-basic-println-handler! {:keys [ns-pattern handler-id format-pstats-opts]
:or {ns-pattern "*" handler-id :basic-println}})
Adds a simple handler that logs profile
stats output with println
.
Adds a simple handler that logs `profile` stats output with `println`.
(add-handler! handler-id handler-fn)
(add-handler! handler-id ns-pattern handler-fn)
Use this to register interest in stats output produced by profile
calls.
Each registered handler-fn
will be called as:
(handler-fn {:ns-str _ :level _ :?id _ :?data _ :pstats _ :pstats-str_ _})
Map args:
:ns-str - Namespace string where profile
call took place
:level - Level e/o #{0 1 2 3 4 5}, given in (profile {:level _} ...)
:?id - Optional group id, given in (profile {:id _} ...)
:?data - Optional arb data, given in (profile {:data _} ...)
:pstats - As in (second (profiled ...))
. Derefable, mergeable.
:pstats-str_ - `(delay (format-pstats pstats))
Error handling (NB):
Handler errors will be silently swallowed. Please try
/catch
and
appropriately deal with (e.g. log) possible errors within handler-fn
.
Async/blocking:
handler-fn
should ideally be non-blocking, or reasonably cheap. Handler
dispatch occurs through a 1-thread 1k-buffer dropping queue.
Ns filtering:
Provide an optional ns-pattern
arg to only call handler for matching
namespaces. See *ns-filter*
for example patterns.
Handler ideas:
Save to a db, log, put!
to an appropriate core.async
channel, filter,
aggregate, use for a realtime analytics dashboard, examine for outliers
or unexpected output, ...
Use this to register interest in stats output produced by `profile` calls. Each registered `handler-fn` will be called as: (handler-fn {:ns-str _ :level _ :?id _ :?data _ :pstats _ :pstats-str_ _}) Map args: :ns-str - Namespace string where `profile` call took place :level - Level e/o #{0 1 2 3 4 5}, given in `(profile {:level _} ...)` :?id - Optional group id, given in `(profile {:id _} ...)` :?data - Optional arb data, given in `(profile {:data _} ...)` :pstats - As in `(second (profiled ...))`. Derefable, mergeable. :pstats-str_ - `(delay (format-pstats pstats)) Error handling (NB): Handler errors will be silently swallowed. Please `try`/`catch` and appropriately deal with (e.g. log) possible errors *within* `handler-fn`. Async/blocking: `handler-fn` should ideally be non-blocking, or reasonably cheap. Handler dispatch occurs through a 1-thread 1k-buffer dropping queue. Ns filtering: Provide an optional `ns-pattern` arg to only call handler for matching namespaces. See `*ns-filter*` for example patterns. Handler ideas: Save to a db, log, `put!` to an appropriate `core.async` channel, filter, aggregate, use for a realtime analytics dashboard, examine for outliers or unexpected output, ...
(capture-time! id nano-secs-elapsed)
(capture-time! pdata id nano-secs-elapsed)
Note: this is a low-level primitive for advanced users! Can be useful when tracking time across arbitrary thread boundaries or for async jobs / callbacks / etc.
See new-pdata
for more info on low-level primitives.
See also capture-time!*
.
Note: this is a low-level primitive for advanced users! Can be useful when tracking time across arbitrary thread boundaries or for async jobs / callbacks / etc. See `new-pdata` for more info on low-level primitives. See also `capture-time!*`.
(capture-time!* id nano-secs-elapsed)
(capture-time!* pdata id nano-secs-elapsed)
Like capture-time!
but: a function, and does not collect callsite location info.
Like `capture-time!` but: a function, and does not collect callsite location info.
(chance p)
Returns true with 0<p
<1 probability.
Returns true with 0<`p`<1 probability.
(compile-ns-filter ns-pattern)
Wraps taoensso.encore/compile-str-filter
.
Wraps `taoensso.encore/compile-str-filter`.
(defnp name doc-string? attr-map? [params*] prepost-map? body)
(defnp name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?)
Like defn
but wraps fn bodies with p
macro.
Like `defn` but wraps fn bodies with `p` macro.
(defnp- name doc-string? attr-map? [params*] prepost-map? body)
(defnp- name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?)
Like defn-
but wraps fn bodies with p
macro.
Like `defn-` but wraps fn bodies with `p` macro.
(fnp name? ([params*] prepost-map? body) +)
(fnp name? [params*] prepost-map? body)
Like fn
but wraps fn bodies with p
macro.
Like `fn` but wraps fn bodies with `p` macro.
(format-grouped-pstats m)
(format-grouped-pstats m
{:keys [group-sort-fn format-pstats-opts]
:or {group-sort-fn (fn [m]
(get-in m [:clock :total] 0))}})
Alpha, subject to change.
Takes a map of {<group-id> <pstats>} and formats a combined
output string using format-pstats
.
See also example clj project.
Alpha, subject to change. Takes a map of {<group-id> <pstats>} and formats a combined output string using `format-pstats`. See also example clj project.
(format-id-abbr)
(format-id-abbr n)
Returns a cached (fn [id]) => abbreviated id string.
Takes n
(default 1), the number of namespace parts to keep unabbreviated.
Examples: ((format-id-abbr) :foo) => "foo" ((format-id-abbr) :example.hello/foo) => "e.hello/foo" ((format-id-abbr 1) :example.hello/foo) => "e.hello/foo" ((format-id-abbr 1) :example.hello.world/foo) => "e.h.world/foo" ((format-id-abbr 2) :example.hello.world/foo) => "e.hello.world/foo" ((format-id-abbr 0) :example.hello.world/foo) => "e.h.w/foo"
Returns a cached (fn [id]) => abbreviated id string. Takes `n` (default 1), the number of namespace parts to keep unabbreviated. Examples: ((format-id-abbr) :foo) => "foo" ((format-id-abbr) :example.hello/foo) => "e.hello/foo" ((format-id-abbr 1) :example.hello/foo) => "e.hello/foo" ((format-id-abbr 1) :example.hello.world/foo) => "e.h.world/foo" ((format-id-abbr 2) :example.hello.world/foo) => "e.hello.world/foo" ((format-id-abbr 0) :example.hello.world/foo) => "e.h.w/foo"
(format-pstats ps)
(format-pstats ps opts)
Formats given pstats to a string table. Accounted < Clock => Some work was done that wasn't tracked by any p forms. Accounted > Clock => Nested p forms, and/or parallel threads.
Formats given pstats to a string table. Accounted < Clock => Some work was done that wasn't tracked by any p forms. Accounted > Clock => Nested p forms, and/or parallel threads.
Debug info for compile-time options used by Tufte's profile
handler runner.
See taoensso.encore/runner
ns for more info.
(Advanced) Override with one of the following (as edn):
taoensso.tufte.handler.runner-opts
JVM property value
TAOENSSO_TUFTE_HANDLER_RUNNER_OPTS
environment variable value
taoensso.tufte.handler.runner-opts
classpath resource content
Debug info for compile-time options used by Tufte's `profile` handler runner. See `taoensso.encore/runner` ns for more info. (Advanced) Override with one of the following (as edn): `taoensso.tufte.handler.runner-opts` JVM property value `TAOENSSO_TUFTE_HANDLER_RUNNER_OPTS` environment variable value `taoensso.tufte.handler.runner-opts` classpath resource content
(may-profile? level)
(may-profile? level ns)
Implementation detail. Returns true iff level and ns are runtime unfiltered.
Implementation detail. Returns true iff level and ns are runtime unfiltered.
(merge-pstats)
(merge-pstats ps0)
(merge-pstats ps0 ps1)
Statistics are lossless unless data to merge are very large.
Statistics are lossless unless data to merge are very large.
(new-pdata)
(new-pdata {:keys [dynamic? nmax] :or {dynamic? true nmax default-nmax}})
Note: this is a low-level primitive for advanced users!
Returns a new pdata object for use with with-profiling
and/or capture-time!
.
Deref to get pstats:
(let [pd (new-pdata) t0 (System/nanoTime)] (with-profiling pd {} (p :foo (Thread/sleep 100)) (capture-time! pd :bar (- t0 (System/nanoTime)))) (deref pd))
Dynamic (thread-safe) by default. WARNING: don't change this default unless you're very sure the resulting pdata object will not be concurrently modified across threads. Concurrent modification will lead to bad data and/or exceptions!
Note: this is a low-level primitive for advanced users! Returns a new pdata object for use with `with-profiling` and/or `capture-time!`. Deref to get pstats: (let [pd (new-pdata) t0 (System/nanoTime)] (with-profiling pd {} (p :foo (Thread/sleep 100)) (capture-time! pd :bar (- t0 (System/nanoTime)))) (deref pd)) Dynamic (thread-safe) by default. *WARNING*: don't change this default unless you're very sure the resulting pdata object will not be concurrently modified across threads. Concurrent modification will lead to bad data and/or exceptions!
(p id & body)
(p opts & body)
Profiling spy. Always executes body, and always returns <body-result>.
When [ns level] unelided and profiling is active, records execution time of body.
Compile-time opts:
:id - Form id for this body in stats output (e.g. ::my-fn-call
)
:level - e/o #{0 1 2 3 4 5} ; Default is 5
Profiling spy. Always executes body, and always returns <body-result>. When [ns level] unelided and profiling is active, records execution time of body. Compile-time opts: :id - Form id for this body in stats output (e.g. `::my-fn-call`) :level - e/o #{0 1 2 3 4 5} ; Default is `5`
(profile opts & body)
Always executes body, and always returns <body-result>.
When [ns level] unelided and [ns level when
] unfiltered, executes body
with profiling active and dispatches stats to any registered handlers
(see add-handler!
).
Handy if you'd like to consume/aggregate stats output later/elsewhere.
Otherwise see profiled
.
Compile-time opts:
:dynamic? - Use multi-threaded profiling? ; Default is false
:nmax - ~Max captures per id before compaction ; Default is 8e5
Runtime opts:
:level - e/o #{0 1 2 3 4 5} ; Default is 5
:when - Optional arbitrary conditional form (e.g. boolean expr)
:id - Optional group id provided to handlers (e.g. ::my-stats-1
)
:data - Optional arbitrary data provided to handlers
Laziness in body: Lazy seqs and other forms of laziness (e.g. delays) in body will only contribute to profiling results if/when EVALUATION ACTUALLY OCCURS. This is intentional and a useful property. Compare:
(profile {} (delay (Thread/sleep 2000))) ; Doesn't count sleep (profile {} @(delay (Thread/sleep 2000))) ; Does count sleep
Async code in body: Execution time of any code in body that runs asynchronously on a different thread will generally NOT be automatically captured by default.
:dynamic? can be used to support capture in cases where Clojure's
binding conveyance applies (e.g. futures, agents, pmap). Just make sure
that all work you want to capture has COMPLETED before the profiled
form ends- for example, by blocking on pending futures.
In other advanced cases (notably core.async go
blocks), please see
with-profiling
and capture-time!
.
core.async
warning:
core.async
code can be difficult to profile correctly without a deep
understanding of precisely what it's doing under-the-covers.
Some general recommendations that can help keep things simple:
- Try minimize the amount of code + logic in `go` blocks. Use `go`
blocks for un/parking to get the data you need, then pass the data
to external fns. Profile these fns (or in these fns), not in your
`go` blocks.
- In particular: you MUST NEVER have parking calls inside
`(profiled {:dynamic? false} ...)`.
This can lead to concurrency exceptions.
If you must profile code within a go block, and you really want to
include un/parking times, use `(profiled {:dynamic? true} ...)`
instead.
Always executes body, and always returns <body-result>. When [ns level] unelided and [ns level `when`] unfiltered, executes body with profiling active and dispatches stats to any registered handlers (see `add-handler!`). Handy if you'd like to consume/aggregate stats output later/elsewhere. Otherwise see `profiled`. Compile-time opts: :dynamic? - Use multi-threaded profiling? ; Default is `false` :nmax - ~Max captures per id before compaction ; Default is 8e5 Runtime opts: :level - e/o #{0 1 2 3 4 5} ; Default is `5` :when - Optional arbitrary conditional form (e.g. boolean expr) :id - Optional group id provided to handlers (e.g. `::my-stats-1`) :data - Optional arbitrary data provided to handlers Laziness in body: Lazy seqs and other forms of laziness (e.g. delays) in body will only contribute to profiling results if/when EVALUATION ACTUALLY OCCURS. This is intentional and a useful property. Compare: (profile {} (delay (Thread/sleep 2000))) ; Doesn't count sleep (profile {} @(delay (Thread/sleep 2000))) ; Does count sleep Async code in body: Execution time of any code in body that runs asynchronously on a different thread will generally NOT be automatically captured by default. :dynamic? can be used to support capture in cases where Clojure's binding conveyance applies (e.g. futures, agents, pmap). Just make sure that all work you want to capture has COMPLETED before the `profiled` form ends- for example, by blocking on pending futures. In other advanced cases (notably core.async `go` blocks), please see `with-profiling` and `capture-time!`. `core.async` warning: `core.async` code can be difficult to profile correctly without a deep understanding of precisely what it's doing under-the-covers. Some general recommendations that can help keep things simple: - Try minimize the amount of code + logic in `go` blocks. Use `go` blocks for un/parking to get the data you need, then pass the data to external fns. Profile these fns (or in these fns), not in your `go` blocks. - In particular: you MUST NEVER have parking calls inside `(profiled {:dynamic? false} ...)`. This can lead to concurrency exceptions. If you must profile code within a go block, and you really want to include un/parking times, use `(profiled {:dynamic? true} ...)` instead.
(profiled opts & body)
Always executes body, and always returns [<body-result> <?pstats>].
When [ns level] unelided and [ns level when
] unfiltered, executes body
with profiling active.
Handy if you'd like to consume stats output directly.
Otherwise see profile
.
pstats
objects are derefable and mergeable:
Full set of keys in :stats
maps:
:n :min :max :mean :mad :sum :p25 :p50 :p75 :p90 :p95 :p99 :loc
All values are numerical (longs or doubles), except for :loc
which
is a map of p
callsite location information, e.g.:
{:ns "my-ns", :file "/tmp/my-ns.clj", :line 122, :column 21}
Compile-time opts:
:dynamic? - Use multi-threaded profiling? ; Default is false
:nmax - ~Max captures per id before compaction ; Default is 8e5
Runtime opts:
:level - e/o #{0 1 2 3 4 5} ; Default is 5
:when - Optional arbitrary conditional form (e.g. boolean expr)
Laziness in body: Lazy seqs and other forms of laziness (e.g. delays) in body will only contribute to profiling results if/when EVALUATION ACTUALLY OCCURS. This is intentional and a useful property. Compare:
(profiled {} (delay (Thread/sleep 2000))) ; Doesn't count sleep (profiled {} @(delay (Thread/sleep 2000))) ; Does count sleep
Async code in body: Execution time of any code in body that runs asynchronously on a different thread will generally NOT be automatically captured by default.
:dynamic? can be used to support capture in cases where Clojure's
binding conveyance applies (e.g. futures, agents, pmap). Just make sure
that all work you want to capture has COMPLETED before the profiled
form ends- for example, by blocking on pending futures.
In other advanced cases (notably core.async go
blocks), please see
with-profiling
and capture-time!
.
core.async
warning:
core.async
code can be difficult to profile correctly without a deep
understanding of precisely what it's doing under-the-covers.
Some general recommendations that can help keep things simple:
- Try minimize the amount of code + logic in `go` blocks. Use `go`
blocks for un/parking to get the data you need, then pass the data
to external fns. Profile these fns (or in these fns), not in your
`go` blocks.
- In particular: you MUST NEVER have parking calls inside
`(profiled {:dynamic? false} ...)`.
This can lead to concurrency exceptions.
If you must profile code within a go block, and you really want to
include un/parking times, use `(profiled {:dynamic? true} ...)`
instead.
Always executes body, and always returns [<body-result> <?pstats>]. When [ns level] unelided and [ns level `when`] unfiltered, executes body with profiling active. Handy if you'd like to consume stats output directly. Otherwise see `profile`. `pstats` objects are derefable and mergeable: - @pstats => {:clock {:keys [t0 t1 total]}, :stats {<id> {:keys [n sum ...]}}} - @(merge-pstats ps1 ps2) => {:clock {:keys [t0 t1 total]}, :stats {<id> {:keys [n sum ...]}}} Full set of keys in `:stats` maps: :n :min :max :mean :mad :sum :p25 :p50 :p75 :p90 :p95 :p99 :loc All values are numerical (longs or doubles), except for `:loc` which is a map of `p` callsite location information, e.g.: {:ns "my-ns", :file "/tmp/my-ns.clj", :line 122, :column 21} Compile-time opts: :dynamic? - Use multi-threaded profiling? ; Default is `false` :nmax - ~Max captures per id before compaction ; Default is 8e5 Runtime opts: :level - e/o #{0 1 2 3 4 5} ; Default is `5` :when - Optional arbitrary conditional form (e.g. boolean expr) Laziness in body: Lazy seqs and other forms of laziness (e.g. delays) in body will only contribute to profiling results if/when EVALUATION ACTUALLY OCCURS. This is intentional and a useful property. Compare: (profiled {} (delay (Thread/sleep 2000))) ; Doesn't count sleep (profiled {} @(delay (Thread/sleep 2000))) ; Does count sleep Async code in body: Execution time of any code in body that runs asynchronously on a different thread will generally NOT be automatically captured by default. :dynamic? can be used to support capture in cases where Clojure's binding conveyance applies (e.g. futures, agents, pmap). Just make sure that all work you want to capture has COMPLETED before the `profiled` form ends- for example, by blocking on pending futures. In other advanced cases (notably core.async `go` blocks), please see `with-profiling` and `capture-time!`. `core.async` warning: `core.async` code can be difficult to profile correctly without a deep understanding of precisely what it's doing under-the-covers. Some general recommendations that can help keep things simple: - Try minimize the amount of code + logic in `go` blocks. Use `go` blocks for un/parking to get the data you need, then pass the data to external fns. Profile these fns (or in these fns), not in your `go` blocks. - In particular: you MUST NEVER have parking calls inside `(profiled {:dynamic? false} ...)`. This can lead to concurrency exceptions. If you must profile code within a go block, and you really want to include un/parking times, use `(profiled {:dynamic? true} ...)` instead.
(profiling?)
Returns e/o #{nil :thread :dynamic}.
Returns e/o #{nil :thread :dynamic}.
(refer-tufte)
(require '[taoensso.tufte :as tufte :refer [defnp p profiled profile]])
(require '[taoensso.tufte :as tufte :refer [defnp p profiled profile]])
(remove-handler! handler-id)
(stats-accumulator)
Alpha, subject to change. Small util to help merge pstats from multiple runs or threads.
Returns a stateful StatsAccumulator (sacc
) with:
(sacc <group-id> <pstats>)
; Merges given pstats under given group id@sacc
; Drains accumulator and returns {<group-id> <merged-pstats>}Note that you may want some kind of async/buffer/serialization mechanism in front of merge calls for performance (e.g. by using an agent).
See also add-accumulating-handler!
example clj project.
Alpha, subject to change. Small util to help merge pstats from multiple runs or threads. Returns a stateful StatsAccumulator (`sacc`) with: - `(sacc <group-id> <pstats>)` ; Merges given pstats under given group id - `@sacc` ; Drains accumulator and returns {<group-id> <merged-pstats>} Note that you may want some kind of async/buffer/serialization mechanism in front of merge calls for performance (e.g. by using an agent). See also `add-accumulating-handler!` example clj project.
(with-profiling pdata {:keys [dynamic? nmax] :or {nmax default-nmax}} & body)
Note: this is a low-level primitive for advanced users!
Enables p
forms in body and returns body's result.
If :dynamic?
is false (default), body's evaluation MUST begin
and end without interruption on the same thread. In particular
this means that body MUST NOT contain any parking core.async
calls.
See new-pdata
for more info on low-level primitives.
Note: this is a low-level primitive for advanced users! Enables `p` forms in body and returns body's result. If `:dynamic?` is false (default), body's evaluation MUST begin and end without interruption on the same thread. In particular this means that body MUST NOT contain any parking `core.async` calls. See `new-pdata` for more info on low-level primitives.
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close