If you AOT protocol call sites, then you will also need to AOT the protocol itself
Description
Environment
Attachments
Activity

Stuart Halloway September 7, 2017 at 10:33 PM
See description for explanation of the problem and possible workarounds.

import March 15, 2017 at 12:15 AM
Comment made by: mikerod
Isn't the issue just that the "clj" source files also need to be included on the runtime classpath in order to be JITed when needed - since they were not AOTed?
i.e.
Why would the expectation be that "target" contains everything needed by the classpath when the AOT compilation was not done on everything? I don't get a failure when I add this step here.
The AOTed code looks fine. It just calls a `require` on the `myrecord` ns to JIT it. So I'm guessing I'm not seeing something else about this general problem statement.

John Szakmeister October 27, 2016 at 10:54 AM
I just spent quite a bit of time tracking down what I thought might be a variant of this problem. I've been trying to use Colin Fleming's new gradle-clojure plugin and was running into issues with the resulting shadow jar (the equivalent of an uberjar). At the time I believed it to be a problem in the gradle-clojure plugin, but it turned out to be a different issue. The shadow plugin was not preserving the last modified time on files extracted from dependencies, and it resulted in some source files looking newer than the class files. I suspect that Clojure was then recompiling the class, thinking it was out-of-date. This was nasty to track down and quite unexpected, but I can see the sense in the behavior now that I know what's going on. I'm not sure if anything should be done on the Clojure side, but it points to a problem with including the source and AOT files together--you really need to make sure the timestamps are kept intact and I can see that fact being easily overlooked. In this case, it was a plugin completely unrelated to Clojure that had to be fixed. I should also add that taking the infectious approach Stuart mentions is probably an issue in the Gradle and (possibly?) Maven environments, since there are separate plugins for packaging the uberjar.
FWIW, I have a fair amount of information in the ticket for gradle-clojure about the failure mode and the steps I went through to try and track down the problem: https://github.com/cursive-ide/gradle-clojure/issues/8. Also, I've put a pull request in on the shadow plugin to help keep the timestamps intact: https://github.com/johnrengelman/shadow/pull/260. There is also an issue in the shadow plugin describing the problem too: https://github.com/johnrengelman/shadow/issues/259.

import August 15, 2016 at 8:24 PM
Comment made by: sperber
This problem occurs within the compilation of a single library/project, so I don't think this can be solved by simple usages of Leiningen or Boot.

Stuart Halloway August 15, 2016 at 7:35 PM
This is actually a runtime problem, not a compilation problem.
AOTed Clojure code cannot see on-the-fly Clojure classes (vars are fine), because of the rules of Java classloader delegation. The on-the-fly code is loaded by a DynamicClassLoader, which delegates up to the classpath loader for the AOTed code. This is a particular problem when AOT code wants to consume a protocol from source, because protocols necessitate a class-level (not just var-level) relationship.
The person who runs a particular Clojure app can solve this problem by making sure their own consumption of AOT compilation is "infectious", i.e. if you want to AOT-compile library A which uses library B, then you need to AOT-compile library B as well.
I think that attempts to have the Clojure compiler magically implement the "infectious" rule above will cause more problems than they solve, and that we should close this ticket and provide good guidance for tools like lein and boot.
Details
Assignee
UnassignedUnassignedReporter
Allen RohnerAllen RohnerLabels
Approval
IncompletePatch
CodePriority
CriticalFix versions
Details
Details
Assignee
Reporter

Closed as not a bug--Happy to help improve the usability of the advice below. (@stuarthalloway Sept 2017)
Background
Clojure's DynamicClassLoader allows Clojure code (including Java classes) to be dymamically reloaded during development.
Clojure allows Ahead-Of-Time (AOT) compilation into class files. AOTed code will e.g. load faster but does not support the dynamic reloading approach described above.
Clojure protocols provide fast dispatch by creating a Java interface, and by directly using that Java interface at protocol call sites.
Java's classloader delegation model means that protocol call sites must be able to "see" protocol implementations via the classloader delegation.
Problem
If you have an AOTed protocol call site, you must also AOT the protocol itself. This is irritating, but it is not a bug. It is a predictable consequence of the design choices above, but it is surprising because the Java-level symptom stems not from something you did directly, but from an implementation detail of protocols.
A minimal reproducible case is described in the following comment: http://dev.clojure.org/jira/browse/CLJ-1544?focusedCommentId=36734&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-36734
Other examples:
https://github.com/arohner/clj-aot-repro
A real issue triggered by this bug: https://github.com/cemerick/austin/issues/23
Avoiding the Problem:
If you have an AOT compiled protocol call site, make sure that you AOT the protocol itself. This is true regardless of when the problem occurs, e.g. dev-time vs. build-time vs. runtime. Build-space tools could be augmented to be smart about this, e.g. detect the situation, show what needs to be compiled, etc.
Not a Problem:
Some of the repros attached to this ticket show a similar symptom when trying to directly use defrecord or deftype class names, e.g.
https://github.com/methylene/class-not-found
Deftype/defrecord references can and should be avoided by looser coupling: Instead of naming the deftype or defrecord class directly, use a factory function. Note that deftype and defrecord always make a factory function for you.
History
This ticket includes a patch that papers over the issue by magically reloading namespaces during AOT compilation if no matching classfile is found in the compile-path or in the classpath. This "cure" would be worse than the disease.
Related ticket: CLJ-1641 contains descriptions and comments about some potentially unwanted consequences of applying proposed patch 0001-CLJ-1544-force-reloading-of-namespaces-during-AOT-co-v3.patch