Fork me on GitHub
#clojure
<
2023-03-15
>
kirill.salykin09:03:50

Is there a way to tell from the fn which being executed inside a future that it was canceled? how the running code should be aware about need to stop? Want to understand how handle cancelation within long running process thanks! main-thread -> (future long-running-job) what Iam looking for - how long-running-job can get notified that it should stop?

2
leonoel09:03:26

future-cancel interrupts the thread running the future. When a thread is interrupted, blocking calls are expected to throw. You can also poll interruption status with .isInterrupted

(future
  (loop [i 0]
    (when (.isInterrupted (Thread/currentThread))
      (throw (InterruptedException.)))
    (recur (inc i))))

🙏 2
kirill.salykin09:03:07

ah, super thank you!

tomd12:03:07

If I set the default uncaught exception handler to println or replace (throw (InterruptedException.)) with a println , nothing happens after running (future-cancel). What is supposed to happen when the future is cancelled? From what I've seen, nothing happens 😕

kirill.salykin12:03:02

I checked the code example, .isInterrupted doesnt work but java.util.concurrent.CancellationException is thrown (bubbles up on deref)

tomd12:03:31

Ah nice, that does help, thanks!

tomd12:03:51

Just tried a try catch inside the function aiming to catch java.util.concurrent.CancellationExceptions but that doesn't trigger when the future-cancel is called. Your initial question was about the function itself knowing about being cancelled. Presumably that's still not resolved?

kirill.salykin13:03:28

you need to deref

kirill.salykin13:03:39

then the exception will convey up

tomd13:03:16

A function can't deref itself though. I think what you're saying is outside the function?

tomd13:03:36

tbh you didn't actually say anything about needing to be inside the future code. I guess dereffing and catching there is enough 🙂

kirill.salykin13:03:13

(let [job-future (future (core/run executor job))]
  (async/go-loop []
    (if (realized? shutdown)
      (future-cancel job-future)
      (recur)))

   (deref job-future)
   (mark-completed db job)
   ::success)

   (catch java.util.concurrent.CancellationException _
     (log/info logger (str "WORKER INTERRUPTED" job))
     (mark-interrupted db job)
     ::interrupted)

   (catch Throwable t
     (log/error logger t "JOB FAILED" {:type      type
                                       :exception (.getMessage t)}))

kirill.salykin13:03:30

this is the code example which I think works for me (didnt test it yet)

emccue14:03:15

rather than using thread interrupt, you can use an atomic boolean (or an atom containing a boolean)

nice 2
emccue14:03:29

in my background jobs I pass an explicit "poison pill" param

emccue14:03:36

(defn run-standard-consumer-loop-in-thread
  "Runs a standard consumer loop in a single thread.

  The arguments this accepts are the same as the arguments
  accepted by `standard-consumer-loop`.

  The return value of this call is a function that will interrupt
  the consumer thread and block until the thread dies."
  [message-consumer options]
  (let [poison-pill (AtomicBoolean. false)
        thread      (Thread.
                      ^Runnable
                      (fn []
                        (standard-consumer-loop
                          message-consumer
                          (assoc options
                                 :poison-pill #(.get poison-pill))))
                      (str "standard-consumer-loop-"
                           (.getAndIncrement consumer-thread-counter)))]

    (.start thread)
    (fn shutdown-standard-consumer-loop
      []
      (.set poison-pill true)
      (.join thread))))

emccue14:03:44

(loop []
    (when-not (poison-pill)
      

emccue14:03:48

inside the rest of the code

emccue14:03:42

the only thing special about this versus thread interrupt is that you don't need to think about how other code might react to it

emccue14:03:37

like if you are making an http request, the http client might just fail to function because it detects that the thread is interrupted

emccue14:03:00

if you make your own flag it is a lot more predictable what will happen when you tell your thread to cancel

slipset12:03:43

Is there a fn in core (or elsewhere) that given a keyword :my-ns/whatever returns :my-ns ?

lispyclouds12:03:12

ive used (-> :foo/bar namespace keyword)

delaguardo12:03:15

(comp keyword namespace)

DrLjótsson16:03:27

Is there a simple way to write this (let [foo (bar ,,,)] (when (pred? foo) foo))

DrLjótsson16:03:46

That is, only return the value of (bar ,,,) if it fulfills predicate pred?

p-himik16:03:39

Pretty sure there isn't. After all, we probably wouldn't have not-empty otherwise. :)

DrLjótsson16:03:24

That makes sense. I stumbled upon this and thought there must be an easier way. Then again, I don’t think I’ve encountered it before so it may not be a super common pattern.

DrLjótsson16:03:43

I am a frequent user of not-empty though

p-himik16:03:26

Right. Just superficially checked a project that has ~50kloc for \(when \([a-z0-9-]+\?. Only 2 instances where it's used in combination with let, and both of those wouldn't benefit for an extra macro because the predicate depends on other data, so it would have to become a lambda. Both cases are something like

(let [item (get-item)]
  (when (contains? some-set item)
    item))

👍 2
kwladyka16:03:31

when is good. The alternative can be:

(?get-item ...)
which return item or nil depends on conditions inside the fn ?get-item. Then it is 1 line instead of 3, but this is much different fn

kwladyka16:03:24

so later everywhere in the code you use ?get-item instead of get-item

p-himik16:03:04

"Everywhere" - exactly in 2 locations. :) Not worth it.

p-himik16:03:13

New things have to be justified.

kwladyka16:03:19

unless you use it in 30 places 🙂

kwladyka16:03:36

I was talking about the concept, not specific use case

👍 2
kwladyka16:03:26

and of course get-iteam is poor name, it should be like ?get-availalbe-item then or something like that

DrLjótsson16:03:08

Wouldn’t have to be a macro though. Nevertheless, I agree that there is probably little need

kwladyka16:03:33

no macro, normal fn

Ben Sless17:03:30

Proposing a new macro, when-let->

skylize15:03:45

Since foo must be evaluated regardless, and (pred? foo) must also be evaluated regardless, there is no reason this needs to be a macro. It is trivial to capture the logic in a simple reusable function.

(defn when? [pred x]
  (when (pred x) x))

(let [pred boolean
      bar identity]
  {:truthy (when? pred (bar :foo))
   :falsey (when? pred (bar false))})

; =>
{:truthy :foo,
 :falsey nil}

👍 4