Lokasi ngalangkungan proxy:   [ UP ]  
[Ngawartoskeun bug]   [Panyetelan cookie]                
Skip to content

Optional interruption support#1039

Open
whilo wants to merge 4 commits intobabashka:masterfrom
whilo:resource-check
Open

Optional interruption support#1039
whilo wants to merge 4 commits intobabashka:masterfrom
whilo:resource-check

Conversation

@whilo
Copy link
Copy Markdown

@whilo whilo commented Apr 16, 2026

Adds :interrupt-fn callback for per-context execution bounding (fixes #1038).

An optional :interrupt-fn in the SCI context options is called on every function entry. The function can throw to abort execution, enabling iteration limits, memory limits, and external timeout checks — independently per context.

The check is captured as a closed-over local at function-creation time, so it is a free nil test when not configured: zero overhead for existing users.

Coverage: loop/recur, dotimes, while, direct self-calls, mutual recursion. merge-opts and fork preserve the callback.


whilo added 4 commits April 16, 2026 13:23
Adds an optional :resource-check function to SCI contexts. When provided,
it is called on every loop/recur iteration and every fn-call dispatch.
The function can throw to abort execution.

This enables:
- Iteration limits (prevent infinite loops)
- Memory limits (via JVM getThreadAllocatedBytes in the callback)
- External timeout (via Thread.interrupted check in the callback)
- Per-context bounds (different sandboxes get different limits)

The check is cached at function creation time (not looked up per call).
When nil (default), zero overhead — the `when` branch is never taken.

Overhead with amortized check (every 10K iterations): ~2.5x on tight loops.
With every 100K: ~10%. For real workloads: negligible.

Changes:
- opts.cljc: add :resource-check field to Ctx record, wire through init
- fns.cljc: call resource-check on each recur in gen-fn macro
- evaluator.cljc: call resource-check on fn-call dispatch
- Rename the option to :interrupt-fn throughout (opts, fns, evaluator)
- Move the check to the top of gen-fn's loop: fires on every function
  entry (initial call) AND every recur, covering direct recursion,
  mutual recursion, dotimes, while, and loop/recur uniformly
- Remove the check from fn-call: it was only reachable for 20+ arg
  calls (gen-return-call generates direct (f ...) calls for 0-19 args),
  so the original placement was both insufficient and added overhead for
  existing users — now fn-call is truly zero-cost for nil :interrupt-fn
- Add rc# capture to arity-many (20+ arg) fallback in gen-fn
- Fix merge-opts to preserve :interrupt-fn when not overridden
- Add interrupt_fn_test.cljc covering recur, dotimes, direct recursion,
  mutual recursion, nil default, fork, and merge-opts propagation
…ll/dorun/count/into/reduce

When :interrupt-fn is provided, opts/init installs interruptible versions
of nine clojure.core functions that would otherwise bypass the interrupt
mechanism by running entirely host-side:

  Producers:     range, repeat, cycle, iterate
  Materializers: doall, dorun, count, into, reduce

Each wrapper calls store/get-ctx at invocation time to read :interrupt-fn,
so fork and merge-opts work correctly. When :interrupt-fn is absent the
original host functions are used unchanged — zero overhead for existing users.

counted? collections (vectors, maps, sets) take the fast O(1) path in count.
reduce supports reduced for early termination.
#_`(defn ~'fn-call ~'[ctx f args]
(apply ~'f (map #(eval ~'ctx %) ~'args)))
`(defn ~'fn-call ~'[ctx bindings f args]
;; TODO: can we prevent hitting this at all, by analyzing more efficiently?
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't remove anything unrelated to the PR

Comment thread src/sci/impl/fns.cljc
~@(when varargs
[`(aset ~'invoc-array ~'vararg-idx ~varargs-param)])
(loop []
(when interrupt-fn# (interrupt-fn#))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(when (some? interrupt-fn#) ...) seems to be faster on CLJS. Since this is on a hot path let's use that instead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same goes for all other places.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

External interruption support

2 participants