I've just started using agents and as I've done that, I've noticed that
once you use an agent, exiting the REPL leads to a hang (with zero CPU
usage). As Rich pointed out to me, this is avoided by calling
(shutdown-agents).
So my question is this: What's a simple way to ensure that
(shutdown-agents) is called when necessary without having to remember
to manually enter that call at the REPL before exiting?
Randall Schulz
I tried adding this to the module that starts the agents in question
(the question of the proper place for such code being left aside for
now):
(def *shutdown-hook* (atom nil))
(when-not @*shutdown-hook*
(let [shutdown-thread (new Thread shutdown-agents)]
(swap! *shutdown-hook* (fn [old] shutdown-thread))
(when (identical? @*shutdown-hook* shutdown-thread)
(.. Runtime (getRuntime) (addShutdownHook shutdown-thread))))
)
I think this is doing what I think it's doing (reliably registering only
one agent shutdown handler Thread), but exiting the REPL via CTRL-D
still hangs. I can't say it hangs literally forever, obviously, but for
longer than I'm willing to wait, certainly. So then it occurred to me
to try calling (System/exit 0), and that exits immediately. So then I
took out the code to set up the shutdown handler and found the
(System/exit 0) _still_ effects an immediate exit from the REPL.
Lastly, if I just execute Mike's code directly:
(let [shutdown-thread (new Thread shutdown-agents)]
(.. Runtime (getRuntime) (addShutdownHook shutdown-thread)))
the hang upon CTRL-D / EOF at the REPL still happens.
So I am still confused about the right way to deal with agents in order
to get reliable and prompt exit from the REPL and why an EOF from the
terminal hangs (only if agents were started and regardless of whether a
shutdown hook is in place) but (System/exit 0) does not hang (again,
regardless of whether agents were started and whether a shutdown hook
is in place).
What am I missing?
> Your question made me wonder if the agent thread pools should use
> daemon threads - then this wouldn't be an issue.
Yes. That seems plausible, but I don't know enough about the possible
subtleties to say for sure.
Randall Schulz
One last piece of evidence on this. If I directly call (shutdown-agents)
before exiting the REPL using CTRL-D, then Clojure exits promptly.
Randall Schulz
This sequence of evaluations ends in the REPL hanging:
1:1 user=> (.. Runtime (getRuntime) (addShutdownHook (new Thread shutdown-agents)))
nil
1:2 user=> (def agent-99 (agent "Secret agent (99)"))
#'user/agent-99
1:3 user=> (send-off agent-99 (fn [a] (println (str "In agent: " a))))
In agent: Secret agent (99)
#<Agent clojure.lang.Agent@5b0668>
1:4 user=> (await agent-99)
nil
1:5 user=>
CTRL-D
[ Hangs here. CTRL-C gets back to shell prompt. ]
Randall Schulz
Since Chouser asked on #clojure which REPL (I was using the Contrib
REPL), I tried this with the stock REPL and the hang does not occur. In
fact, with the stock REPL and _without_ registering the shutdown hook,
the hang also does _not_ occur.
So it would seem this is all about the Contrib REPL, not Agents or
shutdown hooks or interactions between them.
Randall Schulz
Specifically, this line causes the JVM to shut down, even if there are
agent threads in their pools:
http://code.google.com/p/clojure/source/browse/trunk/src/jvm/clojure/lang/Repl.java?r=1160#119
I don't see a similar line in clojure.main or the recent unification
patch.
--Chouser