stuartsierra / component

Managed lifecycle of stateful objects in Clojure
MIT License
2.09k stars 95 forks source link

Cljs source files requiring component break refresh for clj code #45

Closed zonotope closed 7 years ago

zonotope commented 8 years ago

If you call (clojure.tools.namespace.repl/refresh) in a project repl which contains clj and cljs source that both require com.stuartsierra.component, the function call is reported as successful, but it doesn't seem as if any namespaces are actually reloaded after they're unloaded.

The quickest way to reproduce the problem is using the duct template:

$ lein new duct tester +cljs +site
$ cd tester
$ lein setup

Then put the following in src/cljs/tester/core.cljs

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

Then, back at the shell:

$ lein repl
user=> (go)

Visiting localhost:3000 gives a welcome page, but after calling (do (stop) (refresh) (start)) at the repl, no connections to the web server can be established even though all the functions return either :ok or :started with no error.

If you kill the repl, run lein clean, and modify the above clojurescript source so that the component library isn't required, everything works as expected.

It looks like the problem comes from unloading and reloading the component library while there is compiled clojurescript code that uses component on the classpath. If I modify clojure.tools.namespace.reload/track-reload-one to skip both unloading and reloading the component namespace during refresh calls, everything works fine then.

stuartsierra commented 8 years ago

At first read, this looks like a tools.namespace bug, not anything related to 'Component'.

What version of tools.namespace are you using?

.cljc support was added to tools.namespace in the 0.3.0-alpha series. There are still open bugs for cljc support.

zonotope commented 8 years ago

I'm using version 0.2.1 of clojure.tools.namespace but, to be clear, I'm not trying reload a ClojureScript system or using a ClojureScript repl; I'm trying to reload a back-end Clojure system from the Clojure repl, but that system has compiled (and static) cljs sources requiring component on the classpath. This is a ring webapp that serves compiled cljs as static resources.

I initially thought this was a bug with c.t.namespace, but I tried requiring other cross platform libs (bouncer and your dependency lib) in my cljs source, and my minimal system didn't exhibit the problem. Component is the only library I've found that kills the Clojure system if it's required in ClojureScript source.

I will give 0.3.0-alpha of c.t.namespace a shot. Is there a bug tracker for that project?

jafingerhut commented 8 years ago

@zonotope Bug tracker for tools.namespace project is here: http://dev.clojure.org/jira/browse/TNS

stuartsierra commented 8 years ago

A second guess I have is that a ClojureScript build process is copying .cljc files into a resources directory, thereby confusing tools.namespace's file tracker. It may be possible to fix this by explicitly setting the directories tools.namespace scans with clojure.tools.namespace.repl/set-refresh-dirs.

I still don't know why 'Component' specifically would be affected differently from other libraries.

jcf commented 8 years ago

I'm compiling Clojurescript files into my resources directly, and can confirm setting refresh directories resolved the issue for me.

(clojure.tools.namespace.repl/set-refresh-dirs "src" "test")

Thank you, @stuartsierra.

Update: Only temporarily. After running another refresh my component system is broken again.

Update 2: If I disable reloading of the following namespaces things look good again.

(doseq [sym ['com.stuartsierra.component
             'com.stuartsierra.dependency]
        :let [ns (find-ns sym)]
        :when ns]
  (clojure.tools.namespace.repl/disable-reload! ns))
stuartsierra commented 8 years ago

I have not been able reproduce this issue so far. There are so many variables in how projects get set up. And a description like "my component system is broken" doesn't help me much.

If, as I suspect, this is caused by the interaction of tools.namespace and the ClojureScript compiler, then there is likely nothing I can do about it except add a warning to the documentation.

If someone can provide an isolated test case, I would appreciate it. Here's what I need: a complete sample project with only the minimum pieces necessary to reproduce this problem and precise instructions for running it to provoke the failure. Make it as small as possible. Please omit anything that can be omitted and still demonstrate the failure: other libraries, frameworks, tools, plugins, templates, or application code.

jcf commented 8 years ago

Hi @stuartsierra,

I apologise for not providing something easier to work with in my previous comment. I thought I'd found a solution and wanted to share it with others in case it could help someone out.

I don't have a sample project to share (yet), but I have been able to identify a repeatable process that corrupts my system by going from this. It involves refreshing namespaces via Cider's cider-refresh (I'm jacking in via cider-jack-in).

What I mean by corrupts/breaks is the dependencies are all nil, and only values I merge in with merge-with merge are present:

#inno.listener.Listener{:clients nil, :host "0.0.0.0", :port 3000, :server nil, :socket nil, :session nil}

The listener component should have a :server and :session; both of which come from component/system-using:

(defn make-system
  []
  (component/system-using
   (component/system-map
    :bootstrapper (bootstrap/map->Bootstrapper {})
    :listener (listener/map->Listener {})
    :timbre (timbre/map->Logger {})
    :session (listener/map->Session {})
    :socket (listener/map->Socket {}))
   {:bootstrapper [:logger]
    :listener [:session :socket]}))

I'll cut up my project and see if I can get a minimal test case to surface exactly what the problem is (I think it may be down to Cider's refresh as running (clojure.tools.namespace.repl/refresh) works as expected).

In fact, now I vaguely remember an issue with the way Cider vendors its dependencies that meant I couldn't set refresh dirs in the past… I'll post back when I have more info.

https://github.com/clojure-emacs/cider-nrepl/blob/aeb8745d650721d9b91e0f659618b0cc9473d429/src/cider/nrepl/middleware/refresh.clj

Update: Even with refresh dirs set in my dev/user.clj Cider is reloading dependencies it shouldn't be.

cider-refresh: Reloading (inno.ws inno.bootstrap taoensso.sente.interfaces taoensso.sente.packers.transit taoensso.sente inno.listener inno.timbre inno.system inno.config user inno.main)

The Sente namespaces should not be reloaded.

stuartsierra commented 8 years ago

Possibly-related tools.namespace ticket: http://dev.clojure.org/jira/browse/TNS-45

zonotope commented 7 years ago

@stuartsierra I can confirm that the problem I am seeing is due to that tools.namespace ticket you linked above.

The suggested workaround using set-refresh-dirs fixes the problem for me. Executing the following form from my user namespace (or any other namespace loaded by the repl) works for me:

(clojure.tools.namespace.repl/set-refresh-dirs "dev" "src" "test")

I am closing this issue as it is not a component bug.