[#18]: cleaning up stale states
stale running instances are going into limbo on redefs caused by recompiling namespaces with defstates, tn/refresh, tn/refresh-all, etc. cleaning those stale instances with their stop functions before redefining their states
This commit is contained in:
parent
8e5926ac75
commit
ead12a7e88
2 changed files with 37 additions and 2 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
|
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
|
||||||
(defonce ^:private state-seq (atom 0))
|
(defonce ^:private state-seq (atom 0))
|
||||||
(defonce ^:private state-order (atom {}))
|
(defonce ^:private state-order (atom {}))
|
||||||
|
(defonce ^:private running (atom {})) ;; to clean dirty states on redefs
|
||||||
|
|
||||||
;; supporting tools.namespace: (disable-reload!)
|
;; supporting tools.namespace: (disable-reload!)
|
||||||
(alter-meta! *ns* assoc ::load false) ;; to exclude the dependency
|
(alter-meta! *ns* assoc ::load false) ;; to exclude the dependency
|
||||||
|
|
@ -31,15 +32,34 @@
|
||||||
(defn- with-ns [ns name]
|
(defn- with-ns [ns name]
|
||||||
(str ns "/" name))
|
(str ns "/" name))
|
||||||
|
|
||||||
|
(defn- pounded? [f]
|
||||||
|
(let [pound "(fn* [] "] ;;TODO: think of a better (i.e. typed) way to distinguish #(f params) from (fn [params] (...)))
|
||||||
|
(.startsWith (str f) pound)))
|
||||||
|
|
||||||
|
(defn- unpound [f]
|
||||||
|
(if (pounded? f)
|
||||||
|
(nth f 2) ;; magic 2 is to get the body => ["fn*" "[]" "(fn body)"]
|
||||||
|
f))
|
||||||
|
|
||||||
|
(defn- cleanup-if-dirty
|
||||||
|
"in case a namespace is recompiled without calling (mount/stop),
|
||||||
|
a running state instance will still be running.
|
||||||
|
this function stops this 'lost' state instance.
|
||||||
|
it is meant to be called by defstate before defining a new state"
|
||||||
|
[state]
|
||||||
|
(when-let [stop (@running state)]
|
||||||
|
(stop)))
|
||||||
|
|
||||||
(defmacro defstate [state & body]
|
(defmacro defstate [state & body]
|
||||||
(let [[state params] (macro/name-with-attributes state body)
|
(let [[state params] (macro/name-with-attributes state body)
|
||||||
{:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)]
|
{:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)]
|
||||||
(validate lifecycle)
|
(validate lifecycle)
|
||||||
|
(cleanup-if-dirty (with-ns *ns* state))
|
||||||
(let [s-meta (cond-> {:mount-state mount-state
|
(let [s-meta (cond-> {:mount-state mount-state
|
||||||
:order (make-state-seq (with-ns *ns* state))
|
:order (make-state-seq (with-ns *ns* state))
|
||||||
:start `(fn [] ~start)
|
:start `(fn [] ~start)
|
||||||
:status #{:stopped}}
|
:status #{:stopped}}
|
||||||
stop (assoc :stop `(fn [] ~stop))
|
stop (assoc :stop `(fn [] ~(unpound stop)))
|
||||||
suspend (assoc :suspend `(fn [] ~suspend))
|
suspend (assoc :suspend `(fn [] ~suspend))
|
||||||
resume (assoc :resume `(fn [] ~resume)))]
|
resume (assoc :resume `(fn [] ~resume)))]
|
||||||
`(defonce ~(with-meta state (merge (meta state) s-meta))
|
`(defonce ~(with-meta state (merge (meta state) s-meta))
|
||||||
|
|
@ -50,7 +70,7 @@
|
||||||
(swap! done conj (ns-resolve ns name))
|
(swap! done conj (ns-resolve ns name))
|
||||||
state))
|
state))
|
||||||
|
|
||||||
(defn- up [var {:keys [ns name start resume status] :as state} done]
|
(defn- up [var {:keys [ns name start stop resume status] :as state} done]
|
||||||
(when-not (:started status)
|
(when-not (:started status)
|
||||||
(let [s (try (if (:suspended status)
|
(let [s (try (if (:suspended status)
|
||||||
(record! state resume done)
|
(record! state resume done)
|
||||||
|
|
@ -58,6 +78,7 @@
|
||||||
(catch Throwable t
|
(catch Throwable t
|
||||||
(throw (RuntimeException. (str "could not start [" name "] due to") t))))]
|
(throw (RuntimeException. (str "could not start [" name "] due to") t))))]
|
||||||
(intern ns (symbol name) s)
|
(intern ns (symbol name) s)
|
||||||
|
(swap! running assoc (with-ns ns name) stop)
|
||||||
(alter-meta! var assoc :status #{:started}))))
|
(alter-meta! var assoc :status #{:started}))))
|
||||||
|
|
||||||
(defn- down [var {:keys [ns name stop status] :as state} done]
|
(defn- down [var {:keys [ns name stop status] :as state} done]
|
||||||
|
|
@ -68,6 +89,7 @@
|
||||||
(catch Throwable t
|
(catch Throwable t
|
||||||
(throw (RuntimeException. (str "could not stop [" name "] due to") t)))))
|
(throw (RuntimeException. (str "could not stop [" name "] due to") t)))))
|
||||||
(intern ns (symbol name) (NotStartedState. name)) ;; (!) if a state does not have :stop when _should_ this might leak
|
(intern ns (symbol name) (NotStartedState. name)) ;; (!) if a state does not have :stop when _should_ this might leak
|
||||||
|
(swap! running dissoc (with-ns ns name))
|
||||||
(alter-meta! var assoc :status #{:stopped})))
|
(alter-meta! var assoc :status #{:stopped})))
|
||||||
|
|
||||||
(defn- sigstop [var {:keys [ns name suspend resume status] :as state} done]
|
(defn- sigstop [var {:keys [ns name suspend resume status] :as state} done]
|
||||||
|
|
|
||||||
13
test/check/cleanup_dirty_states_test.clj
Normal file
13
test/check/cleanup_dirty_states_test.clj
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
(ns check.cleanup_dirty_states_test
|
||||||
|
(:require [mount.core :as mount]
|
||||||
|
[app]
|
||||||
|
[clojure.test :refer :all]))
|
||||||
|
|
||||||
|
(deftest cleanup-dirty-states
|
||||||
|
(let [_ (mount/start)]
|
||||||
|
(is (not (.isClosed (:server-socket app/nrepl))))
|
||||||
|
(require 'app :reload)
|
||||||
|
(mount/start) ;; should not result in "BindException Address already in use" since the clean up will stop the previous instance
|
||||||
|
(is (not (.isClosed (:server-socket app/nrepl))))
|
||||||
|
(mount/stop)
|
||||||
|
(is (instance? mount.core.NotStartedState app/nrepl))))
|
||||||
Loading…
Reference in a new issue