Gracefully exit a Clojure core.async go loop on kill
Asked Answered
E

3

10

I have a top-level core.async go loop. I want it to run indefinitely, at least until I signal it to stop with CTRL-C or kill or similar. I'm currently using java.lang.Runtime/addShutdownHook like this:

(ns async-demo.core
  (:require [clojure.core.async :as async
             :refer [<! >! <!! timeout chan alt! go]]))
(defn run [] (go (loop [] (recur))))
(.addShutdownHook (Runtime/getRuntime) (Thread. #(println "SHUTDOWN")))

Here are my problems:

  1. If I start the REPL and (run) then it starts and runs in a background thread. When I exit the REPL, I don't see the desired shutdown message.

  2. However, when I run from lein run, the go loop exits immediately and displays "SHUTDOWN".

Neither is what I want.

I don't necessarily expect to find a solution that works for all JVM's. I develop on a Mac and deploy to Ubuntu, so I'd like to find a solution that works for both:

  • Mac JVM: java version "1.7.0_45" Java(TM) SE Runtime Environment (build 1.7.0_45-b18) Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

  • Ubuntu JVM: java version "1.7.0_25" OpenJDK Runtime Environment (IcedTea 2.3.10) (7u25-2.3.10-1ubuntu0.12.04.2) OpenJDK 64-Bit Server VM (build 23.7-b01, mixed mode)

Exscind answered 10/12, 2013 at 2:56 Comment(2)
See also: #11710139Exscind
See also: #18612563Exscind
S
-2

go function returns a channel. You may want to (close! chan) in shutdown hook.

If you run lein run you need a main function that will call (run) to start go thread.

(ns async-demo.core
  (:require [clojure.core.async :as async
             :refer [<! >! <!! timeout chan alt! go close!]]))

(def ch (atom nil))

(defn run []
  (go (while true
        (<! (timeout 500))
        (prn "inside go"))))

(defn -main [& args]
  (println "Starting")
  (reset! ch (run))
  (.addShutdownHook (Runtime/getRuntime)
                    (Thread. #(do
                                (println "SHUTDOWN")
                                (close! @ch))))
  (while true
    (<!! @ch)))
Sapele answered 10/12, 2013 at 11:27 Comment(4)
Are you seeing the shutdown message when you (a) run from the REPL; (b) run via lein run?Exscind
There is no shutdown message in case (a) lein repl, case (b) lein run does display shutdownSapele
If I run lein trampoline repl shutdown display when I exit with Ctrl-D or using (exit) functionSapele
I don't understand why it's an accepted answer. It's wrong because closing a channel returned by go from outside doesn't stop while-loopRacketeer
A
13

You can use a signalling exit-ch and do a conditional to see if you should exit the loop. The channel + conditional is better handled in clojure by alt!:

(ns async-demo.core
  (:require [clojure.core.async :refer [chan go-loop alt! timeout put!]]))

(defn run
  []
  (let [exit-ch (chan)]
    (go-loop []
      (alt!
         (timeout 10) (recur)
         exit-ch nil
    exit-ch

(let [exit-ch (run)]
  (.addShutdownHook (Runtime/getRuntime) (put! exit-ch true)))

If you just close! the channel, it won't stop the loop from running. Also, don't need to start a go block just to send a value to a channel.

Arithmetic answered 30/11, 2018 at 14:30 Comment(0)
E
-1

Regarding part 1: "When I exit the REPL, I don't see the desired shutdown message." I think the shutdown thread isn't connected to lein repl's console.

Regarding part 2: After the go loop is started, it runs in background threads. Since the main thread exits after creating the go block, the program shuts down. To make the loop long-lived it needs to be put in a normal loop. (It is also way nicer to put a Thread/sleep inside!)

Exscind answered 10/12, 2013 at 4:19 Comment(0)
S
-2

go function returns a channel. You may want to (close! chan) in shutdown hook.

If you run lein run you need a main function that will call (run) to start go thread.

(ns async-demo.core
  (:require [clojure.core.async :as async
             :refer [<! >! <!! timeout chan alt! go close!]]))

(def ch (atom nil))

(defn run []
  (go (while true
        (<! (timeout 500))
        (prn "inside go"))))

(defn -main [& args]
  (println "Starting")
  (reset! ch (run))
  (.addShutdownHook (Runtime/getRuntime)
                    (Thread. #(do
                                (println "SHUTDOWN")
                                (close! @ch))))
  (while true
    (<!! @ch)))
Sapele answered 10/12, 2013 at 11:27 Comment(4)
Are you seeing the shutdown message when you (a) run from the REPL; (b) run via lein run?Exscind
There is no shutdown message in case (a) lein repl, case (b) lein run does display shutdownSapele
If I run lein trampoline repl shutdown display when I exit with Ctrl-D or using (exit) functionSapele
I don't understand why it's an accepted answer. It's wrong because closing a channel returned by go from outside doesn't stop while-loopRacketeer

© 2022 - 2024 — McMap. All rights reserved.