From b88ee7a927e825250adf387021df077309a14ea5 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sat, 5 Sep 2026 23:57:36 -0400 Subject: [PATCH] Give RT/iter the empty input list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit latest/run and sample/run both open with (.iterator (seq fs)), and in both `fs` can be empty: a zero-input (m/latest f) has no flows at all, and (m/sample f sampler) splits its varargs so the continuous inputs are (). An empty seq is nil, and a method call on nil is a NullPointerException. It only ever ran because jolt through 0.8.1 answered (.iterator nil) with an empty iterator instead of refusing it; 0.8.2 refuses it as the JVM always has, which is what surfaced this as #4. RT/iter is what clojure.core's own walkers use for this and takes nil on both hosts. The sample half was unreported and uncovered — a sampler-only sample is the single most ordinary shape of the operator. It has a row now, and it fails without the fix. Pin moved to 0.8.2 with the fix, per the cutover convention in ci.yml. Closes #4. Claude-Session: https://claude.ai/code/session_019tGgRzPx3vtr5siPkKjjLE --- .github/workflows/ci.yml | 2 +- src/ebb/impl/latest.clj | 9 ++++++++- src/ebb/impl/sample.clj | 5 ++++- test/ebb/missionary_issues_test.clj | 10 ++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d13618d..ea1ccfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ on: default: "" env: - JOLT_VERSION: ${{ inputs.jolt-version || '0.8.1' }} + JOLT_VERSION: ${{ inputs.jolt-version || '0.8.2' }} jobs: test: diff --git a/src/ebb/impl/latest.clj b/src/ebb/impl/latest.clj index 9d0ece3..741760e 100644 --- a/src/ebb/impl/latest.clj +++ b/src/ebb/impl/latest.clj @@ -180,8 +180,15 @@ (step ps i)))) (or (= p (.-pending ps)) (terminated ps i)))) +;; RT/iter, not (.iterator (seq fs)): `fs` is a rest arg, so a zero-input +;; (m/latest f) -- issue #126, a well defined continuous flow whose state is +;; always (f) -- hands this nil, and a method call on nil is a +;; NullPointerException. It ran only because jolt through 0.8.1 answered +;; (.iterator nil) with an empty iterator instead of refusing it +;; (jolt-lang/jolt#867); 0.8.2 refuses it as the JVM always has. RT/iter is what +;; clojure.core's own walkers use for exactly this and takes nil on both hosts. (defn run [c fs s d] - (let [it (.iterator (seq fs)) + (let [it (clojure.lang.RT/iter fs) arity (count fs) ;; `step` starts NIL, as Latest.java's does. It is what says "this flow ;; has no value yet": `transfer` reports Uninitialized on it, and diff --git a/src/ebb/impl/sample.clj b/src/ebb/impl/sample.clj index 53bf035..c84fbea 100644 --- a/src/ebb/impl/sample.clj +++ b/src/ebb/impl/sample.clj @@ -212,8 +212,11 @@ (if (nil? (.-notifier ps)) (throw x) x))) +;; RT/iter for the same reason latest/run uses it: `fs` here is (rest flows), +;; so (m/sample f sampler) with no continuous inputs -- the single-flow sample, +;; which nothing in the suite covered -- hands this an empty seq's nil. (defn run [c f fs n t] - (let [it (.iterator (seq fs)) + (let [it (clojure.lang.RT/iter fs) arity (inc (count fs)) args (object-array arity) owed (int-array arity) diff --git a/test/ebb/missionary_issues_test.clj b/test/ebb/missionary_issues_test.clj index 8d48fad..ab6bf64 100644 --- a/test/ebb/missionary_issues_test.clj +++ b/test/ebb/missionary_issues_test.clj @@ -45,6 +45,16 @@ (t/is (= [:ok [:nullary]] (settle (m/reduce conj [] (m/latest (fn [] :nullary))))))) +(t/deftest sample-supports-a-sampler-only + ;; Not an upstream issue: ebb's own. `sample` splits its varargs into the + ;; sampler and the continuous inputs, so (m/sample f sampler) hands the impl + ;; an EMPTY input list -- the same nil target the zero-input latest above + ;; hands it, and the same NullPointerException. Nothing covered the shape, + ;; so it went unreported while the latest case was being filed. The result is + ;; (f) per sampled value, as it is with any other input count. + (t/is (= [:ok [[1] [2] [3]]] + (settle (m/reduce conj [] (m/sample vector (m/seed [1 2 3]))))))) + (t/deftest signal-reports-an-uninitialised-flow-125 ;; #125: (m/signal (m/ap (m/? m/never))) leaked internal state and reported ;; nothing. It must fail instead.