Last active
December 13, 2015 20:48
-
-
Save zerowidth/4972131 to your computer and use it in GitHub Desktop.
code for blog post at http://zerowidth.com/2013/02/17/initializing-shared-resources-with-clojure-and-ruby.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns clj-shared-resource.coordinated) | |
; define a few helper functions | |
(defn thread-id [] | |
(.getId (Thread/currentThread))) | |
; because stdout isn't synchronized: | |
(def logger (agent nil)) | |
(defn log [& msgs] | |
(send-off logger (fn [_] (apply println (cons ";" msgs))))) | |
(def app {:started (promise) | |
:starting (atom nil)}) | |
(defn start-app [] | |
(let [tid (thread-id)] | |
(Thread/sleep (rand 500)) | |
(deliver | |
(:started app) | |
(fn [n] | |
(str "app started in thread " tid " handled request " n))))) | |
(defn start-or-wait-for-app [] | |
(let [me (thread-id)] | |
(if (= me (swap! (:starting app) (fn [id] (or id me)))) | |
(do | |
(log me " starting app") | |
(let [started (start-app)] | |
(deliver (:started app) started) | |
(deref (:started app)))) | |
(do | |
(log me " waiting") | |
(deref (:started app)))))) | |
(defn get-app [] | |
(if (realized? (:started app)) | |
(deref (:started app)) | |
(start-or-wait-for-app))) | |
(defn make-request | |
"start and call the shared application" | |
[request-number] | |
(log "request " request-number " retrieving app from " (thread-id)) | |
(let [instance (get-app)] | |
(log (instance request-number)))) | |
; have the logger sleep for a second so output isn't interleaved | |
(send-off logger (fn [_] (Thread/sleep 1000))) | |
(->> | |
(range 1 4) | |
(map (fn [n] (future-call #(make-request n)))) | |
(map deref)) | |
(Thread/sleep 600) | |
(make-request 4) | |
; wait for the logger to collect the log output | |
(await-for 1000 logger) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "celluloid" | |
class App | |
def initialize(instance) | |
@instance = instance | |
end | |
def call(n) | |
"response #{n} from app #{@instance}" | |
end | |
end | |
class Server | |
include Celluloid | |
def get_app(thread_id) | |
if @app | |
puts "thread #{thread_id}: app started already" | |
else | |
if @starting | |
puts "thread #{thread_id}: waiting for app to start" | |
wait :started | |
else | |
@starting = true | |
puts "thread #{thread_id}: starting app" | |
sleep rand | |
puts "thread #{thread_id}: started app" | |
@app = App.new thread_id | |
signal :started | |
end | |
end | |
@app | |
end | |
end | |
server = Server.new | |
3.times.map do |n| | |
Thread.new do | |
puts server.get_app(Thread.current.object_id % 1000).call(n) | |
end | |
end.map(&:join) | |
sleep 1 | |
puts server.get_app("main").call 3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns clj-shared-resource.naive) | |
(defn thread-id [] | |
(.getId (Thread/currentThread))) | |
; because stdout isn't synchronized: | |
(def logger (agent nil)) | |
(defn log [& msgs] | |
(send-off logger (fn [_] (apply println (cons ";" msgs))))) | |
; define the shared app | |
(def app (ref nil)) | |
; naive check-or-set | |
(defn get-app [] | |
(let [tid (thread-id)] | |
(if-let [instance @app] | |
(do | |
(log "thread " tid " got running app") | |
instance) | |
(do | |
(log "thread " tid " starting app") | |
(Thread/sleep (rand 500)) | |
(log "thread " tid " started") | |
(dosync | |
(ref-set | |
app | |
(fn [n] | |
(str "app started in thread " tid " handled request " n)))))))) | |
(defn make-request | |
"start and call the shared application" | |
[request-number] | |
(log "request " request-number " retrieving app from " (thread-id)) | |
(let [instance (get-app)] | |
(log (instance request-number)))) | |
; have the logger sleep for a second so output isn't interleaved | |
(send-off logger (fn [_] (Thread/sleep 1000))) | |
(->> | |
(range 1 4) | |
(map (fn [n] (future-call #(make-request n)))) | |
(map deref)) | |
(Thread/sleep 600) | |
(make-request 4) | |
; wait for the logger to collect the log output | |
(await-for 1000 logger) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "celluloid" | |
class App | |
def initialize(instance) | |
@instance = instance | |
end | |
def call(n) | |
"response #{n} from app #{@instance}" | |
end | |
end | |
class Server | |
include Celluloid | |
# The thread this actor uses for execution doesn't change, so pass the calling | |
# thread id in so it can be logged instead. | |
def get_app(thread_id) | |
if @app | |
puts "thread #{thread_id}: app started already" | |
else | |
puts "thread #{thread_id}: starting app" | |
sleep rand | |
puts "thread #{thread_id}: started app" | |
@app = App.new thread_id | |
end | |
@app | |
end | |
end | |
server = Server.new | |
3.times.map do |n| | |
Thread.new do | |
puts server.get_app(Thread.current.object_id % 1000).call(n) | |
end | |
end.map(&:join) | |
sleep 1 | |
puts server.get_app("main").call 3 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment