missionary

Jeremy 2026-02-08T21:11:36.589869Z

Hi all, I've got 2 questions about the debounce sample: 1. why did we have to wrap the sleep inside a try-catch? 2. what's the point of returning (amb) rather than just nil

leonoel 2026-02-09T08:34:03.833249Z

By convention, tasks and flows crash with missionary.Cancelled when they're cancelled before termination. In ap initial design, I chose to reflect this behavior on cancellations generated by `?>` ?< which is why the cancellation exception has to be caught. amb just means we don't have any meaningful value to return in this case, but nil also works as long as the consumer expects it. It turns out this design choice was a mistake, the correct behavior for ?< is to spawn the new branch immediately and silently flush the previous one in the background. It is implemented in cp and will eventually be ported to ap so you won't have to guard cancellation anymore. https://github.com/leonoel/missionary/issues/68 https://github.com/leonoel/missionary/issues/94

👍 1
xificurC 2026-02-09T08:44:02.975299Z

was the first also meant to be ?<?

✅ 1
xificurC 2026-02-09T08:45:10.970999Z

returning nil means the consumer will see a nil. Returning (amb) means the consumer won't see anything.

👍 1
Jeremy 2026-02-09T10:11:41.809229Z

@leonoel if you remove the cancelation behavior from ap, how we we cleanup? Do the tasks under the ap still get the cancelation exception?

leonoel 2026-02-09T10:13:34.699069Z

the current branch is still cancelled with all its children, just post-cancellation events are ignored

Jeremy 2026-02-09T10:16:47.929809Z

I see. So we can still catch the exception, but failure do so wouldn't affect the fork that interrupted the current branch?

leonoel 2026-02-09T10:18:37.438049Z

do you have an example ?

xificurC 2026-02-09T10:38:35.672969Z

no, the assumption is you never want to interact with the old branch, therefore missionary cleans it up silently. ?< says "I care about the latest value", i.e. it's OK to silently clean up the old value's supervision tree

Jeremy 2026-02-09T10:55:11.731669Z

I get that. The case I'm talking about it.. I care about the latest value. However processing of the current value mightve reached a point that I need to do cleanup before cancelation

Jeremy 2026-02-09T11:05:20.994019Z

How's can this be done if current branch is silently killed:

(m/ap (try
        (let [x (m/?< a-flow)
              y (allocate-resource! x)]
          (do-something! x y))
        (catch missionary.Cancelled e
          (deallocate-resource! y))))

xificurC 2026-02-09T15:30:39.128319Z

you'd wrap your resource with cleanup, not the ap

xificurC 2026-02-09T15:34:28.065999Z

see this discussion https://clojurians.slack.com/archives/CL85MBPEF/p1739693891085089 . TLDR you'd use the (m/sp (m/? m/never) (finally (cleanup))) pattern or the (m/?> (m/observe (fn [!] (! resource) #(cleanup)))) pattern

Jeremy 2026-02-08T22:11:54.390659Z

From playing in the repl, I think i got it: 1. cancellation of incomplete forks of ?< isn't silent. 2. unlike core.async, nil is a valid return value in an ap block