Fork me on GitHub
#clojure
<
2024-04-24
>
Anton Shastun18:04:39

Hello! Do any one know how to stop task execution in promesa?

(def task
 (p/vthread
  (loop []
   (println "x…")
   (p/sleep 500)
   (recur))))
p/vthread returns CompletableFuture

jpmonettas19:04:06

I don't think there is a way of interrupting a loop inside a CompletableFuture without explicitly checking a flag before doing the (recur). Checking (Thread/interrupted) doesn't work either because the cancel don't interrupt the future thread.

jpmonettas19:04:56

You can do it with normal Clojure futures like this :

clj
Clojure 1.11.1
user=> (def f (future (loop [] (println "x") (Thread/sleep 1000) (when-not (Thread/interrupted) (recur)))))
x
x
x
(future-cancel f)
true
user=> 

jgomez19:04:10

The code has to be cooperative

jgomez19:04:42

But there are some facilities in there to detect if your async task has been canceled

jgomez19:04:58

Similar to thread/isInterrupted

jgomez19:04:07

You can use a Fiber pool with it and it effectively works the same, ofc with core async semantics instead of promesa so use at your own risk

Anton Shastun07:04:22

@U0739PUFQ Thanks! but for clojure.core/future as i can see there is no need to check (Thread/interrupted) (future-cancel task) interrupt task execution

Anton Shastun07:04:25

@U07MK6PLN Thank you! cool lib, but it look like a bit overhead for my task

jpmonettas10:04:17

@U04410FUMCL you do need the (Thread/interrupted) check if you don't have the Thread/sleep. Something like this will not cancel :

user=> (def a (atom 0))
#'user/a
user=> (def f (future (loop [] (swap! a inc) (recur))))
#'user/f
user=> @a
69724592
user=> (future-cancel f)
true
user=> @a
620767611
user=> @a
653178836
user=> @a
679249561
There is no way to stop a loop non cooperatively in the JVM since the https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/doc-files/threadPrimitiveDeprecation.html

Anton Shastun10:04:14

@U0739PUFQ thank you, thats interesting!

jgomez15:04:07

@U04410FUMCL the problem I faced with this, is that if you cancel a CompletableFuture, it does not interrupt the underlying Thread upon which the Future that will eventually complete the completable future is running. So if your code returns a completable future, it needs to be wired up to perform that cancelation if the CompletableFuture fails (by cancellation or some other means)

jgomez15:04:02

there are other edge cases as well when you have nested calls to completable futures, but at a simplest level if you just need to cancel the task you started, your code needs to be cooperative and the library code that returns the completable future needs to do something like this: https://github.com/k13labs/futurama/blob/main/src/futurama/core.clj#L189

Anton Shastun07:04:37

@U0739PUFQ @U07MK6PLN i think i will go with this

(def task
 (let [t (p/deffered)]
  (p/vthread
    #(while (p/pendding? t)
      (println "x…")
      (p/sleep 500)))
  t))

(p/cancel! task)

🙌 1
teodorlu20:04:15

Is there a neat way to list all functions from the babashka.fs namespace that I’m using in my Clojure project? (I suppose i could try to grep for fs/ or something similar, but that would give flaky results, and I wonder if this problem perhaps has been solved before)

borkdude20:04:18

you could use the clj-kondo analysis for this

teodorlu20:04:00

You’re right. I’ll the docs. Thank you!

borkdude20:04:52

$ clj-kondo --lint src --config '{:analysis true :output {:format :edn}}' | bb -e '(def ana (edn/read-string (slurp *in*))) (prn (->> ana :analysis :var-usages (filter #(= (:to %) (symbol "babashka.fs")))))' | jet

👀 2
😮 1
💪 3
borkdude20:04:36

(I could have done this with jet alone, but details)

👍 1
teodorlu20:04:06

This is amazing. Thank you! 💯 🙏 I was able to make your very long line even longer:

$ clj-kondo --lint src --config '{:analysis true :output {:format :edn}}' | bb -e '(def ana (edn/read-string (slurp *in*))) (prn (->> ana :analysis :var-usages (filter #(= (:to %) (symbol "babashka.fs"))) (map :name) (into #{}) sort))' | jet
(canonicalize
 create-dirs
 delete-if-exists
 directory?
 exists?
 file
 file-name
 file-time->millis
 glob
 last-modified-time
 list-dir
 which
 xdg-config-home)

👍 2
borkdude20:04:52

frequencies (sort-by second) reverse
could also be interesting, the most used ones: I'm getting:
([exists? 22]
 [file 11]
 [file-separator 10]
 [parent 8]
 [path 8]
 [create-dirs 6]
 [extension 5]
 [read-all-bytes 5]
 [directory? 3]
 [unixify 3]
 [delete-tree 3]
 [cwd 3]
 [strip-ext 2]
 [glob 2]
 [canonicalize 2]
 [relativize 2]
 [components 1]
 [absolutize 1]
 [delete-if-exists 1]
 [normalize 1]
 [absolute? 1]
 [windows? 1])

teodorlu20:04:10

Yes, count is nice! Here’s mine (edited to include cli and process):

$ clj-kondo --lint src --config '{:analysis true :output {:format :edn}}' | bb -e '(def ana (edn/read-string (slurp *in*))) (prn (->> ana :analysis :var-usages (filter #('"'"'#{babashka.fs babashka.process babashka.cli} (:to %))) (map (juxt :to :name)) frequencies (sort-by second) reverse))' | jet
([[babashka.fs file] 7]
 [[babashka.fs exists?] 6]
 [[babashka.fs file-name] 3]
 [[babashka.fs canonicalize] 3]
 [[babashka.fs last-modified-time] 3]
 [[babashka.fs list-dir] 3]
 [[babashka.process shell] 3]
 [[babashka.fs create-dirs] 2]
 [[babashka.cli coerce] 2]
 [[babashka.fs xdg-config-home] 2]
 [[babashka.fs which] 2]
 [[babashka.process process] 1]
 [[babashka.fs delete-if-exists] 1]
 [[babashka.cli dispatch] 1]
 [[babashka.process tokenize] 1]
 [[babashka.fs directory?] 1]
 [[babashka.fs file-time->millis] 1]
 [[babashka.fs glob] 1])

👍 1