From 991f9c6ffa24ee4f453ccbdb0462f99a7db9c4c0 Mon Sep 17 00:00:00 2001 From: Jose Gomez Date: Wed, 10 Dec 2025 15:13:15 -0600 Subject: [PATCH 1/2] chore: add utility function to easily convert values to futures --- CHANGELOG.md | 3 +++ src/futurama/core.clj | 22 ++++++++++++++++++++++ test/futurama/core_test.clj | 17 +++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 010fa5b..8f0e287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ This is a history of changes to gateless/futurama +# 1.4.1 +* **Feature**: Add utility `->future` function to easily convert values to future. + # 1.4.1 * **Bug Fix**: Ensure future is only cancelled if async item was cancelled. diff --git a/src/futurama/core.clj b/src/futurama/core.clj index d0319bb..5865548 100644 --- a/src/futurama/core.clj +++ b/src/futurama/core.clj @@ -893,3 +893,25 @@ c) (cancelled? [c] (get-cancel-state c))) + +(defn ->future + "Converts any value into a CompletableFuture, reading + through any async result returned inside or returning + a completed future for non-async objects." + [val] + (cond + (instance? CompletableFuture val) + val + + (async? val) + (let [fut (CompletableFuture.)] + (async + (try + (let [res (!future (async ::foobar))] + (is (= ::foobar @fut)))) + (testing "can convert any thread result to CompletableFuture" + (let [fut (f/->future (thread ::foobar))] + (is (= ::foobar @fut)))) + (testing "can use CompletableFuture as CompletableFuture" + (let [fut' (CompletableFuture/completedFuture ::foobar) + fut (f/->future fut')] + (is (= ::foobar @fut)) + (is (identical? fut' fut)))) + (testing "can use non-async value as CompletableFuture" + (let [val ::foobar + fut (f/->future val)] + (is (= ::foobar @fut))))) From 2adfa7c02c89e0eb30879b927c9063c5ecbab13c Mon Sep 17 00:00:00 2001 From: Jose Gomez Date: Thu, 11 Dec 2025 09:27:01 -0600 Subject: [PATCH 2/2] feat: ensure async ->future can be cancelled and cancels wrapped async --- CHANGELOG.md | 2 +- src/futurama/core.clj | 7 ++++--- test/futurama/core_test.clj | 30 +++++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f0e287..1181cff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ This is a history of changes to gateless/futurama -# 1.4.1 +# 1.4.2 * **Feature**: Add utility `->future` function to easily convert values to future. # 1.4.1 diff --git a/src/futurama/core.clj b/src/futurama/core.clj index 5865548..c860377 100644 --- a/src/futurama/core.clj +++ b/src/futurama/core.clj @@ -807,7 +807,7 @@ (let [^BiConsumer invoke-cb (impl/->JavaBiConsumer (fn [_ _] (when (impl/cancelled? fut) - (future-cancel fut'))))] ;;; cancel any linked future + (impl/cancel! fut'))))] ;;; cancel any linked future (.whenComplete ^CompletableFuture fut ^BiConsumer invoke-cb) fut)) (cancel! [fut] @@ -861,7 +861,7 @@ (on-cancel-interrupt [dfd fut] (let [interrupt-handler (fn [_] (when (impl/cancelled? dfd) - (future-cancel fut)))] + (impl/cancel! fut)))] (d/on-realized dfd interrupt-handler interrupt-handler) dfd)) (cancelled? [dfd] @@ -889,7 +889,7 @@ (instance? PromiseBuffer)) (async/take! c (fn [_] (when (impl/cancelled? c) - (future-cancel fut))))) + (impl/cancel! fut))))) c) (cancelled? [c] (get-cancel-state c))) @@ -905,6 +905,7 @@ (async? val) (let [fut (CompletableFuture.)] + (impl/on-cancel-interrupt fut val) (async (try (let [res (!future is interrupted test" + (with-pool @test-pool + (let [interrupted (atom false) + a (promise) + s (atom 0) + f (future + (try + (while (not (async-cancelled?)) ;;; this loop goes on infinitely until the thread is interrupted + (Thread/sleep 90) + (println "thread looping..." (swap! s inc))) + (println "ended thread looping.") + (deliver a true) + (catch InterruptedException e + (println "interrupted looping by:" (type e)) + (reset! interrupted true) + (deliver a true)))) + f' (f/->future f)] + (is (true? (async-cancellable? f))) + (go + (future causes the converted async object to be interrupted + (is (true? @a)) + (is (true? (async-cancelled? f))) + (is (true? (async-completed? f))) + (is (true? @interrupted))))) (testing "cancellable future is interrupted test" (with-pool @test-pool (let [interrupted (atom false) @@ -514,9 +539,12 @@ ::result)))))))) (deftest test-future-conversion - (testing "can convert any async result to CompletableFuture" + (testing "can convert any async result to CompletableFuture - success" (let [fut (f/->future (async ::foobar))] (is (= ::foobar @fut)))) + (testing "can convert any async result to CompletableFuture - failure" + (let [fut (f/->future (async (throw (ex-info "foobar" {}))))] + (is (thrown-with-msg? Exception #"foobar" @fut)))) (testing "can convert any thread result to CompletableFuture" (let [fut (f/->future (thread ::foobar))] (is (= ::foobar @fut))))