Skip to content

Commit cf12fe4

Browse files
committed
Remove unnecessary with-context-classloader in compile.clj
1 parent 8efa263 commit cf12fe4

File tree

4 files changed

+112
-82
lines changed

4 files changed

+112
-82
lines changed

src/rules_clojure/compile.clj

Lines changed: 94 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
[rules-clojure.java.classpath :as cp]
66
[rules-clojure.namespace.parse :as parse]
77
[rules-clojure.fs :as fs]
8-
[rules-clojure.util :refer [with-context-classloader]])
9-
(:import [java.util.concurrent CompletableFuture]
10-
[java.security MessageDigest]))
8+
[rules-clojure.util :refer [shasum]])
9+
(:import [java.util.concurrent CompletableFuture]))
1110

1211
(set! *warn-on-reflection* true)
1312

@@ -17,35 +16,37 @@
1716
;; all users to upgrade so try to do that sparingly as well.
1817

1918
(defn debug [& args]
20-
#_(println (locking true (apply print-str args))))
19+
#_ (println (locking true (apply print-str args))))
20+
21+
(defn deref!
22+
"throw `ex` if *f does not complete within timeout"
23+
[*f timeout ex]
24+
{:pre [(future? *f)
25+
(integer? timeout)
26+
(instance? Throwable ex)]}
27+
(when (= ::timeout (deref *f timeout ::timeout))
28+
(throw ex)))
2129

2230
(defn src-resource-name [ns]
2331
(.substring ^String (#'clojure.core/root-resource ns) 1))
2432

25-
(defn get-class-loader-path
26-
"returns a tuple of [classloader classpath]"
27-
[]
28-
(let [thread-cl (-> (Thread/currentThread) (.getContextClassLoader))]
29-
[thread-cl (cp/classpath thread-cl)]))
30-
3133
(defn src-resource
3234
"given a namespace symbol, return a tuple of [filename URL] where the
3335
backing .clj is located, or nil if it couldn't be found"
3436
[ns]
3537
{:pre [(symbol? ns)]}
36-
(let [[cl _] (get-class-loader-path)]
37-
(->> [".clj" ".cljc"]
38-
(map (fn [ext]
39-
(let [src-path (str (src-resource-name ns) ext)
40-
src-resource (io/resource src-path cl)]
41-
(when src-resource
42-
[src-path src-resource]))))
43-
(filter identity)
38+
(->> [".clj" ".cljc"]
39+
(map (fn [ext]
40+
(let [src-path (str (src-resource-name ns) ext)
41+
src-resource (io/resource src-path)]
42+
(when src-resource
43+
[src-path src-resource]))))
44+
(filter identity)
4445
;; ((fn [srcs]
4546
;; (when (> (count srcs) 1)
4647
;; (println "WARNING multiple copies of" ns "found:" srcs))
4748
;; srcs))
48-
(first))))
49+
(first)))
4950

5051
(defn loaded? [ns]
5152
{:pre [(symbol? ns)]}
@@ -54,9 +55,10 @@
5455
;; block i.e. `(ns foo (:require bar))`. We use futures that require
5556
;; namespaces, so the best way to know if a namespace is _done_
5657
;; loading is to either deref the future, or check inside _your own_
57-
;; namespace future. if loaded? returns true anywhere else, it could
58-
;; be in the process of loading, which will lead to weird errors like
59-
;; `Unable to resolve symbol: foo`, when you know it should be there.
58+
;; namespace future. if loaded? returns true anywhere else, the
59+
;; namespace loading could be after the ns block, but before the end
60+
;; of the file, which will lead to weird errors like `Unable to
61+
;; resolve symbol: foo`, when you know it should be there.
6062

6163
(contains? (loaded-libs) ns))
6264

@@ -93,18 +95,10 @@ be found"
9395
(str/replace #"\." "/")
9496
(str "__init.class")))
9597

96-
(defn ns->class-name
97-
"given a namespace symbol, return the name of the class that will load it"
98-
[ns]
99-
(-> ns
100-
(munge)
101-
(str "__init")))
102-
10398
(defn compiled?
10499
"truthy if the namespace has AOT .class files on the classpath"
105100
[ns]
106-
(let [[cl _cp] (get-class-loader-path)]
107-
(.getResource ^ClassLoader cl (ns->resource-name ns))))
101+
(io/resource (ns->resource-name ns)))
108102

109103
;; root directory for all compiles. Each compile will be a subdir of
110104
;; this
@@ -113,14 +107,6 @@ be found"
113107
(-> (Runtime/getRuntime) (.addShutdownHook (Thread. ^Runnable (fn []
114108
(fs/rm-rf temp-dir)))))
115109

116-
(defn shasum [^bytes bs]
117-
{:pre [(seq bs)]}
118-
(let [digest (MessageDigest/getInstance "SHA-1")
119-
hexer (java.util.HexFormat/of)]
120-
(-> bs
121-
(#(.digest digest %))
122-
(#(.formatHex hexer %)))))
123-
124110
(defn ns-sha
125111
"return the hash of the ns file contents"
126112
[ns]
@@ -208,34 +194,32 @@ be found"
208194

209195
(defn ns-deps- [ns]
210196
(assert (src-resource ns) (print-str "couldn't find resource for" ns))
211-
212197
(-> ns
213198
src-resource
214199
second
215200
reader
216201
parse/read-ns-decl
217-
parse/deps-from-ns-decl))
202+
parse/deps-from-ns-decl
203+
(disj ns)))
218204

219205
(def ns-deps (memoize ns-deps-))
220206

221-
(defn ensure-dir-classpath [dir]
222-
(let [[cl classpath] (get-class-loader-path)]
223-
(when (not (some (partial = dir) classpath))
224-
(.addURL cl (.toURL (fs/path->file dir))))))
225-
226207
(defn compile- [ns]
227208
(let [sha (ns-sha ns)
228209
classes-dir (->cache-dir sha)]
229-
(when-not (compiled? ns)
230-
(when (loaded? ns)
231-
(debug "WARNING:" ns "already loaded before compilation!"
232-
:compiled? (compiled? ns)
233-
:loaded? (loaded? ns)
234-
:bound-require? (bound? #'clojure.core/require)
235-
:sha sha))
236-
(assert (not (loaded? ns)) (print-str ns :compiled? (compiled? ns) :loaded? (loaded? ns) :sha sha))
237-
(binding [*compile-path* (str classes-dir)]
238-
(compile ns)))))
210+
(if (compiled? ns)
211+
(require ns)
212+
(do
213+
(when (loaded? ns)
214+
(debug "WARNING:" ns "already loaded before compilation!"
215+
:compiled? (compiled? ns)
216+
:loaded? (loaded? ns)
217+
:bound-require? (bound? #'clojure.core/require)
218+
:sha sha))
219+
(assert (not (loaded? ns)) (print-str ns :compiled? (compiled? ns) :loaded? (loaded? ns) :sha sha))
220+
(binding [*compile-path* (str classes-dir)]
221+
(compile ns)
222+
(assert (seq (fs/ls-r classes-dir)) (print-str "compile-: no .class files found in" classes-dir)))))))
239223

240224
;; map of ns symbol to future.
241225
(def ns-futures (atom {}))
@@ -264,19 +248,44 @@ be found"
264248
;; once. Compilation will happen once, repeat sends to the same ns do
265249
;; nothing
266250

251+
(defn context-classloader-conveyor-fn [f]
252+
;; context classloaders are not conveyed by default in futures, but we set it in rules-clojure.jar/compile!
253+
(let [cl (.getContextClassLoader (Thread/currentThread))]
254+
(fn
255+
([]
256+
(.setContextClassLoader (Thread/currentThread) cl)
257+
(f)))))
258+
259+
(defn binding-conveyor-fn [f]
260+
;; don't use clojure.core/binding-conveyor-fn, because that usees
261+
;; clone/reset ThreadBindings, rather than push/pop. We use push and
262+
;; pop because clone shares TBoxes, which store the threadId they
263+
;; came from, which breaks clojure.lang.Compiler when an in-progress
264+
;; compile attempts to Var/set! from a new thread
265+
(let [bindings (clojure.lang.Var/getThreadBindings)]
266+
(fn
267+
([]
268+
(try
269+
(clojure.lang.Var/pushThreadBindings bindings)
270+
(f)
271+
(finally
272+
(clojure.lang.Var/popThreadBindings)))))))
273+
267274
(defn ns-send
268275
[ns f]
269276
{:pre [(symbol? ns)]
270277
:post [(future? %)]}
271-
(let [cl (.getContextClassLoader (Thread/currentThread))]
278+
279+
(let [f (context-classloader-conveyor-fn
280+
(binding-conveyor-fn f))]
272281
(-> ns-futures
273282
(swap! update ns (fn [**f]
274283
(or **f
275-
(delay (future (with-context-classloader cl
276-
(try
277-
(f)
278-
(catch Throwable t
279-
(throw (ex-info (print-str "in ns-send" ns :parallel? *parallel*) {} t))))))))))
284+
(delay (future
285+
(try
286+
(f)
287+
(catch Throwable t
288+
(throw (ex-info (print-str "in ns-send" ns :parallel? *parallel*) {} t)))))))))
280289
(get ns)
281290
(deref))))
282291

@@ -300,8 +309,9 @@ be found"
300309
(deref)))
301310

302311
(defn track-dep!
303-
"track that ns a depends on ns b. Returns truthy if there's no cycle,
304-
falsey if there is"
312+
"track that ns a depends on ns b. Updates the graph and returns truthy
313+
if there's no cycle. Does not update the graph and returns false if
314+
there is"
305315
[a b]
306316
{:pre [(symbol? a)
307317
(symbol? b)]}
@@ -315,15 +325,12 @@ be found"
315325
(debug "WARNING cycle:" a "->" b)
316326
false))))
317327

318-
(def ^:dynamic *parallel* true)
319-
320328
(defn pcompile
321329
"From namespace `parent`, compile `ns`, in parallel"
322330
[parent ns]
323331
{:pre [(or (symbol? parent) (nil? parent))
324332
(symbol? ns)]
325333
:post [(future? %)]}
326-
327334
(let [parallel? (and *parallel* (not (contains? no-parallel ns)))
328335
send (if parallel? ns-send ns-send-sync)]
329336
(if (or (not parent) (track-dep! parent ns))
@@ -337,12 +344,12 @@ be found"
337344
(let [cycle? (not (track-dep! ns d))
338345
*f (pcompile ns d)]
339346
(when (not cycle?)
340-
;; don't deref the compiles that cause cycles
341-
[d *f]))))
347+
;; don't deref the compile that
348+
;; cause cycles, the other
349+
;; thread will take care of it
350+
*f))))
342351
(filter identity)
343-
(mapv (fn [[d *f]]
344-
(when (= :timeout (deref *f 30000 :timeout))
345-
(throw (ex-info (print-str "timeout in" ns "waiting for" d) {:dep d}))))))
352+
(mapv deref))
346353
(if compile?
347354
(compile- ns)
348355
(require ns))
@@ -352,7 +359,8 @@ be found"
352359
(CompletableFuture/completedFuture true)))))
353360

354361
(defn pcopy [dest-dir ns]
355-
@(pcompile nil ns)
362+
(deref! (pcompile nil ns) 120000
363+
(ex-info (print-str "pcopy timeout waiting for" ns) {:dest-dir dest-dir :ns ns}))
356364
(assert (loaded? ns) (print-str ns "not loaded"))
357365
(let [sha (ns-sha ns)
358366
cache-dir (get-cache-dir sha)]
@@ -361,23 +369,28 @@ be found"
361369
:compiled? (compiled? ns)
362370
:dest dest-dir
363371
:actual-classpath (cp/classpath)))
372+
(assert (seq (fs/ls-r cache-dir)) (print-str "pcopy: no .class files found in" cache-dir))
364373
(copy-classes (fs/->path cache-dir) (fs/->path dest-dir))))
365374

366375
(defn prequire
367376
"given a seq of namespaces, ensure all are compiled (and loaded) in parallel. Blocking."
368377
[nses]
369378
{:pre [(every? symbol? nses)]}
370379
(let [ns-block? (not (contains? (loaded-libs) (symbol (str *ns*))))
371-
ns-sym (symbol (str *ns*))]
380+
ns-sym (symbol (str *ns*))
381+
;; don't hang on self-requires. Silly bug, but it happens in
382+
;; real life, and regular compiles don't explode
383+
nses (disj (set nses) ns-sym)]
372384
(when-not ns-block?
373385
(debug ns-sym "loading" nses "at toplevel"))
374-
(debug "spy-require" ns-sym nses)
375386
(->> nses
376387
(mapv (partial pcompile ns-sym))
377-
;; (mapv (fn [*f]
378-
;; (when (= :timeout (deref *f 30000 :timeout))
379-
;; (throw (ex-info (print-str "in" ns-sym "timeout waiting for" nses) {})))))
380-
(mapv deref))))
388+
(mapv (fn [*f]
389+
;; if we hang using 1-arity deref here, bazel doesn't
390+
;; return logging and it's very hard to debug. Set
391+
;; this high enough that it never accidentally
392+
;; triggers
393+
(deref! *f 120000 (ex-info (print-str "prequire timeout in" ns-sym "waiting for" nses) {})))))))
381394

382395
;; `require` calls load-libs->load-libs->load-one
383396
;; `load` calls clojure.lang.RT/load, so there's no common place to hook into both
@@ -526,3 +539,5 @@ be found"
526539
(pcompile nil n))
527540
(doseq [n aot-nses]
528541
(pcopy (str classes-dir "/") n))))))
542+
543+
(comment "text here to force rebuilding")

src/rules_clojure/jar.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
(try
6868
(util/shim-invoke cl "rules-clojure.compile" "compile!" classes-dir aot-arr *out*)
6969
(catch Throwable t
70-
(util/print-err "jar/compile!:" args t)))))
70+
(throw (ex-info "while compiling" {:args args} t))))))
7171

7272
(defn create-jar [{:keys [src-dir classes-dir output-jar resources aot-nses] :as args}]
7373
(s/assert ::compile args)

src/rules_clojure/util.clj

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(ns rules-clojure.util
22
(:require [clojure.spec.alpha :as s])
3-
(:import java.net.URLClassLoader))
3+
(:import java.net.URLClassLoader
4+
java.security.MessageDigest))
45

56
(set! *warn-on-reflection* true)
67

@@ -102,3 +103,11 @@ invokes f in the classloader, efficiently"
102103
(.getParent cl)) (clojure.lang.RT/baseLoader)))))
103104
(system-classpath))
104105
(map str)))
106+
107+
(defn shasum [^bytes bs]
108+
{:pre [(seq bs)]}
109+
(let [digest (MessageDigest/getInstance "SHA-1")
110+
hexer (java.util.HexFormat/of)]
111+
(-> bs
112+
(#(.digest digest %))
113+
(#(.formatHex hexer %)))))

src/rules_clojure/worker.clj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:require [clojure.data.json :as json]
33
[clojure.java.io :as io]
44
[clojure.spec.alpha :as s]
5+
[clojure.string :as str]
56
[rules-clojure.fs :as fs]
67
[rules-clojure.jar :as jar]
78
[rules-clojure.util :as util :refer [print-err]]
@@ -39,6 +40,11 @@
3940

4041
(def temp-dirs (atom #{}))
4142

43+
(defn src-dir-munge [req]
44+
;; include the src-dir to avoid src1/foo/bar.clj conflicting with src2/foo/bar.clj
45+
(when-let [dir (:src-dir req)]
46+
(str/replace dir "/" ".")))
47+
4248
(defn process-request
4349
[{:keys [classloader-strategy
4450
input-map] :as req}]
@@ -47,7 +53,7 @@
4753
(assert classloader-strategy)
4854
(try
4955
(assert (:classpath req))
50-
(let [classes-dir (fs/new-temp-dir (apply str (interpose "+" (concat ["classes"] (:aot-nses req)))) )
56+
(let [classes-dir (fs/new-temp-dir (apply str (interpose "+" (concat ["rules_clojure" (src-dir-munge req)] (:aot-nses req)))) )
5157
req (-> req
5258
(update :classpath conj (str classes-dir))
5359
(assoc :classes-dir (str classes-dir)))

0 commit comments

Comments
 (0)