I'm trying to use missionary RAII to open and close portal on demand. This works: ๐งต
I've written this up in a bit more depth here: https://github.com/leonoel/missionary/issues/132
(def >portal
(m/signal
(m/observe
(fn [cb]
;; TODO: Technically we shouldn't block here
;; TODO: Handle errors
((m/via m/blk ((requiring-resolve 'portal.api/open))) {} {})
(cb :open)
(fn []
((m/via m/blk ((requiring-resolve 'portal.api/close))) {} {}))))))...but there are two things that feel off. 1. Manually running the tasks in m/observe smells weird 2. The cleanup thunk calls portal.api/close's arity-zero form. It would be preferrable to call the arity-1 form, which accepts a portal as an argument. However, I'm struggling to come up with a clean pattern for passing the constructed portal object to the cleanup thunk.
I've run into this in a few other places. I guess the broader question is, what is the idiomatic way of using m/observe to instantiate an object with a blocking constructor and destructor?
I think this could work:
(m/?
(m/reduce conj []
(m/eduction (take 1)
(m/ap
(let [resource (m/? (m/via m/blk (prn :init!) :the-resource))]
(m/amb= resource
(try (m/? m/never)
(finally (m/? (m/via m/blk (prn :cleanup!)))))))))))
;; outputs:
;; :init!
;; :cleanup!
;; => [:the-resource]
Hmm, doing Thread/sleep or other blocking calls in the cleanup task might be problematic, couldn't get prn output then.Another cancellation puzzle.
Here, resource b depends on resource a. Both resources are represented as flows:
(def a*
(m/stream
(m/ap
(let [a :a]
(m/amb= (do (println "Opening" a)
a)
(try (m/? m/never)
(finally (println "Closing" a))))))))
(def b*
(m/ap
(let [a (m/?< a*)
b [a :b]]
(m/amb= (do
(println "Opening" b)
(m/amb))
(try (m/? m/never)
(finally (println "Closing" b)))))))
(def cancel
((m/reduce prn {} b*) prn prn))
(cancel)
When I invoke (cancel), I want b to close before a (since the resource b depends on the existence of a ), but the opposite actually happens โ a closes before b. It's almost as if my intuition about the supervision tree is backwards.
I can't quite figure out how to get the behavior that I want. (Well, actually, I can if I inline b inside a, but then I lose some of the compositional properties that I'd like to hold onto.)Hm, as @itsfah pointed out, it looks like x will never contain :c or :d.
use m/compel on cleanup task to disable interruption
Ah! Cool, this works
I've been wondering when I would use m/compel, that answers that.
It isn't entirely clear to me how that task receives the cancelation signal, but I'll do a bit of experimenting and see if I can build some intuition.
(One thing that I've found challenging about missionary is noticing and debugging cancelation. Some kind of tracing mechanism for that would be very useful. I'd love to get a sense of how the hyperfiddle team thinks about this.)
yes that's the right pattern, blocking in the cleanup task is fine as long as it's in the m/via
Thank you both! This is great.
I would have never thought of the (m/? m/never) pattern, but it makes a lot of sense. You need some flow to throw during cancellation.