engineyard-serverside 1.5.26 → 1.5.27.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/engineyard-serverside.rb +1 -2
- data/lib/engineyard-serverside/cli.rb +19 -35
- data/lib/engineyard-serverside/deploy.rb +4 -3
- data/lib/engineyard-serverside/future.rb +29 -0
- data/lib/engineyard-serverside/futures/celluloid.rb +25 -0
- data/lib/engineyard-serverside/futures/dataflow.rb +31 -0
- data/lib/engineyard-serverside/server.rb +27 -2
- data/lib/engineyard-serverside/task.rb +10 -11
- data/lib/engineyard-serverside/version.rb +1 -1
- data/lib/vendor/celluloid/lib/celluloid.rb +261 -0
- data/lib/vendor/celluloid/lib/celluloid/actor.rb +242 -0
- data/lib/vendor/celluloid/lib/celluloid/actor_pool.rb +54 -0
- data/lib/vendor/celluloid/lib/celluloid/actor_proxy.rb +75 -0
- data/lib/vendor/celluloid/lib/celluloid/application.rb +78 -0
- data/lib/vendor/celluloid/lib/celluloid/calls.rb +94 -0
- data/lib/vendor/celluloid/lib/celluloid/core_ext.rb +14 -0
- data/lib/vendor/celluloid/lib/celluloid/events.rb +14 -0
- data/lib/vendor/celluloid/lib/celluloid/fiber.rb +33 -0
- data/lib/vendor/celluloid/lib/celluloid/fsm.rb +141 -0
- data/lib/vendor/celluloid/lib/celluloid/future.rb +60 -0
- data/lib/vendor/celluloid/lib/celluloid/links.rb +61 -0
- data/lib/vendor/celluloid/lib/celluloid/logger.rb +32 -0
- data/lib/vendor/celluloid/lib/celluloid/mailbox.rb +124 -0
- data/lib/vendor/celluloid/lib/celluloid/receivers.rb +66 -0
- data/lib/vendor/celluloid/lib/celluloid/registry.rb +33 -0
- data/lib/vendor/celluloid/lib/celluloid/responses.rb +26 -0
- data/lib/vendor/celluloid/lib/celluloid/rspec.rb +2 -0
- data/lib/vendor/celluloid/lib/celluloid/signals.rb +50 -0
- data/lib/vendor/celluloid/lib/celluloid/supervisor.rb +57 -0
- data/lib/vendor/celluloid/lib/celluloid/task.rb +73 -0
- data/lib/vendor/celluloid/lib/celluloid/tcp_server.rb +33 -0
- data/lib/vendor/celluloid/lib/celluloid/timers.rb +109 -0
- data/lib/vendor/celluloid/lib/celluloid/version.rb +4 -0
- data/spec/basic_deploy_spec.rb +1 -1
- data/spec/bundler_deploy_spec.rb +1 -1
- data/spec/nodejs_deploy_spec.rb +1 -1
- data/spec/rails31_deploy_spec.rb +1 -1
- data/spec/services_deploy_spec.rb +1 -1
- metadata +78 -87
@@ -0,0 +1,242 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Don't do Actor-like things outside Actor scope
|
3
|
+
class NotActorError < StandardError; end
|
4
|
+
|
5
|
+
# Trying to do something to a dead actor
|
6
|
+
class DeadActorError < StandardError; end
|
7
|
+
|
8
|
+
# The caller made an error, not the current actor
|
9
|
+
class AbortError < StandardError
|
10
|
+
attr_reader :cause
|
11
|
+
|
12
|
+
def initialize(cause)
|
13
|
+
@cause = cause
|
14
|
+
super "caused by #{cause.inspect}: #{cause.to_s}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Actors are Celluloid's concurrency primitive. They're implemented as
|
19
|
+
# normal Ruby objects wrapped in threads which communicate with asynchronous
|
20
|
+
# messages.
|
21
|
+
class Actor
|
22
|
+
extend Registry
|
23
|
+
|
24
|
+
attr_reader :proxy
|
25
|
+
attr_reader :links
|
26
|
+
attr_reader :mailbox
|
27
|
+
|
28
|
+
# Invoke a method on the given actor via its mailbox
|
29
|
+
def self.call(mailbox, meth, *args, &block)
|
30
|
+
call = SyncCall.new(Thread.mailbox, meth, args, block)
|
31
|
+
|
32
|
+
begin
|
33
|
+
mailbox << call
|
34
|
+
rescue MailboxError
|
35
|
+
raise DeadActorError, "attempted to call a dead actor"
|
36
|
+
end
|
37
|
+
|
38
|
+
if Celluloid.actor?
|
39
|
+
response = Thread.current[:actor].wait [:call, call.id]
|
40
|
+
else
|
41
|
+
# Otherwise we're inside a normal thread, so block
|
42
|
+
response = Thread.mailbox.receive do |msg|
|
43
|
+
msg.respond_to?(:call_id) and msg.call_id == call.id
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
response.value
|
48
|
+
end
|
49
|
+
|
50
|
+
# Invoke a method asynchronously on an actor via its mailbox
|
51
|
+
def self.async(mailbox, meth, *args, &block)
|
52
|
+
begin
|
53
|
+
mailbox << AsyncCall.new(Thread.mailbox, meth, args, block)
|
54
|
+
rescue MailboxError
|
55
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
56
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
57
|
+
# async calls should find other ways to deal with actors dying
|
58
|
+
# during an async call (i.e. linking/supervisors)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Wrap the given subject with an Actor
|
63
|
+
def initialize(subject)
|
64
|
+
@subject = subject
|
65
|
+
|
66
|
+
if subject.respond_to? :mailbox_factory
|
67
|
+
@mailbox = subject.mailbox_factory
|
68
|
+
else
|
69
|
+
@mailbox = Mailbox.new
|
70
|
+
end
|
71
|
+
|
72
|
+
@links = Links.new
|
73
|
+
@signals = Signals.new
|
74
|
+
@receivers = Receivers.new
|
75
|
+
@timers = Timers.new
|
76
|
+
@proxy = ActorProxy.new(@mailbox, self.class.to_s)
|
77
|
+
@running = true
|
78
|
+
|
79
|
+
@thread = Pool.get do
|
80
|
+
Thread.current[:actor] = self
|
81
|
+
Thread.current[:mailbox] = @mailbox
|
82
|
+
|
83
|
+
run
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Is this actor alive?
|
88
|
+
def alive?
|
89
|
+
@running
|
90
|
+
end
|
91
|
+
|
92
|
+
# Terminate this actor
|
93
|
+
def terminate
|
94
|
+
@running = false
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Send a signal with the given name to all waiting methods
|
99
|
+
def signal(name, value = nil)
|
100
|
+
@signals.send name, value
|
101
|
+
end
|
102
|
+
|
103
|
+
# Wait for the given signal
|
104
|
+
def wait(name)
|
105
|
+
@signals.wait name
|
106
|
+
end
|
107
|
+
|
108
|
+
# Receive an asynchronous message
|
109
|
+
def receive(timeout = nil, &block)
|
110
|
+
@receivers.receive(timeout, &block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Run the actor loop
|
114
|
+
def run
|
115
|
+
while @running
|
116
|
+
begin
|
117
|
+
message = @mailbox.receive(timeout)
|
118
|
+
rescue ExitEvent => exit_event
|
119
|
+
Task.new(:exit_handler) { handle_exit_event exit_event }.resume
|
120
|
+
retry
|
121
|
+
end
|
122
|
+
|
123
|
+
if message
|
124
|
+
handle_message message
|
125
|
+
else
|
126
|
+
# No message indicates a timeout
|
127
|
+
@timers.fire
|
128
|
+
@receivers.fire_timers
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
cleanup ExitEvent.new(@proxy)
|
133
|
+
rescue MailboxShutdown
|
134
|
+
# If the mailbox detects shutdown, exit the actor
|
135
|
+
@running = false
|
136
|
+
rescue Exception => ex
|
137
|
+
@running = false
|
138
|
+
handle_crash(ex)
|
139
|
+
ensure
|
140
|
+
Pool.put @thread
|
141
|
+
end
|
142
|
+
|
143
|
+
# How long to wait until the next timer fires
|
144
|
+
def timeout
|
145
|
+
i1 = @timers.wait_interval
|
146
|
+
i2 = @receivers.wait_interval
|
147
|
+
|
148
|
+
if i1 and i2
|
149
|
+
i1 < i2 ? i1 : i2
|
150
|
+
elsif i1
|
151
|
+
i1
|
152
|
+
else
|
153
|
+
i2
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Obtain a hash of tasks that are currently waiting
|
158
|
+
def tasks
|
159
|
+
# A hash of tasks to what they're waiting on is more meaningful to the
|
160
|
+
# end-user, and lets us make a copy of the tasks table, rather than
|
161
|
+
# handing them the one we're using internally across threads, a definite
|
162
|
+
# thread safety shared state no-no
|
163
|
+
tasks = {}
|
164
|
+
current_task = Task.current rescue nil
|
165
|
+
tasks[current_task] = :running if current_task
|
166
|
+
|
167
|
+
@signals.waiting.each do |waitable, waiters|
|
168
|
+
if waiters.is_a? Enumerable
|
169
|
+
waiters.each { |waiter| tasks[waiter] = waitable }
|
170
|
+
else
|
171
|
+
tasks[waiters] = waitable
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
tasks
|
176
|
+
end
|
177
|
+
|
178
|
+
# Schedule a block to run at the given time
|
179
|
+
def after(interval)
|
180
|
+
@timers.add(interval) do
|
181
|
+
Task.new(:timer) { yield }.resume
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Sleep for the given amount of time
|
186
|
+
def sleep(interval)
|
187
|
+
task = Task.current
|
188
|
+
@timers.add(interval) { task.resume }
|
189
|
+
Task.suspend
|
190
|
+
end
|
191
|
+
|
192
|
+
# Handle an incoming message
|
193
|
+
def handle_message(message)
|
194
|
+
case message
|
195
|
+
when Call
|
196
|
+
Task.new(:message_handler) { message.dispatch(@subject) }.resume
|
197
|
+
when Response
|
198
|
+
handled_successfully = signal [:call, message.call_id], message
|
199
|
+
|
200
|
+
unless handled_successfully
|
201
|
+
Logger.debug("anomalous message! spurious response to call #{message.call_id}")
|
202
|
+
end
|
203
|
+
else
|
204
|
+
@receivers.handle_message(message)
|
205
|
+
end
|
206
|
+
message
|
207
|
+
end
|
208
|
+
|
209
|
+
# Handle exit events received by this actor
|
210
|
+
def handle_exit_event(exit_event)
|
211
|
+
exit_handler = @subject.class.exit_handler
|
212
|
+
if exit_handler
|
213
|
+
return @subject.send(exit_handler, exit_event.actor, exit_event.reason)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Reraise exceptions from linked actors
|
217
|
+
# If no reason is given, actor terminated cleanly
|
218
|
+
raise exit_event.reason if exit_event.reason
|
219
|
+
end
|
220
|
+
|
221
|
+
# Handle any exceptions that occur within a running actor
|
222
|
+
def handle_crash(exception)
|
223
|
+
Logger.crash("#{@subject.class} crashed!", exception)
|
224
|
+
cleanup ExitEvent.new(@proxy, exception)
|
225
|
+
rescue Exception => ex
|
226
|
+
Logger.crash("#{@subject.class}: ERROR HANDLER CRASHED!", ex)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Handle cleaning up this actor after it exits
|
230
|
+
def cleanup(exit_event)
|
231
|
+
@mailbox.shutdown
|
232
|
+
@links.send_event exit_event
|
233
|
+
tasks.each { |task, _| task.terminate }
|
234
|
+
|
235
|
+
begin
|
236
|
+
@subject.finalize if @subject.respond_to? :finalize
|
237
|
+
rescue Exception => ex
|
238
|
+
Logger.crash("#{@subject.class}#finalize crashed!", ex)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Celluloid
|
4
|
+
class Actor
|
5
|
+
# Maintain a thread pool of actors FOR SPEED!!
|
6
|
+
class Pool
|
7
|
+
@pool = []
|
8
|
+
@lock = Mutex.new
|
9
|
+
@max_idle = 16
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :max_idle
|
13
|
+
|
14
|
+
def get(&block)
|
15
|
+
@lock.synchronize do
|
16
|
+
if @pool.empty?
|
17
|
+
thread = create
|
18
|
+
else
|
19
|
+
thread = @pool.shift
|
20
|
+
end
|
21
|
+
|
22
|
+
thread[:queue] << block
|
23
|
+
thread
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def put(thread)
|
28
|
+
@lock.synchronize do
|
29
|
+
if @pool.size >= @max_idle
|
30
|
+
thread[:queue] << nil
|
31
|
+
else
|
32
|
+
@pool << thread
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def create
|
38
|
+
queue = Queue.new
|
39
|
+
thread = Thread.new do
|
40
|
+
begin
|
41
|
+
while func = queue.pop
|
42
|
+
func.call
|
43
|
+
end
|
44
|
+
rescue Exception => ex
|
45
|
+
Logger.crash("#{self} internal failure", ex)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
thread[:queue] = queue
|
49
|
+
thread
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# A proxy object returned from Celluloid::Actor.spawn/spawn_link which
|
3
|
+
# dispatches calls and casts to normal Ruby objects which are running inside
|
4
|
+
# of their own threads.
|
5
|
+
class ActorProxy
|
6
|
+
attr_reader :mailbox
|
7
|
+
|
8
|
+
def initialize(mailbox, klass = "Object")
|
9
|
+
@mailbox, @klass = mailbox, klass
|
10
|
+
end
|
11
|
+
|
12
|
+
def send(meth, *args, &block)
|
13
|
+
Actor.call @mailbox, :send, meth, *args, &block
|
14
|
+
end
|
15
|
+
|
16
|
+
def class
|
17
|
+
Actor.call @mailbox, :send, :class
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to?(meth)
|
21
|
+
Actor.call @mailbox, :respond_to?, meth
|
22
|
+
end
|
23
|
+
|
24
|
+
def methods(include_ancestors = true)
|
25
|
+
Actor.call @mailbox, :methods, include_ancestors
|
26
|
+
end
|
27
|
+
|
28
|
+
def alive?
|
29
|
+
@mailbox.alive?
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
Actor.call @mailbox, :to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
Actor.call @mailbox, :inspect
|
38
|
+
rescue DeadActorError
|
39
|
+
"#<Celluloid::Actor(#{@klass}) dead>"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a Celluloid::Future which calls a given method
|
43
|
+
def future(method_name, *args, &block)
|
44
|
+
Future.new { Actor.call @mailbox, method_name, *args, &block }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Terminate the associated actor
|
48
|
+
def terminate
|
49
|
+
raise DeadActorError, "actor already terminated" unless alive?
|
50
|
+
|
51
|
+
begin
|
52
|
+
send :terminate
|
53
|
+
rescue DeadActorError
|
54
|
+
# In certain cases this is thrown during termination. This is likely
|
55
|
+
# a bug in Celluloid's internals, but it shouldn't affect the caller.
|
56
|
+
# FIXME: track this down and fix it, or at the very least log it
|
57
|
+
end
|
58
|
+
|
59
|
+
# Always return nil until a dependable exit value can be obtained
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# method_missing black magic to call bang predicate methods asynchronously
|
64
|
+
def method_missing(meth, *args, &block)
|
65
|
+
# bang methods are async calls
|
66
|
+
if meth.to_s.match(/!$/)
|
67
|
+
unbanged_meth = meth.to_s.sub(/!$/, '')
|
68
|
+
Actor.async @mailbox, unbanged_meth, *args, &block
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
Actor.call @mailbox, meth, *args, &block
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Applications describe and manage networks of Celluloid actors
|
3
|
+
class Application
|
4
|
+
include Celluloid
|
5
|
+
trap_exit :restart_supervisor
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Actors or sub-applications to be supervised
|
9
|
+
def supervisables
|
10
|
+
@supervisables ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Start this application (and watch it with a supervisor)
|
14
|
+
alias_method :run!, :supervise
|
15
|
+
|
16
|
+
# Run the application in the foreground with a simple watchdog
|
17
|
+
def run
|
18
|
+
loop do
|
19
|
+
supervisor = run!
|
20
|
+
|
21
|
+
# Take five, toplevel supervisor
|
22
|
+
sleep 5 while supervisor.alive?
|
23
|
+
|
24
|
+
Logger.error "!!! Celluloid::Application #{self} crashed. Restarting..."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Register an actor class or a sub-application class to be launched and
|
29
|
+
# supervised while this application is running. Available options are:
|
30
|
+
#
|
31
|
+
# * as: register this application in the Celluloid::Actor[] directory
|
32
|
+
# * args: start the actor with the given arguments
|
33
|
+
def supervise(klass, options = {})
|
34
|
+
supervisables << Supervisable.new(klass, options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Start the application
|
39
|
+
def initialize
|
40
|
+
@supervisors = {}
|
41
|
+
|
42
|
+
# This is some serious lolcode, but like... start the supervisors for
|
43
|
+
# this application
|
44
|
+
self.class.supervisables.each do |supervisable|
|
45
|
+
supervisor = supervisable.supervise
|
46
|
+
@supervisors[supervisor] = supervisable
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Restart a crashed supervisor
|
51
|
+
def restart_supervisor(supervisor, reason)
|
52
|
+
supervisable = @supervisors.delete supervisor
|
53
|
+
raise "a supervisable went missing. This shouldn't be!" unless supervisable
|
54
|
+
|
55
|
+
supervisor = supervisable.supervise
|
56
|
+
@supervisors[supervisor] = supervisable
|
57
|
+
end
|
58
|
+
|
59
|
+
# A subcomponent of an application to be supervised
|
60
|
+
class Supervisable
|
61
|
+
attr_reader :klass, :as, :args
|
62
|
+
|
63
|
+
def initialize(klass, options = {})
|
64
|
+
@klass = klass
|
65
|
+
|
66
|
+
# Stringify keys :/
|
67
|
+
options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
68
|
+
|
69
|
+
@as = options['as']
|
70
|
+
@args = options['args'] || []
|
71
|
+
end
|
72
|
+
|
73
|
+
def supervise
|
74
|
+
Supervisor.new_link(@as, @klass, *@args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|