component dependency cleanup problem

155 views
Skip to first unread message

Jochen

unread,
Jan 18, 2017, 9:28:24 AM1/18/17
to Clojure
Hi…

playing around with Stuart Sierras component library I found some behavior (code at end) that I find odd.
M sample has just two components, data and compute, where data is injected into compute. On start data associates a data map and on stop dissociates it.
compute uses a value from data's map to compute.

What I found is that after system stop the started version of data is still associated to compute, while the data component in SystemMap is stopped. So, when do

  (def my-system (make-my-system {:factor 2}))
  (alter-var-root #'my-system component/start-system)
  (alter-var-root #'my-system component/stop-system)
  (my-compute (:mycomputecomp my-system) 16)  

my-compute should (as data map is nil'ed on stop) crash but doesn't.

Calling

  (alter-var-root #'my-system component/stop-system)

again fixes it and my-compute crashes as expected.

Any ideas?

Ciao

…Jochen

(ns foo.core
  (:require [com.stuartsierra.component :as component]))

(defrecord MyDataComponent [data]
  component/Lifecycle
  (start [this]
    (println "Starting MyDataComponent")
    (assoc this :data {:foo 42}))
  (stop [this]
    (println "Stopping MyDataComponent")
    (dissoc this :data)))

(defn my-data-component []
  (->MyDataComponent nil))

(defn mydatacomp-foo [mydatacomp]
  (get-in mydatacomp [:data :foo]))


(defrecord MyComputeComponent [factor mydatacomp]
  component/Lifecycle
  (start [this]
    (println "Starting MyComputeComponent")
    this)
  (stop [this]
    (println "Stopping MyComputeComponent")
    this))

(defn my-compute-component [factor]
  (map->MyComputeComponent {:factor factor}))

(defn my-compute [mycomputecomp offset]
  (let [factor (:factor mycomputecomp)
        foo    (mydatacomp-foo (:mydatacomp mycomputecomp))]
    (+ (* factor foo) offset)))

(defn make-my-system [config-options]
  (let [{:keys [factor]} config-options]
    (component/system-map
      :mydatacomp (my-data-component)
      :mycomputecomp (component/using
                       (my-compute-component factor)
                       [:mydatacomp]))))


;;;;;; running ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(comment
  (def my-system (make-my-system {:factor 2}))
  (alter-var-root #'my-system component/start-system)
  (alter-var-root #'my-system component/stop-system)
  (my-compute (:mycomputecomp my-system) 16)
  )

Jose Figueroa Martinez

unread,
Jan 18, 2017, 11:56:37 AM1/18/17
to Clojure
Hello, your code is acting ok, as you are not dissocing the data component from the compute component during its "stop" method .

Remember, this is not object oriented programming. You removed the data from the data component, but did nothing to the compute component. That is the reason why the compute component still have a "copy" of the data component with the old data.

If you want the "correct" behavior (depends of what you want) then your compute component should look like this:


(defrecord MyComputeComponent [factor mydatacomp]
  component/Lifecycle
  (start [this]
    (println "Starting MyComputeComponent")
    this)
  (stop [this]
    (println "Stopping MyComputeComponent")
    (dissoc this :mydatacomp))  ;; or (assoc this :mydatacomp nil))


Saludos!

Jose Figueroa Martinez

unread,
Jan 18, 2017, 11:59:43 AM1/18/17
to Clojure
Hello, I forgot a parenthesis :-P


(defrecord MyComputeComponent [factor mydatacomp]
  component/Lifecycle
  (start [this]
    (println "Starting MyComputeComponent")
    this)
  (stop [this]
    (println "Stopping MyComputeComponent")
    (dissoc this :mydatacomp)))

Jochen

unread,
Jan 18, 2017, 1:05:03 PM1/18/17
to Clojure
Hi Jose…

thank you for the hint, indeed this fixes it. 

Ciao

…Jochen

P. S.
However, just for completeness, I still am a bit uncertain this is perfect behavior as (dissoc this :mydatacomp) fiddles with the dependencies which I understood should be managed by component. 

So, when I call component/stop-server twice, the dependency is pointing to the right (nil'ed data component so clearly component does something.
Looking inside the system map after a single stop-server call, (get-in my-system [:mydatacomp]) is the properly stopped data component, while under  (get-in my-system [:mycomputecomp :mydatacomp]) the unstopped component is still referenced. 
In other words, there are two dependency versions in the system-map at the same time.

Sean Corfield

unread,
Jan 18, 2017, 1:12:03 PM1/18/17
to Clojure Mailing List

Do not dissoc a base field from a record – instead assoc a nil value into it.

 

If you dissoc a base field, the record changes into a hash map.

 

Change:

 

                (dissoc this :data)

 

to:

 

                (assoc this :data nil)

 

Sean Corfield -- (970) FOR-SEAN -- (904) 302-SEAN
An Architect's View -- http://corfield.org/

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jochen

unread,
Jan 19, 2017, 4:22:32 AM1/19/17
to Clojure
Hi Sean…

thanks a lot, indeed, this is a good hint :-). 

Does not affect the stop strangeness, however.
I had a quick look into the code, namely update-system-reverse, and it looks to me like the deps update is a no-op here as the update code in the reduce is the same as in the is the update-system (forward).
If one is really interested in fixing the deps (who uses stopped components, anyway ;-), a second pass with 
  (alter-var-root #'my-system component/update-system (keys my-system) identity)
fixes it.

An inline fix is non trivial as it IMHO requires a assoc-dependencies-reverse function.

Ciao

…Jochen

joakim.t...@nova.com

unread,
Jan 20, 2017, 9:45:25 AM1/20/17
to Clojure
Hi there!

Another solution is to use the Micro Monolith Architecture to solve dependency problems
which also gets you a really awesome development experience!

Reply all
Reply to author
Forward
0 new messages