Skip to content

Commit 000312a

Browse files
committed
(PE-37376) Parallize JRuby instance creation
1 parent 8f8ee48 commit 000312a

File tree

5 files changed

+88
-36
lines changed

5 files changed

+88
-36
lines changed

src/clj/puppetlabs/services/jruby_pool_manager/impl/jruby_agents.clj

+42-25
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,21 @@
77
[puppetlabs.i18n.core :as i18n])
88
(:import (clojure.lang IFn IDeref)
99
(puppetlabs.services.jruby_pool_manager.jruby_schemas PoisonPill JRubyInstance)
10-
(java.util.concurrent TimeUnit TimeoutException)))
10+
(java.util.concurrent TimeUnit TimeoutException ExecutionException Future ExecutorService)))
1111

1212
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1313
;;; Private
1414

15+
(schema/defn execute-tasks!
16+
[tasks :- [IFn]
17+
task-executor :- ExecutorService]
18+
(let [results (.invokeAll task-executor tasks)]
19+
(try
20+
(doseq [result results]
21+
(.get ^Future result))
22+
(catch ExecutionException ex
23+
(throw (.getCause ex))))))
24+
1525
(schema/defn ^:always-validate
1626
next-instance-id :- schema/Int
1727
[id :- schema/Int
@@ -68,15 +78,17 @@
6878
(i18n/trs "Initializing JRubyInstances with the following settings:")
6979
(ks/pprint-to-string config)))
7080
(let [pool (jruby-internal/get-pool pool-context)
71-
count (.remainingCapacity pool)]
72-
(dotimes [i count]
73-
(let [id (inc i)]
74-
(log/debug (i18n/trs "Priming JRubyInstance {0} of {1}"
75-
id count))
76-
(add-instance pool-context id)
77-
(log/info (i18n/trs "Finished creating JRubyInstance {0} of {1}"
78-
id count))))))
79-
81+
creation-service (jruby-internal/get-creation-service pool-context)
82+
total (.remainingCapacity pool)
83+
ids (->> total range (map inc))
84+
add-instance* (fn [id]
85+
(log/debug (i18n/trs "Priming JRubyInstance {0} of {1}"
86+
id count))
87+
(add-instance pool-context id)
88+
(log/info (i18n/trs "Finished creating JRubyInstance {0} of {1}"
89+
id count)))
90+
tasks (for [id ids] (fn [] (add-instance* id)))]
91+
(execute-tasks! tasks creation-service)))
8092

8193
(schema/defn ^:always-validate
8294
flush-instance!
@@ -148,23 +160,28 @@
148160
refill? :- schema/Bool]
149161
(let [pool (jruby-internal/get-pool pool-context)
150162
pool-size (jruby-internal/get-pool-size pool-context)
163+
creation-service (jruby-internal/get-creation-service pool-context)
151164
new-instance-ids (map inc (range pool-size))
152165
config (:config pool-context)
153-
cleanup-fn (get-in config [:lifecycle :cleanup])]
154-
(doseq [[old-instance new-id] (zipmap old-instances new-instance-ids)]
155-
(try
156-
(jruby-internal/cleanup-pool-instance! old-instance cleanup-fn)
157-
(when refill?
158-
(jruby-internal/create-pool-instance! pool new-id config
159-
(:splay-instance-flush config))
160-
(log/info (i18n/trs "Finished creating JRubyInstance {0} of {1}"
161-
new-id pool-size)))
162-
(catch Exception e
163-
(.clear pool)
164-
(jruby-internal/insert-poison-pill pool e)
165-
(throw (IllegalStateException.
166-
(i18n/trs "There was a problem creating a JRubyInstance for the pool.")
167-
e))))))
166+
cleanup-fn (get-in config [:lifecycle :cleanup])
167+
cleanup-and-refill-instance
168+
(fn [old-instance new-id]
169+
(try
170+
(jruby-internal/cleanup-pool-instance! old-instance cleanup-fn)
171+
(when refill?
172+
(jruby-internal/create-pool-instance! pool new-id config
173+
(:splay-instance-flush config))
174+
(log/info (i18n/trs "Finished creating JRubyInstance {0} of {1}"
175+
new-id pool-size)))
176+
(catch Exception e
177+
(.clear pool)
178+
(jruby-internal/insert-poison-pill pool e)
179+
(throw (IllegalStateException.
180+
(i18n/trs "There was a problem creating a JRubyInstance for the pool.")
181+
e)))))
182+
cleanup-and-refill-tasks (for [[old-instance new-id] (zipmap old-instances new-instance-ids)]
183+
(fn [] (cleanup-and-refill-instance old-instance new-id)))]
184+
(execute-tasks! cleanup-and-refill-tasks creation-service))
168185
(if refill?
169186
(log/info (i18n/trs "Finished draining and refilling pool."))
170187
(log/info (i18n/trs "Finished draining pool."))))

src/clj/puppetlabs/services/jruby_pool_manager/impl/jruby_internal.clj

+14-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
(com.puppetlabs.jruby_utils.jruby InternalScriptingContainer
1616
ScriptingContainer)
1717
(java.io File)
18-
(java.util.concurrent TimeUnit)
18+
(java.util.concurrent TimeUnit Executors ExecutorService)
1919
(org.jruby CompatVersion Main Ruby RubyInstanceConfig RubyInstanceConfig$CompileMode RubyInstanceConfig$ProfilingMode)
2020
(org.jruby.embed LocalContextScope)
2121
(org.jruby.runtime.profile.builtin ProfileOutput)
@@ -183,12 +183,16 @@
183183
"Create a new PoolState based on the config input."
184184
[config :- jruby-schemas/JRubyConfig]
185185
(let [multithreaded (:multithreaded config)
186-
size (:max-active-instances config)]
186+
size (:max-active-instances config)
187+
creation-concurrency (get config :instance-creation-concurrency 4)
188+
creation-service (Executors/newFixedThreadPool creation-concurrency)]
187189
(if multithreaded
188190
{:pool (instantiate-reference-pool size)
189-
:size 1}
191+
:size 1
192+
:creation-service creation-service}
190193
{:pool (instantiate-instance-pool size)
191-
:size size})))
194+
:size size
195+
:creation-service creation-service})))
192196

193197
(schema/defn ^:always-validate
194198
cleanup-pool-instance!
@@ -281,6 +285,12 @@
281285
[context :- jruby-schemas/PoolContext]
282286
(:size (get-pool-state context)))
283287

288+
(schema/defn
289+
get-creation-service :- ExecutorService
290+
"Gets the ExecutorService that will execute instance creation and termination."
291+
[context :- jruby-schemas/PoolContext]
292+
(:creation-service (get-pool-state context)))
293+
284294
(schema/defn ^:always-validate
285295
get-flush-timeout :- schema/Int
286296
"Gets the size of the JRuby pool from the pool context."

src/clj/puppetlabs/services/jruby_pool_manager/jruby_schemas.clj

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
(ns puppetlabs.services.jruby-pool-manager.jruby-schemas
22
(:require [schema.core :as schema])
33
(:import (clojure.lang Atom Agent IFn PersistentArrayMap PersistentHashMap)
4+
(com.puppetlabs.jruby_utils.jruby ScriptingContainer)
45
(com.puppetlabs.jruby_utils.pool LockablePool)
5-
(org.jruby Main Main$Status RubyInstanceConfig)
6-
(com.puppetlabs.jruby_utils.jruby ScriptingContainer)))
6+
(java.util.concurrent ExecutorService)
7+
(org.jruby Main Main$Status RubyInstanceConfig)))
78

89

910
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -73,8 +74,13 @@
7374
* :profiler-output-file - A target file to direct profiler output to. If
7475
not set, defaults to a random file relative to the working directory
7576
of the service.
77+
7678
* :multithreaded - Instead of managing the number of JRuby Instances create
77-
a single JRuby instance and manage the number of threads that may access it."
79+
a single JRuby instance and manage the number of threads that may access it.
80+
81+
* :instance-creation-concurrency - How many instances to create at once. This
82+
will improve start up and potentially reload times, but if too high may
83+
create unaceptable load on the system during startup or reload."
7884
{:ruby-load-path [schema/Str]
7985
:gem-home schema/Str
8086
:gem-path (schema/maybe schema/Str)
@@ -88,7 +94,8 @@
8894
:environment-vars {schema/Keyword schema/Str}
8995
:profiling-mode SupportedJRubyProfilingModes
9096
:profiler-output-file schema/Str
91-
:multithreaded schema/Bool})
97+
:multithreaded schema/Bool
98+
(schema/optional-key :instance-creation-concurrency) schema/Int})
9299

93100
(def JRubyPoolAgent
94101
"An agent configured for use in managing JRuby pools"
@@ -102,8 +109,9 @@
102109

103110
(def PoolState
104111
"A map that describes all attributes of a particular JRuby pool."
105-
{:pool pool-queue-type
106-
:size schema/Int})
112+
{:pool pool-queue-type
113+
:size schema/Int
114+
:creation-service ExecutorService})
107115

108116
(def PoolStateContainer
109117
"An atom containing the current state of all of the JRuby pool."

test/unit/puppetlabs/services/jruby_pool_manager/jruby_agents_test.clj

+17
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,28 @@
44
[puppetlabs.services.jruby-pool-manager.jruby-testutils :as jruby-testutils]
55
[puppetlabs.services.jruby-pool-manager.jruby-core :as jruby-core]
66
[puppetlabs.services.jruby-pool-manager.impl.jruby-agents :as jruby-agents]
7+
[puppetlabs.services.jruby-pool-manager.impl.jruby-internal :as jruby-internal]
78
[puppetlabs.services.jruby-pool-manager.impl.jruby-pool-manager-core :as jruby-pool-manager-core])
89
(:import (puppetlabs.services.jruby_pool_manager.jruby_schemas JRubyInstance)))
910

1011
(use-fixtures :once schema-test/validate-schemas)
1112

13+
(deftest execute-tasks!-test
14+
(let [pool-context (jruby-pool-manager-core/create-pool-context
15+
(jruby-testutils/jruby-config {:instance-creation-concurrency 3}))
16+
creation-service (jruby-internal/get-creation-service pool-context)]
17+
(testing "creation-service is a FixedThreadPool of configured number of threads"
18+
(is (= 3 (.getMaximumPoolSize creation-service))))
19+
;; this isn't a requirement and should be able to change in the future without issue,
20+
;; but none of the current callers require the result, so explictly test the assumption.
21+
(testing "does not return results of task execution"
22+
(let [tasks [(fn [] :foo) (fn [] :bar)]
23+
results (jruby-agents/execute-tasks! tasks creation-service)]
24+
(is (nil? results))))
25+
(testing "throws original execptions"
26+
(let [tasks [(fn [] (throw (IllegalStateException. "BOOM")))]]
27+
(is (thrown? IllegalStateException (jruby-agents/execute-tasks! tasks creation-service)))))))
28+
1229
(deftest next-instance-id-test
1330
(let [pool-context (jruby-pool-manager-core/create-pool-context
1431
(jruby-testutils/jruby-config {:max-active-instances 8}))]

test/unit/puppetlabs/services/jruby_pool_manager/jruby_pool_test.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@
189189
config (jruby-testutils/jruby-config {:max-active-instances pool-size})
190190
pool-context (jruby-pool-manager-core/create-pool-context config)
191191
err-msg (re-pattern "Unable to borrow JRubyInstance from pool")]
192-
(is (thrown? IllegalStateException (jruby-agents/prime-pool!
192+
(is (thrown? IllegalStateException (jruby-agents/prime-pool!
193193
(assoc-in pool-context [:config :lifecycle :initialize-pool-instance]
194194
(fn [_] (throw (IllegalStateException. "BORK!")))))))
195195
(testing "borrow and borrow-with-timeout both throw an exception if the pool failed to initialize"

0 commit comments

Comments
 (0)