Skip to content

Commit 20808c0

Browse files
committed
Primitive support for customize *context*
This commit add options to customize - *customize-builder* :: to customize the default ContextBuilder - *after-init-hook* :: called after a context is created More importantly, primitive support for automatically creating and using thread local context. Check *context-per-thread*.
1 parent 2c62604 commit 20808c0

File tree

4 files changed

+123
-21
lines changed

4 files changed

+123
-21
lines changed

src/net/coruscation/js4clj/context.clj

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
[org.graalvm.polyglot Context])
55
(:require [clojure.java.io :as io]))
66

7-
(defonce ^{:dynamic true :private true} *cjs-cwd* (str (System/getProperty "user.dir")
8-
"/node_modules"))
7+
(defonce ^{:dynamic true :private true} *cjs-cwd*
8+
(str (System/getProperty "user.dir")
9+
"/node_modules"))
910

1011
(defn default-builder
1112
"Return a `org.graalvm.polyglot.Context$Builder` object with necessary options set for js4clj to function properly.
@@ -31,26 +32,69 @@
3132
"js.commonjs-require-cwd" *cjs-cwd*}))
3233
(.allowIO true)))
3334

34-
(defonce ^{:dynamic true
35-
:doc "An atom wrapping a instance of `org.graalvm.polyglot.Context$Builder`, used for all the operations "}
36-
*context* (atom (.build (default-builder))))
35+
(def ^:dynamic *context-per-thread*
36+
"Whether to use thread local context when doing js4clj operations."
37+
false)
3738

38-
(defn initialize-context!
39-
"Initialize the `*context*` variable with a lambda taking `default-builder` as an argument.
39+
(def ^:dynamic *customize-builder*
40+
"A fn takes a `Context$Builder` as an argument and returns a `Context$Builder`"
41+
nil)
4042

41-
`defaut-builder` is an `org.graalvm.polyglot.Context$Builder` object with necessary option set for js4clj to function properly.
42-
The lambda should return a `org.graalvm.polyglot.Context` object to initialize the *context* variable.
43+
(def ^:private builtin-after-init
44+
(fn [ctx]
45+
(.eval ctx "js" "globalThis.global = this")
46+
(.eval ctx "js" "globalThis.process = {}")
47+
(.eval ctx "js" "globalThis.process.env = {}")))
4348

44-
Example:
45-
```clojure
46-
(initialize-context! (fn [builder]
47-
(.build builder)))
48-
```
49-
"
50-
[build-fn]
51-
(let [default-builder (default-builder)]
52-
(reset! *context* (build-fn default-builder))))
49+
(def ^:dynamic *after-init-hook*
50+
"A fn takes no arguments, called after a context is created, with *context* bound to the newly created context."
51+
nil)
52+
53+
(declare *context*)
54+
55+
(defn context-new
56+
"Create a context, respect `customize-builder` and `after-init-hook`"
57+
[]
58+
(let [context (-> (default-builder)
59+
(or *customize-builder* identity)
60+
(.build))]
61+
(when builtin-after-init (builtin-after-init context))
62+
(when *after-init-hook*
63+
(binding [*context* (ref context)]
64+
(*after-init-hook*)))
65+
context))
66+
67+
(def ^:private global-context (atom nil))
5368

54-
(.eval @*context* "js" "globalThis.global = this")
55-
(.eval @*context* "js" "globalThis.process = {}")
56-
(.eval @*context* "js" "globalThis.process.env = {}")
69+
(def ^:private thread-local-context (ThreadLocal/withInitial (fn [] (context-new))))
70+
71+
(def ^{:dynamic true
72+
:doc
73+
"When `*context-per-thread*` is false, deref it returns the global context,
74+
When `*context-per-thread*` is true, deref it returns the thread local context.
75+
76+
In any case, if it is not already initialized, initialize it with `context-new`
77+
78+
You can bind it with an IDeref instance to manage context."}
79+
*context* (reify clojure.lang.IDeref
80+
(deref [_]
81+
(if *context-per-thread*
82+
(.get thread-local-context)
83+
(if (not (nil? @global-context))
84+
@global-context
85+
(swap! global-context
86+
(fn [old]
87+
(if old
88+
old
89+
(context-new)))))))))
90+
91+
(defn reinitialize-context!
92+
"If `*context-per-thread*` is false, reinitialize the global `*context*` with `context-new`.
93+
If `*context-per-thread*` is true, reinitialize the thread-local context with `context-new`.
94+
95+
Note: Contexts are automatically initialize upon first usage if not already initialized.
96+
"
97+
[]
98+
(if *context-per-thread*
99+
(.set thread-local-context (context-new))
100+
(reset! global-context (context-new))))

src/net/coruscation/js4clj/core.clj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@
2626
2727
Example (js-new js/Array 1 2 3) => [1 2 3]"
2828
(apply new-instance obj (map polyglotalize-clojure args)))
29+
30+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
(ns net.coruscation.js4clj.context-test
2+
(:require
3+
[clojure.test :refer [deftest is use-fixtures]]
4+
[net.coruscation.js4clj.context :as subject]
5+
[net.coruscation.js4clj.js :as js]
6+
[net.coruscation.js4clj.test-utils :refer [fresh-context]]
7+
[net.coruscation.js4clj.utils :refer [js-set! js.-]]))
8+
9+
(deftest *context-per-thread*-test
10+
(binding [subject/*context-per-thread* true]
11+
(.eval @subject/*context*
12+
"js"
13+
"globalThis.demo = 1")
14+
(let [thread (Thread/new
15+
(bound-fn []
16+
(is (nil? (js.- js/globalThis
17+
demo)))))]
18+
(.start thread)
19+
(is (= 1
20+
(js.- js/globalThis
21+
demo)))
22+
(.join thread
23+
200))))
24+
25+
(deftest *after-init-hook*-test
26+
(binding [subject/*after-init-hook*
27+
(fn []
28+
(js-set! (js.- js/globalThis
29+
demo)
30+
2))]
31+
(subject/reinitialize-context!)
32+
(is (= 2 (js.- js/globalThis
33+
demo)))))
34+
35+
(deftest reinitialize-context!-test
36+
(is (nil? (js.- js/globalThis
37+
demo)))
38+
(js-set! (js.- js/globalThis
39+
demo)
40+
2)
41+
(is (= 2
42+
(js.- js/globalThis
43+
demo)))
44+
(subject/reinitialize-context!)
45+
(is (nil? (js.- js/globalThis
46+
demo))))
47+
48+
(use-fixtures :each #'fresh-context)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(ns net.coruscation.js4clj.test-utils
2+
(:require
3+
[clojure.test :refer :all]
4+
[net.coruscation.js4clj.context :as context]))
5+
6+
(defn fresh-context [test]
7+
(context/reinitialize-context!)
8+
(test))

0 commit comments

Comments
 (0)