From ead12a7e883901338459fb4445542f1c4bc27af6 Mon Sep 17 00:00:00 2001 From: anatoly Date: Mon, 30 Nov 2015 13:00:45 -0500 Subject: [PATCH] [#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 --- src/mount/core.clj | 26 ++++++++++++++++++++++-- test/check/cleanup_dirty_states_test.clj | 13 ++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/check/cleanup_dirty_states_test.clj diff --git a/src/mount/core.clj b/src/mount/core.clj index ba777f6..19125be 100644 --- a/src/mount/core.clj +++ b/src/mount/core.clj @@ -5,6 +5,7 @@ (defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files (defonce ^:private state-seq (atom 0)) (defonce ^:private state-order (atom {})) +(defonce ^:private running (atom {})) ;; to clean dirty states on redefs ;; supporting tools.namespace: (disable-reload!) (alter-meta! *ns* assoc ::load false) ;; to exclude the dependency @@ -31,15 +32,34 @@ (defn- with-ns [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] (let [[state params] (macro/name-with-attributes state body) {:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)] (validate lifecycle) + (cleanup-if-dirty (with-ns *ns* state)) (let [s-meta (cond-> {:mount-state mount-state :order (make-state-seq (with-ns *ns* state)) :start `(fn [] ~start) :status #{:stopped}} - stop (assoc :stop `(fn [] ~stop)) + stop (assoc :stop `(fn [] ~(unpound stop))) suspend (assoc :suspend `(fn [] ~suspend)) resume (assoc :resume `(fn [] ~resume)))] `(defonce ~(with-meta state (merge (meta state) s-meta)) @@ -50,7 +70,7 @@ (swap! done conj (ns-resolve ns name)) 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) (let [s (try (if (:suspended status) (record! state resume done) @@ -58,6 +78,7 @@ (catch Throwable t (throw (RuntimeException. (str "could not start [" name "] due to") t))))] (intern ns (symbol name) s) + (swap! running assoc (with-ns ns name) stop) (alter-meta! var assoc :status #{:started})))) (defn- down [var {:keys [ns name stop status] :as state} done] @@ -68,6 +89,7 @@ (catch Throwable 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 + (swap! running dissoc (with-ns ns name)) (alter-meta! var assoc :status #{:stopped}))) (defn- sigstop [var {:keys [ns name suspend resume status] :as state} done] diff --git a/test/check/cleanup_dirty_states_test.clj b/test/check/cleanup_dirty_states_test.clj new file mode 100644 index 0000000..73fd0a8 --- /dev/null +++ b/test/check/cleanup_dirty_states_test.clj @@ -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))))