Initial debugging steps
Before creating a report, especially around exceptions being thrown when running Leiningen, please check if the error still occurs after:
Describe the bug
lein test can reload namespaces out of order. This can leave deftypes implementing expired protocol interfaces, and thus an exception will be thrown when attempting to invoke a protocol method.
This is also a potential performance problem, since a namespace maybe reloaded twice.
To Reproduce
A possible cause of this bug is that lein test uses clojure.core/require's :reload flag.
Working backwards from there, a simple way to make require :reload reload things out of order is to call
where A depends on B. B will be loaded twice, first as a dependency of A, then via the :reload logic.
If B contains a protocol and A contains a deftype, then the deftype will implement a now-stale interface.
This scenario is demonstrated here, and can be triggered with lein test.
The concrete scenario involves 2 namespaces:
(ns lein-test-reload-bug.a-deftype
(:require [lein-test-reload-bug.b-protocol
:refer [B]]))
(deftype A []
B
(b [this]))
(ns lein-test-reload-bug.b-protocol)
(defprotocol B
(b [this]))
Notice how this matches the hypothetical scenario, except
- A = lein-test-reload-bug.a-deftype
- B = lein-test-reload-bug.b-protocol
The following require is called by lein test:
(require :reload 'lein-test-reload-bug.a-deftype
'lein-test-reload-bug.b-protocol)
Actual behavior
lein-test-reload-bug.b-protocol is loaded twice, the second time is after lein-test-reload-bug.a-deftype, thus leaving it in a bad state.
It is now impossible to make instances of A that can be called via the protocol, because it implements an old protocol.
Relevant output from sample project:
lein test lein-test-reload-bug.core-test
"The current hash of interface lein_test_reload_bug.b_protocol.B is" 1214133948
"The current instance of A implements lein_test_reload_bug.b_protocol.B with hash" -1634164376
ERROR in (a-test) (core_deftype.clj:583)
Uncaught exception, not in assertion.
expected: nil
actual: java.lang.IllegalArgumentException: No implementation of method: :b of protocol: #'lein-test-reload-bug.b-protocol/B found for class: lein_test_reload
_bug.a_deftype.A
at clojure.core$_cache_protocol_fn.invokeStatic (core_deftype.clj:583)
clojure.core$_cache_protocol_fn.invoke (core_deftype.clj:575)
lein_test_reload_bug.b_protocol$eval418$fn__419$G__409__424.invoke (b_protocol.clj:5)
lein_test_reload_bug.core_test$fn__448.invokeStatic (core_test.clj:39)
<SNIP>
See lein-test-reload-bug.core-test/a-test for exact test setup, but it boils down to the familiar "Foo is not Foo" problem.
Expected behavior
lein-test-reload-bug.b-protocol is loaded exactly once.
Link to sample project
https://github.com/frenchy64/lein-test-reload-bug
Logs
Environment
- Leiningen Version:
Leiningen 2.9.5 on Java 1.8.0_275 OpenJDK 64-Bit Server VM
- Leiningen installation method: manual
- JDK Version:
openjdk version "1.8.0_275"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_275-b01)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.275-b01, mixed mode)
Additional context
Initial debugging steps
Before creating a report, especially around exceptions being thrown when running Leiningen, please check if the error still occurs after:
lein upgrade).~/.lein/profiles.clj(if present) out of the way. This contains third-party dependencies and plugins that can cause problems inside Leiningen.project.clj, especially if the problem is with a plugin not working. Old versions of plugins like nREPL and CIDER (as well as others) can cause problems with newer versions of Leiningen.Describe the bug
lein testcan reload namespaces out of order. This can leave deftypes implementing expired protocol interfaces, and thus an exception will be thrown when attempting to invoke a protocol method.This is also a potential performance problem, since a namespace maybe reloaded twice.
To Reproduce
A possible cause of this bug is that
lein testuses clojure.core/require's :reload flag.Working backwards from there, a simple way to make
require :reloadreload things out of order is to callwhere A depends on B. B will be loaded twice, first as a dependency of A, then via the
:reloadlogic.If B contains a protocol and A contains a deftype, then the deftype will implement a now-stale interface.
This scenario is demonstrated here, and can be triggered with
lein test.The concrete scenario involves 2 namespaces:
Notice how this matches the hypothetical scenario, except
The following require is called by
lein test:Actual behavior
lein-test-reload-bug.b-protocolis loaded twice, the second time is afterlein-test-reload-bug.a-deftype, thus leaving it in a bad state.It is now impossible to make instances of A that can be called via the protocol, because it implements an old protocol.
Relevant output from sample project:
See
lein-test-reload-bug.core-test/a-testfor exact test setup, but it boils down to the familiar "Foo is not Foo" problem.Expected behavior
lein-test-reload-bug.b-protocolis loaded exactly once.Link to sample project
https://github.com/frenchy64/lein-test-reload-bug
Logs
Environment
Leiningen 2.9.5 on Java 1.8.0_275 OpenJDK 64-Bit Server VMAdditional context