1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
(ns puppetlabs.services.jruby-pool-manager.jruby-pool-int-test
(:require [clojure.test :refer :all]
[puppetlabs.trapperkeeper.testutils.bootstrap :as tk-bootstrap]
[puppetlabs.services.jruby-pool-manager.jruby-testutils :as jruby-testutils]
[puppetlabs.trapperkeeper.app :as tk-app]
[puppetlabs.services.jruby-pool-manager.jruby-pool-manager-service :as pool-manager]
[puppetlabs.services.protocols.pool-manager :as pool-manager-protocol]
[puppetlabs.services.jruby-pool-manager.jruby-core :as jruby-core]
[puppetlabs.services.jruby-pool-manager.jruby-schemas :as jruby-schemas]
[puppetlabs.services.jruby-pool-manager.impl.jruby-agents :as jruby-agents]
[puppetlabs.services.jruby-pool-manager.impl.jruby-internal :as jruby-internal]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Utilities
(def test-borrow-timeout 180000)
(defn get-stack-trace-for-thread-as-str
[stack-trace-elements]
(reduce
(fn [acc stack-trace-element]
(str acc
" "
(.getClassName stack-trace-element)
"."
(.getMethodName stack-trace-element)
"("
(.getFileName stack-trace-element)
":"
(.getLineNumber stack-trace-element)
")"
"\n"))
""
stack-trace-elements))
(defn get-all-stack-traces-as-str
[]
(reduce
(fn [acc thread-stack-element]
(let [thread (key thread-stack-element)]
(str acc
"\""
(.getName thread)
"\" id="
(.getId thread)
" state="
(.getState thread)
"\n"
(get-stack-trace-for-thread-as-str
(val thread-stack-element)))))
""
(Thread/getAllStackTraces)))
(def script-to-check-if-constant-is-defined
"! $instance_id.nil?")
(defn set-constants-and-verify
[pool-context num-instances]
;; here we set a variable called 'instance_id' in each instance
(jruby-testutils/reduce-over-jrubies!
pool-context
num-instances
#(format "$instance_id = %s" %))
;; and validate that we can read that value back from each instance
(= (set (range num-instances))
(-> (jruby-testutils/reduce-over-jrubies!
pool-context
num-instances
(constantly "$instance_id"))
set)))
(defn check-all-jrubies-for-constants
[pool-context num-instances]
(jruby-testutils/reduce-over-jrubies!
pool-context
num-instances
(constantly script-to-check-if-constant-is-defined)))
(defn check-jrubies-for-constant-counts
[pool-context expected-num-true expected-num-false]
(let [constants (check-all-jrubies-for-constants
pool-context
(+ expected-num-false expected-num-true))]
(and (= (+ expected-num-false expected-num-true) (count constants))
(= expected-num-true (count (filter true? constants)))
(= expected-num-false (count (filter false? constants))))))
(defn verify-no-constants
[pool-context num-instances]
;; verify that the constants are cleared out from the instances by looping
;; over them and expecting a 'NameError' when we reference the constant by name.
(every? false? (check-all-jrubies-for-constants pool-context num-instances)))
(defn borrow-until-desired-borrow-count
[pool-context desired-borrow-count]
(let [max-borrow-wait-count 100000]
(loop [instance (jruby-core/borrow-from-pool-with-timeout
pool-context
:borrow-until-desired-borrow-count
[])
loop-count 0]
(let [borrow-count (:borrow-count (jruby-core/get-instance-state instance))]
(jruby-core/return-to-pool pool-context instance :borrow-until-desired-borrow-count [])
(cond
(= (inc borrow-count) desired-borrow-count) true
(= loop-count max-borrow-wait-count) false
:else (recur (jruby-core/borrow-from-pool-with-timeout
pool-context
:borrow-until-desired-borrow-count
[])
(inc loop-count)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Tests
(deftest ^:integration flush-pool-test
(testing "Flushing the pool results in all new JRubyInstances"
(jruby-testutils/with-pool-context
pool-context
[pool-manager/jruby-pool-manager-service]
(jruby-testutils/jruby-config {:max-active-instances 4
:borrow-timeout test-borrow-timeout})
;; set a ruby constant in each instance so that we can recognize them
(is (true? (set-constants-and-verify pool-context 4)))
(jruby-core/flush-pool! pool-context)
(is (jruby-testutils/timed-await (jruby-agents/get-modify-instance-agent
pool-context))
(str "timed out waiting for the flush to complete, stack:\n"
(get-all-stack-traces-as-str)))
;; now the pool is flushed, so the constants should be cleared
(is (true? (verify-no-constants pool-context 4))))))
(deftest ^:integration flush-pool-for-shutdown-test
(testing "Flushing the pool for shutdown results in no JRubyInstances left"
(tk-bootstrap/with-app-with-config
app
[pool-manager/jruby-pool-manager-service]
{}
(let [config (jruby-testutils/jruby-config {:max-active-instances 4
:borrow-timeout test-borrow-timeout})
pool-manager-service (tk-app/get-service app :PoolManagerService)
pool-context (pool-manager-protocol/create-pool pool-manager-service config)
pool-state (jruby-internal/get-pool-state pool-context)
pool (:pool pool-state)]
;; wait for all jrubies to be added to the pool
(jruby-testutils/wait-for-jrubies-from-pool-context pool-context)
(is (= 4 (.currentSize (jruby-core/get-pool pool-context))))
(jruby-core/flush-pool-for-shutdown! pool-context)
;; flushing the pool should remove all JRubyInstances
(is (= 0 (.currentSize pool)))
;; any borrows should now return shutdown poison pill
(is (jruby-schemas/shutdown-poison-pill? (.borrowItem pool)))))))
(deftest ^:integration hold-file-handle-on-instance-while-another-is-flushed-test
(testing "file handle opened from one pool instance is held after other jrubies are destoyed"
(jruby-testutils/with-pool-context
pool-context
jruby-testutils/default-services
(jruby-testutils/jruby-config {:max-active-instances 4
:max-borrows-per-instance 10
:borrow-timeout test-borrow-timeout})
;; set a ruby constant in each instance so that we can recognize them
(is (true? (set-constants-and-verify pool-context 4)))
;; borrow an instance and hold the reference to it.
(let [instance (jruby-core/borrow-from-pool-with-timeout
pool-context
:hold-file-handle-while-another-instance-is-flushed
[])
sc (:scripting-container instance)]
(.runScriptlet sc
(str "require 'tempfile'\n\n"
"$unique_file = "
"Tempfile.new"
"('hold-instance-test-', './target')"))
(try
; After this, the next borrow and return will trigger a flush
(borrow-until-desired-borrow-count pool-context 9)
(let [instance-to-flush (jruby-core/borrow-from-pool pool-context :instance-to-flush [])]
(is (= 9 (:borrow-count (jruby-core/get-instance-state instance-to-flush))))
(jruby-core/return-to-pool pool-context instance-to-flush :instance-to-flush []))
(is (nil? (.runScriptlet sc "$unique_file.close"))
"Unexpected response on attempt to close unique file")
(finally
(.runScriptlet sc "$unique_file.unlink")))
;; return the instance
(jruby-core/return-to-pool pool-context instance :hold-file-handle-while-another-instance-is-flushed [])
;; Show that the instance-to-flush instance did actually get flushed
(check-jrubies-for-constant-counts pool-context 3 1)))))
(deftest ^:integration max-borrows-flush-while-pool-flush-in-progress-test
(testing "hitting max-borrows while flush in progress doesn't interfere with flush"
(jruby-testutils/with-pool-context
pool-context
jruby-testutils/default-services
(jruby-testutils/jruby-config {:max-active-instances 4
:max-borrows-per-instance 10
:splay-instance-flush false
:borrow-timeout
test-borrow-timeout})
(let [pool (jruby-core/get-pool pool-context)]
;; set a ruby constant in each instance so that we can recognize them.
;; this counts as one request for each instance.
(is (true? (set-constants-and-verify pool-context 4)))
;; borrow one instance and hold the reference to it, to prevent
;; the flush operation from completing
(let [instance1 (jruby-core/borrow-from-pool-with-timeout
pool-context
:max-borrows-flush-while-pool-flush-in-progress-test
[])]
;; we are going to borrow and return a second instance until we get its
;; request count up to max-borrows - 1, so that we can use it to test
;; flushing behavior the next time we return it.
(is (true? (borrow-until-desired-borrow-count pool-context 9)))
;; now we grab a reference to that instance and hold onto it for later.
(let [instance2 (jruby-core/borrow-from-pool-with-timeout
pool-context
:max-borrows-flush-while-pool-flush-in-progress-test
[])]
(is (= 9 (:borrow-count (jruby-core/get-instance-state instance2))))
(is (= 2 (jruby-core/free-instance-count pool)))
; Just to show that the pool is not locked yet
(is (not (.isLocked pool)))
;; trigger a flush asynchronously
(let [flush-future (future (jruby-core/flush-pool! pool-context))]
;; Once the lock is held this means that the flush is waiting
;; for all the instances to be returned before continuing
(is (jruby-testutils/wait-for-pool-to-be-locked pool))
;; now we're going to return instance2 to the pool. This should cause it
;; to get flushed. The main pool flush operation is still blocked.
(jruby-core/return-to-pool pool-context instance2
:max-borrows-flush-while-pool-flush-in-progress-test
[])
;; Wait until instance2 is returned
(is (jruby-testutils/wait-for-instances pool 3) "Timed out waiting for instance2 to return to pool")
;; and finally, we return the last instance we borrowed to the pool
(jruby-core/return-to-pool pool-context instance1
:max-borrows-flush-while-pool-flush-in-progress-test
[])
;; wait until the flush is complete
(is (deref flush-future 10000 false))
(is (not (.isLocked pool)))
(is (jruby-testutils/wait-for-instances pool 4) "Timed out waiting for the flush to finish"))))
;; we should have 4 fresh instances without the constant.
(is (true? (verify-no-constants pool-context 4)))
;; The jruby return instance calls done within the previous
;; check jrubies call may cause an instance to be in the process of
;; being flushed when the server is shut down. This ensures that
;; the flushing is all done before the server is shut down - since
;; that could otherwise cause an annoying error message about the
;; pool not being full at shut down to be displayed.
(jruby-testutils/timed-await (jruby-agents/get-modify-instance-agent pool-context))))))
(deftest initialization-and-cleanup-hooks-test
(testing "custom initialization and cleanup callbacks get called appropriately"
(let [foo-atom (atom "FOO")
lifecycle-fns {:initialize-pool-instance (fn [instance] (assoc instance :foo foo-atom))
:cleanup (fn [instance] (reset! (:foo instance) "Terminating FOO"))}
config (jruby-testutils/jruby-config
{:max-active-instances 1
:max-borrows-per-instance 10
:borrow-timeout test-borrow-timeout
:lifecycle lifecycle-fns})]
(jruby-testutils/with-pool-context
pool-context
jruby-testutils/default-services
config
(let [instance (jruby-core/borrow-from-pool-with-timeout
pool-context
:initialization-and-cleanup-hooks-test
[])]
(is (= "FOO" (deref (:foo instance))))
(jruby-core/return-to-pool pool-context instance :initialization-and-cleanup-hooks-test [])
(jruby-core/flush-pool! pool-context)
; wait until the flush is complete
(is (jruby-testutils/timed-await (jruby-agents/get-modify-instance-agent pool-context)))
(is (= "Terminating FOO" (deref foo-atom))))))))
(deftest initialize-scripting-container-hook-test
(testing "can set custom environment variables via :initialize-scripting-container hook"
(let [lifecycle-fns {:initialize-scripting-container (fn [scripting-container {}]
(.setEnvironment scripting-container
{"CUSTOMENV" "foobar"})
scripting-container)}
config (jruby-testutils/jruby-config
{:max-active-instances 1
:max-borrows-per-instance 10
:borrow-timeout test-borrow-timeout
:lifecycle lifecycle-fns})]
(jruby-testutils/with-pool-context
pool-context
jruby-testutils/default-services
config
(let [instance (jruby-core/borrow-from-pool-with-timeout
pool-context
:initialize-environment-variables-test
[])
scripting-container (:scripting-container instance)
jruby-env (.runScriptlet scripting-container "ENV")]
(.remove jruby-env "RUBY")
(is (= {"CUSTOMENV" "foobar"} jruby-env))
(jruby-core/return-to-pool
pool-context
instance
:initialize-environment-variables-test
[]))))))
|