celluloid 0.16.0 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of celluloid might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGES.md +333 -0
- data/README.md +1 -1
- data/culture/CODE_OF_CONDUCT.md +28 -0
- data/culture/Gemfile +9 -0
- data/culture/README.md +22 -0
- data/culture/Rakefile +5 -0
- data/culture/SYNC.md +70 -0
- data/culture/celluloid-culture.gemspec +18 -0
- data/culture/gems/README.md +39 -0
- data/culture/gems/dependencies.yml +78 -0
- data/culture/gems/loader.rb +101 -0
- data/culture/rubocop/README.md +38 -0
- data/culture/rubocop/lint.yml +8 -0
- data/culture/rubocop/metrics.yml +15 -0
- data/culture/rubocop/rubocop.yml +4 -0
- data/culture/rubocop/style.yml +48 -0
- data/culture/spec/gems_spec.rb +2 -0
- data/culture/spec/spec_helper.rb +0 -0
- data/culture/spec/sync_spec.rb +2 -0
- data/culture/sync.rb +56 -0
- data/culture/tasks/rspec.rake +5 -0
- data/culture/tasks/rubocop.rake +2 -0
- data/examples/basic_usage.rb +49 -0
- data/examples/futures.rb +38 -0
- data/examples/ring.rb +61 -0
- data/examples/simple_pmap.rb +14 -0
- data/examples/timers.rb +72 -0
- data/lib/celluloid.rb +142 -127
- data/lib/celluloid/actor.rb +47 -41
- data/lib/celluloid/actor_system.rb +75 -22
- data/lib/celluloid/autostart.rb +1 -1
- data/lib/celluloid/backported.rb +2 -0
- data/lib/celluloid/call/async.rb +16 -0
- data/lib/celluloid/call/block.rb +22 -0
- data/lib/celluloid/call/sync.rb +70 -0
- data/lib/celluloid/calls.rb +25 -114
- data/lib/celluloid/cell.rb +32 -20
- data/lib/celluloid/condition.rb +3 -3
- data/lib/celluloid/core_ext.rb +1 -1
- data/lib/celluloid/current.rb +2 -0
- data/lib/celluloid/deprecate.rb +18 -0
- data/lib/celluloid/exceptions.rb +1 -1
- data/lib/celluloid/fiber.rb +3 -3
- data/lib/celluloid/future.rb +7 -6
- data/lib/celluloid/group.rb +65 -0
- data/lib/celluloid/group/manager.rb +27 -0
- data/lib/celluloid/group/pool.rb +125 -0
- data/lib/celluloid/group/spawner.rb +71 -0
- data/lib/celluloid/logging.rb +5 -5
- data/lib/celluloid/mailbox.rb +14 -13
- data/lib/celluloid/mailbox/evented.rb +76 -0
- data/lib/celluloid/notices.rb +15 -0
- data/lib/celluloid/proxies.rb +12 -0
- data/lib/celluloid/proxy/abstract.rb +24 -0
- data/lib/celluloid/proxy/actor.rb +46 -0
- data/lib/celluloid/proxy/async.rb +36 -0
- data/lib/celluloid/proxy/block.rb +31 -0
- data/lib/celluloid/proxy/cell.rb +76 -0
- data/lib/celluloid/proxy/future.rb +40 -0
- data/lib/celluloid/proxy/sync.rb +44 -0
- data/lib/celluloid/rspec.rb +9 -10
- data/lib/celluloid/system_events.rb +16 -15
- data/lib/celluloid/{tasks.rb → task.rb} +21 -21
- data/lib/celluloid/task/fibered.rb +45 -0
- data/lib/celluloid/task/threaded.rb +59 -0
- data/lib/celluloid/test.rb +1 -1
- data/lib/celluloid/thread.rb +6 -1
- data/lib/celluloid/version.rb +3 -0
- data/spec/celluloid/actor_spec.rb +2 -2
- data/spec/celluloid/actor_system_spec.rb +35 -21
- data/spec/celluloid/block_spec.rb +3 -5
- data/spec/celluloid/calls_spec.rb +33 -11
- data/spec/celluloid/condition_spec.rb +16 -13
- data/spec/celluloid/evented_mailbox_spec.rb +1 -31
- data/spec/celluloid/future_spec.rb +13 -10
- data/spec/celluloid/group/elastic_spec.rb +0 -0
- data/spec/celluloid/group/manager_spec.rb +0 -0
- data/spec/celluloid/group/pool_spec.rb +8 -0
- data/spec/celluloid/group/spawner_spec.rb +8 -0
- data/spec/celluloid/mailbox/evented_spec.rb +27 -0
- data/spec/celluloid/mailbox_spec.rb +1 -3
- data/spec/celluloid/misc/leak_spec.rb +73 -0
- data/spec/celluloid/task/fibered_spec.rb +5 -0
- data/spec/celluloid/task/threaded_spec.rb +5 -0
- data/spec/celluloid/timer_spec.rb +14 -16
- data/spec/deprecate/actor_system_spec.rb +72 -0
- data/spec/deprecate/block_spec.rb +52 -0
- data/spec/deprecate/calls_spec.rb +57 -0
- data/spec/deprecate/evented_mailbox_spec.rb +34 -0
- data/spec/deprecate/future_spec.rb +32 -0
- data/spec/deprecate/internal_pool_spec.rb +4 -0
- data/spec/shared/actor_examples.rb +1237 -0
- data/spec/shared/group_examples.rb +121 -0
- data/{lib/celluloid/rspec → spec/shared}/mailbox_examples.rb +20 -17
- data/{lib/celluloid/rspec → spec/shared}/task_examples.rb +9 -8
- data/spec/spec_helper.rb +72 -16
- data/spec/support/coverage.rb +4 -0
- data/spec/support/crash_checking.rb +68 -0
- data/spec/support/debugging.rb +31 -0
- data/spec/support/env.rb +16 -0
- data/{lib/celluloid/rspec/example_actor_class.rb → spec/support/examples/actor_class.rb} +21 -2
- data/spec/support/examples/evented_mailbox_class.rb +27 -0
- data/spec/support/includer.rb +9 -0
- data/spec/support/logging.rb +63 -0
- data/spec/support/loose_threads.rb +65 -0
- data/spec/support/reset_class_variables.rb +27 -0
- data/spec/support/sleep_and_wait.rb +14 -0
- data/spec/support/split_logs.rb +1 -0
- data/spec/support/stubbing.rb +14 -0
- metadata +255 -95
- data/lib/celluloid/call_chain.rb +0 -13
- data/lib/celluloid/cpu_counter.rb +0 -34
- data/lib/celluloid/evented_mailbox.rb +0 -73
- data/lib/celluloid/fsm.rb +0 -186
- data/lib/celluloid/handlers.rb +0 -41
- data/lib/celluloid/internal_pool.rb +0 -159
- data/lib/celluloid/legacy.rb +0 -9
- data/lib/celluloid/links.rb +0 -36
- data/lib/celluloid/logger.rb +0 -93
- data/lib/celluloid/logging/incident.rb +0 -21
- data/lib/celluloid/logging/incident_logger.rb +0 -129
- data/lib/celluloid/logging/incident_reporter.rb +0 -48
- data/lib/celluloid/logging/log_event.rb +0 -20
- data/lib/celluloid/logging/ring_buffer.rb +0 -65
- data/lib/celluloid/method.rb +0 -32
- data/lib/celluloid/notifications.rb +0 -83
- data/lib/celluloid/pool_manager.rb +0 -146
- data/lib/celluloid/probe.rb +0 -73
- data/lib/celluloid/properties.rb +0 -24
- data/lib/celluloid/proxies/abstract_proxy.rb +0 -20
- data/lib/celluloid/proxies/actor_proxy.rb +0 -38
- data/lib/celluloid/proxies/async_proxy.rb +0 -31
- data/lib/celluloid/proxies/block_proxy.rb +0 -29
- data/lib/celluloid/proxies/cell_proxy.rb +0 -68
- data/lib/celluloid/proxies/future_proxy.rb +0 -35
- data/lib/celluloid/proxies/sync_proxy.rb +0 -36
- data/lib/celluloid/receivers.rb +0 -63
- data/lib/celluloid/registry.rb +0 -57
- data/lib/celluloid/responses.rb +0 -44
- data/lib/celluloid/rspec/actor_examples.rb +0 -1054
- data/lib/celluloid/signals.rb +0 -23
- data/lib/celluloid/stack_dump.rb +0 -133
- data/lib/celluloid/supervision_group.rb +0 -169
- data/lib/celluloid/supervisor.rb +0 -22
- data/lib/celluloid/task_set.rb +0 -49
- data/lib/celluloid/tasks/task_fiber.rb +0 -43
- data/lib/celluloid/tasks/task_thread.rb +0 -53
- data/lib/celluloid/thread_handle.rb +0 -50
- data/lib/celluloid/uuid.rb +0 -38
- data/spec/celluloid/cpu_counter_spec.rb +0 -82
- data/spec/celluloid/fsm_spec.rb +0 -107
- data/spec/celluloid/internal_pool_spec.rb +0 -52
- data/spec/celluloid/links_spec.rb +0 -45
- data/spec/celluloid/logging/ring_buffer_spec.rb +0 -38
- data/spec/celluloid/notifications_spec.rb +0 -120
- data/spec/celluloid/pool_spec.rb +0 -92
- data/spec/celluloid/probe_spec.rb +0 -121
- data/spec/celluloid/properties_spec.rb +0 -42
- data/spec/celluloid/registry_spec.rb +0 -64
- data/spec/celluloid/stack_dump_spec.rb +0 -64
- data/spec/celluloid/supervision_group_spec.rb +0 -65
- data/spec/celluloid/supervisor_spec.rb +0 -103
- data/spec/celluloid/tasks/task_fiber_spec.rb +0 -5
- data/spec/celluloid/tasks/task_thread_spec.rb +0 -5
- data/spec/celluloid/thread_handle_spec.rb +0 -26
- data/spec/celluloid/uuid_spec.rb +0 -11
@@ -0,0 +1,32 @@
|
|
1
|
+
RSpec.describe "Deprecated Celluloid::Future", actor_system: :global do
|
2
|
+
subject { Celluloid::Future.new }
|
3
|
+
|
4
|
+
it "creates future objects that can be retrieved later" do
|
5
|
+
future = Celluloid::Future.new { 40 + 2 }
|
6
|
+
expect(future.value).to eq(42)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "passes arguments to future blocks" do
|
10
|
+
future = Celluloid::Future.new(40) { |n| n + 2 }
|
11
|
+
expect(future.value).to eq(42)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "reraises exceptions that occur when the value is retrieved" do
|
15
|
+
class ExampleError < StandardError; end
|
16
|
+
|
17
|
+
future = Celluloid::Future.new { fail ExampleError, "oh noes crash!" }
|
18
|
+
expect { future.value }.to raise_exception(ExampleError)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "knows if it's got a value yet" do
|
22
|
+
future = Celluloid::Future.new { sleep CelluloidSpecs::TIMER_QUANTUM * 5 }
|
23
|
+
expect(future).not_to be_ready
|
24
|
+
sleep CelluloidSpecs::TIMER_QUANTUM * 6
|
25
|
+
expect(future).to be_ready
|
26
|
+
end
|
27
|
+
|
28
|
+
it "raises TimeoutError when the future times out" do
|
29
|
+
future = Celluloid::Future.new { sleep 2 }
|
30
|
+
expect { future.value(1) }.to raise_exception(Celluloid::TimeoutError)
|
31
|
+
end
|
32
|
+
end unless $CELLULOID_BACKPORTED == false
|
@@ -0,0 +1,1237 @@
|
|
1
|
+
RSpec.shared_examples "a Celluloid Actor" do
|
2
|
+
before(:each) do |example|
|
3
|
+
@fake_logger = Specs::FakeLogger.new(Celluloid.logger, example.description)
|
4
|
+
stub_const("Celluloid::Internals::Logger", @fake_logger)
|
5
|
+
end
|
6
|
+
|
7
|
+
around do |ex|
|
8
|
+
Celluloid.boot
|
9
|
+
ex.run
|
10
|
+
Celluloid.shutdown
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:task_klass) { Celluloid.task_class }
|
14
|
+
let(:actor_class) { ExampleActorClass.create(CelluloidSpecs.included_module, task_klass) }
|
15
|
+
let(:actor) { actor_class.new "Troy McClure" }
|
16
|
+
|
17
|
+
let(:logger) { Specs::FakeLogger.current }
|
18
|
+
|
19
|
+
it "returns the actor's class, not the proxy's" do
|
20
|
+
expect(actor.class).to eq(actor_class)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "compares with the actor's class in a case statement" do
|
24
|
+
expect(
|
25
|
+
case actor
|
26
|
+
when actor_class
|
27
|
+
true
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end,
|
31
|
+
).to be_truthy
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can be stored in hashes" do
|
35
|
+
expect(actor.hash).not_to eq(Kernel.hash)
|
36
|
+
expect(actor.object_id).not_to eq(Kernel.object_id)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "implements respond_to? correctly" do
|
40
|
+
expect(actor).to respond_to(:alive?)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "supports synchronous calls" do
|
44
|
+
expect(actor.greet).to eq("Hi, I'm Troy McClure")
|
45
|
+
end
|
46
|
+
|
47
|
+
context "with a method accepting a block" do
|
48
|
+
let(:actor) { james_bond_role.new }
|
49
|
+
let(:james_bond_role) do
|
50
|
+
Class.new do
|
51
|
+
include CelluloidSpecs.included_module
|
52
|
+
def act
|
53
|
+
"My name is #{yield('James', 'Bond')}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def give_role_to(actor, &block)
|
57
|
+
actor.act(&block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "supports synchronously passing a block to itself through a reference" do
|
63
|
+
result = actor.give_role_to(actor) { |name, surname| "#{surname}. #{name} #{surname}." }
|
64
|
+
expect(result).to eq("My name is Bond. James Bond.")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it "supports synchronous calls via #method" do
|
69
|
+
method = actor.method(:greet)
|
70
|
+
expect(method.call).to eq("Hi, I'm Troy McClure")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "supports #arity calls via #method" do
|
74
|
+
method = actor.method(:greet)
|
75
|
+
expect(method.arity).to be(0)
|
76
|
+
|
77
|
+
method = actor.method(:change_name)
|
78
|
+
expect(method.arity).to be(1)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "supports #name calls via #method" do
|
82
|
+
method = actor.method(:greet)
|
83
|
+
expect(method.name).to eq(:greet)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "supports #parameters via #method" do
|
87
|
+
method = actor.method(:greet)
|
88
|
+
expect(method.parameters).to eq([])
|
89
|
+
|
90
|
+
method = actor.method(:change_name)
|
91
|
+
expect(method.parameters.first.last).to eq(:new_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "supports future(:method) syntax for synchronous future calls" do
|
95
|
+
future = actor.future :greet
|
96
|
+
expect(future.value).to eq("Hi, I'm Troy McClure")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "supports future.method syntax for synchronous future calls" do
|
100
|
+
future = actor.future.greet
|
101
|
+
expect(future.value).to eq("Hi, I'm Troy McClure")
|
102
|
+
end
|
103
|
+
|
104
|
+
context "when a block is passed synchronously to an actor" do
|
105
|
+
let(:actor) { actor_class.new "Blocky Ralboa" }
|
106
|
+
|
107
|
+
it "the block is called" do
|
108
|
+
block_executed = false
|
109
|
+
actor.run { block_executed = true }
|
110
|
+
expect(block_executed).to be_truthy
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when there is a circular synchronous reference" do
|
115
|
+
let(:ponycopter) do
|
116
|
+
Class.new do
|
117
|
+
include CelluloidSpecs.included_module
|
118
|
+
|
119
|
+
def greet_by_proxy(actor)
|
120
|
+
actor.greet
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_s
|
124
|
+
"a ponycopter!"
|
125
|
+
end
|
126
|
+
end.new
|
127
|
+
end
|
128
|
+
|
129
|
+
let(:actor) { actor_class.new ponycopter }
|
130
|
+
|
131
|
+
it "is called correctly" do
|
132
|
+
expect(ponycopter.greet_by_proxy(actor)).to eq("Hi, I'm a ponycopter!")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
let(:recursive_klass1) do
|
137
|
+
Class.new do
|
138
|
+
include CelluloidSpecs.included_module
|
139
|
+
|
140
|
+
def recursion_test(recurse_through = nil)
|
141
|
+
if recurse_through
|
142
|
+
recurse_through.recursion_thunk(Celluloid::Actor.current)
|
143
|
+
else
|
144
|
+
Celluloid.detect_recursion
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
let(:recursive_klass2) do
|
151
|
+
Class.new do
|
152
|
+
include CelluloidSpecs.included_module
|
153
|
+
|
154
|
+
def recursion_thunk(other)
|
155
|
+
other.recursion_test
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it "detects recursion" do
|
161
|
+
actor1 = recursive_klass1.new
|
162
|
+
actor2 = recursive_klass2.new
|
163
|
+
|
164
|
+
expect(actor1.recursion_test).to be_falsey
|
165
|
+
expect(actor1.recursion_test(actor2)).to be_truthy
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "#respond_to?" do
|
169
|
+
context "with method_missing resolving to :first" do
|
170
|
+
specify { expect(actor).to respond_to(:first) }
|
171
|
+
|
172
|
+
context "when missing method is called" do
|
173
|
+
specify { expect(actor.first).to be :bar }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "with a private method" do
|
178
|
+
specify { expect(actor.respond_to?(:zomg_private)).to be_falsey }
|
179
|
+
|
180
|
+
context "when :include_private is passed" do
|
181
|
+
specify { expect(actor.respond_to?(:zomg_private, true)).to be_truthy }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context "when initialize sleeps" do
|
187
|
+
let(:actor) do
|
188
|
+
Class.new do
|
189
|
+
include CelluloidSpecs.included_module
|
190
|
+
|
191
|
+
def initialize
|
192
|
+
sleep 0.1
|
193
|
+
end
|
194
|
+
end.new
|
195
|
+
end
|
196
|
+
|
197
|
+
it "warns about suspending the initialize" do
|
198
|
+
expect(logger).to receive(:warn).with(/Dangerously suspending task: type=:call, meta={:dangerous_suspend=>true, :method_name=>:initialize}, status=:sleeping/)
|
199
|
+
|
200
|
+
actor.terminate
|
201
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context "with a user defined finalizer" do
|
206
|
+
it "calls the user defined finalizer" do
|
207
|
+
expect(actor.wrapped_object).to receive(:my_finalizer)
|
208
|
+
actor.terminate
|
209
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context "when actor sleeps in finalizer" do
|
214
|
+
let(:actor) do
|
215
|
+
Class.new do
|
216
|
+
include CelluloidSpecs.included_module
|
217
|
+
|
218
|
+
finalizer :cleanup
|
219
|
+
|
220
|
+
def cleanup
|
221
|
+
sleep 0.1
|
222
|
+
end
|
223
|
+
end.new
|
224
|
+
end
|
225
|
+
|
226
|
+
it "warns about suspending the finalizer" do
|
227
|
+
allow(logger).to receive(:warn)
|
228
|
+
allow(logger).to receive(:crash).with(/finalizer crashed!/, Celluloid::Task::TerminatedError)
|
229
|
+
expect(logger).to receive(:warn).with(/Dangerously suspending task: type=:finalizer, meta={:dangerous_suspend=>true, :method_name=>:cleanup}, status=:sleeping/)
|
230
|
+
actor.terminate
|
231
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it "supports async(:method) syntax for asynchronous calls" do
|
236
|
+
actor.async :change_name, "Charlie Sheen"
|
237
|
+
expect(actor.greet).to eq("Hi, I'm Charlie Sheen")
|
238
|
+
end
|
239
|
+
|
240
|
+
it "supports async.method syntax for asynchronous calls" do
|
241
|
+
actor.async.change_name "Charlie Sheen"
|
242
|
+
expect(actor.greet).to eq("Hi, I'm Charlie Sheen")
|
243
|
+
end
|
244
|
+
|
245
|
+
it "supports async.method syntax for asynchronous calls to itself" do
|
246
|
+
actor.change_name_async "Charlie Sheen"
|
247
|
+
expect(actor.greet).to eq("Hi, I'm Charlie Sheen")
|
248
|
+
end
|
249
|
+
|
250
|
+
it "allows an actor to call private methods asynchronously" do
|
251
|
+
actor.call_private
|
252
|
+
expect(actor.private_called).to be_truthy
|
253
|
+
end
|
254
|
+
|
255
|
+
it "knows if it's inside actor scope" do
|
256
|
+
expect(Celluloid).not_to be_actor
|
257
|
+
expect(actor.run do
|
258
|
+
Celluloid.actor?
|
259
|
+
end).to be_falsey
|
260
|
+
expect(actor.run_on_receiver do
|
261
|
+
Celluloid.actor?
|
262
|
+
end).to be_truthy
|
263
|
+
expect(actor).to be_actor
|
264
|
+
end
|
265
|
+
|
266
|
+
it "inspects properly" do
|
267
|
+
expect(actor.inspect).to match(/Celluloid::Proxy::Cell\(/)
|
268
|
+
expect(actor.inspect).to match(/#{actor_class}/)
|
269
|
+
expect(actor.inspect).to include('@name="Troy McClure"')
|
270
|
+
expect(actor.inspect).not_to include("@celluloid")
|
271
|
+
end
|
272
|
+
|
273
|
+
it "inspects properly when dead" do
|
274
|
+
actor.terminate
|
275
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
276
|
+
expect(actor.inspect).to match(/Celluloid::Proxy::Cell\(/)
|
277
|
+
expect(actor.inspect).to match(/#{actor_class}/)
|
278
|
+
expect(actor.inspect).to include("dead")
|
279
|
+
end
|
280
|
+
|
281
|
+
it "reports private methods properly when dead" do
|
282
|
+
actor.terminate
|
283
|
+
expect { actor.private_methods }.not_to raise_error
|
284
|
+
end
|
285
|
+
|
286
|
+
context "with actors referencing each other" do
|
287
|
+
let(:klass) do
|
288
|
+
Class.new do
|
289
|
+
include CelluloidSpecs.included_module
|
290
|
+
|
291
|
+
attr_accessor :other
|
292
|
+
|
293
|
+
def initialize(other = nil)
|
294
|
+
@other = other
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
it "supports recursive inspect" do
|
300
|
+
itchy = klass.new
|
301
|
+
scratchy = klass.new(itchy)
|
302
|
+
itchy.other = scratchy
|
303
|
+
|
304
|
+
inspection = itchy.inspect
|
305
|
+
expect(inspection).to match(/Celluloid::Proxy::Cell\(/)
|
306
|
+
expect(inspection).to include("...")
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
it "allows access to the wrapped object" do
|
311
|
+
expect(actor.wrapped_object).to be_a actor_class
|
312
|
+
end
|
313
|
+
|
314
|
+
it "warns about leaked wrapped objects via #inspect" do
|
315
|
+
expect(actor.inspect).not_to include Celluloid::BARE_OBJECT_WARNING_MESSAGE
|
316
|
+
expect(actor.inspect_thunk).not_to include Celluloid::BARE_OBJECT_WARNING_MESSAGE
|
317
|
+
expect(actor.wrapped_object.inspect).to include Celluloid::BARE_OBJECT_WARNING_MESSAGE
|
318
|
+
end
|
319
|
+
|
320
|
+
it "can override #send" do
|
321
|
+
expect(actor.send("foo")).to eq("oof")
|
322
|
+
end
|
323
|
+
|
324
|
+
if RUBY_PLATFORM == "java" && Celluloid.task_class != Celluloid::Task::Fibered
|
325
|
+
context "when executing under JRuby" do
|
326
|
+
let(:actor) do
|
327
|
+
Class.new do
|
328
|
+
include CelluloidSpecs.included_module
|
329
|
+
|
330
|
+
def current_java_thread
|
331
|
+
Thread.current.to_java.getNativeThread
|
332
|
+
end
|
333
|
+
|
334
|
+
def name_inside_task
|
335
|
+
Thread.current.to_java.getNativeThread.get_name
|
336
|
+
end
|
337
|
+
end.new
|
338
|
+
end
|
339
|
+
|
340
|
+
it "sets execution info" do
|
341
|
+
expect(actor.name_inside_task).to match(/\[Celluloid\] #<Class:0x[0-9a-f]+>#name_inside_task/)
|
342
|
+
end
|
343
|
+
|
344
|
+
context "when the task if finished" do
|
345
|
+
let(:jthread) { actor.current_java_thread }
|
346
|
+
|
347
|
+
before do
|
348
|
+
Specs.sleep_and_wait_until { jthread.get_name !~ /^\[Celluloid\] #<Class:0x[0-9a-f]+>#current_java_thread$/ }
|
349
|
+
end
|
350
|
+
|
351
|
+
it "unsets execution info after task completion" do
|
352
|
+
expect(jthread.get_name).to match(/^Ruby-/)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context "mocking methods" do
|
359
|
+
before do
|
360
|
+
expect(actor.wrapped_object).to receive(:external_hello).once.and_return "World"
|
361
|
+
end
|
362
|
+
|
363
|
+
it "works externally via the proxy" do
|
364
|
+
expect(actor.external_hello).to eq("World")
|
365
|
+
end
|
366
|
+
|
367
|
+
it "works internally when called on self" do
|
368
|
+
expect(actor.internal_hello).to eq("World")
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
context "mocking out the proxy" do
|
373
|
+
it "allows mocking return values" do
|
374
|
+
expect(actor).to receive(:name).and_return "Spiderman"
|
375
|
+
expect(actor.name).to eq "Spiderman"
|
376
|
+
end
|
377
|
+
|
378
|
+
it "allows mocking raises" do
|
379
|
+
expect(actor).to receive(:greet).and_raise ArgumentError
|
380
|
+
expect { actor.greet }.to raise_error(ArgumentError)
|
381
|
+
expect(actor).to be_alive
|
382
|
+
end
|
383
|
+
|
384
|
+
xit "allows mocking async calls via the async proxy" do
|
385
|
+
# TODO: Verify via CIA... This appears to be working, with new celluloid/rspec handler.
|
386
|
+
# pending "Fails due to RSpec's new expectation verification"
|
387
|
+
# fail "TODO: may never work in newer versions of RSpec (no method on Proxy::Async)"
|
388
|
+
expect(actor.async).to receive(:greet).once
|
389
|
+
actor.async.greet
|
390
|
+
end
|
391
|
+
|
392
|
+
it "allows mocking async calls via #async send" do
|
393
|
+
expect(actor).to receive(:async).once.with(:greet)
|
394
|
+
actor.async :greet
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
context :exceptions do
|
399
|
+
context "with a dead actor" do
|
400
|
+
let(:actor) { actor_class.new "James Dean" } # is this in bad taste?
|
401
|
+
|
402
|
+
it "reraises exceptions which occur during synchronous calls in the sender" do
|
403
|
+
allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
|
404
|
+
expect { actor.crash }.to raise_exception(ExampleCrash)
|
405
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
406
|
+
end
|
407
|
+
|
408
|
+
it "includes both sender and receiver in exception traces" do
|
409
|
+
allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
|
410
|
+
|
411
|
+
example_receiver = Class.new do
|
412
|
+
include CelluloidSpecs.included_module
|
413
|
+
|
414
|
+
define_method(:receiver_method) do
|
415
|
+
fail ExampleCrash, "the spec purposely crashed me :("
|
416
|
+
end
|
417
|
+
end.new
|
418
|
+
|
419
|
+
example_caller = Class.new do
|
420
|
+
include CelluloidSpecs.included_module
|
421
|
+
|
422
|
+
define_method(:sender_method) do
|
423
|
+
example_receiver.receiver_method
|
424
|
+
end
|
425
|
+
end.new
|
426
|
+
|
427
|
+
ex = example_caller.sender_method rescue $ERROR_INFO
|
428
|
+
Specs.sleep_and_wait_until { !example_caller.alive? }
|
429
|
+
Specs.sleep_and_wait_until { !example_receiver.alive? }
|
430
|
+
|
431
|
+
expect(ex).to be_a ExampleCrash
|
432
|
+
expect(ex.backtrace.grep(/`sender_method'/)).to be_truthy
|
433
|
+
expect(ex.backtrace.grep(/`receiver_method'/)).to be_truthy
|
434
|
+
end
|
435
|
+
|
436
|
+
it "raises DeadActorError if methods are synchronously called on a dead actor" do
|
437
|
+
allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
|
438
|
+
actor.crash rescue ExampleCrash
|
439
|
+
|
440
|
+
# TODO: avoid this somehow
|
441
|
+
sleep 0.1 # hax to prevent a race between exit handling and the next call
|
442
|
+
|
443
|
+
expect { actor.greet }.to raise_exception(Celluloid::DeadActorError)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
context :abort do
|
449
|
+
let(:actor) { actor_class.new "Al Pacino" }
|
450
|
+
|
451
|
+
it "raises exceptions in the sender but keeps running" do
|
452
|
+
expect do
|
453
|
+
actor.crash_with_abort "You die motherfucker!", :bar
|
454
|
+
end.to raise_exception(ExampleCrash, "You die motherfucker!")
|
455
|
+
|
456
|
+
expect(actor).to be_alive
|
457
|
+
end
|
458
|
+
|
459
|
+
it "converts strings to runtime errors" do
|
460
|
+
expect do
|
461
|
+
actor.crash_with_abort_raw "foo"
|
462
|
+
end.to raise_exception(RuntimeError, "foo")
|
463
|
+
end
|
464
|
+
|
465
|
+
it "crashes the sender if we pass neither String nor Exception" do
|
466
|
+
allow(logger).to receive(:crash).with("Actor crashed!", TypeError)
|
467
|
+
expect do
|
468
|
+
actor.crash_with_abort_raw 10
|
469
|
+
end.to raise_exception(TypeError, "Exception object/String expected, but Fixnum received")
|
470
|
+
|
471
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
472
|
+
expect(actor).not_to be_alive
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
context :termination do
|
477
|
+
let(:actor) { actor_class.new "Arnold Schwarzenegger" }
|
478
|
+
|
479
|
+
context "when alive" do
|
480
|
+
specify { expect(actor).to be_alive }
|
481
|
+
specify { expect(actor).to_not be_dead }
|
482
|
+
end
|
483
|
+
|
484
|
+
context "when terminated" do
|
485
|
+
before do
|
486
|
+
actor.terminate
|
487
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
488
|
+
end
|
489
|
+
|
490
|
+
specify { expect(actor).not_to be_alive }
|
491
|
+
context "when terminated!" do
|
492
|
+
specify do
|
493
|
+
expect do
|
494
|
+
actor.terminate!
|
495
|
+
end.to raise_exception(Celluloid::DeadActorError, "actor already terminated")
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
context "when terminated by a Call::Sync" do
|
501
|
+
before do
|
502
|
+
actor.shutdown
|
503
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
504
|
+
end
|
505
|
+
|
506
|
+
specify { expect(actor).not_to be_alive }
|
507
|
+
end
|
508
|
+
|
509
|
+
unless RUBY_PLATFORM == "java" || RUBY_ENGINE == "rbx"
|
510
|
+
context "when killed" do
|
511
|
+
before do
|
512
|
+
Celluloid::Actor.kill(actor)
|
513
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
514
|
+
end
|
515
|
+
|
516
|
+
specify { expect(actor).not_to be_alive }
|
517
|
+
specify { expect(actor).to be_dead }
|
518
|
+
|
519
|
+
context "when called" do
|
520
|
+
specify do
|
521
|
+
expect { actor.greet }.to raise_exception(Celluloid::DeadActorError)
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
context "when celluloid is shutdown" do
|
527
|
+
before do
|
528
|
+
allow(Celluloid::Actor).to receive(:kill).and_call_original
|
529
|
+
actor
|
530
|
+
Celluloid.shutdown
|
531
|
+
end
|
532
|
+
|
533
|
+
it "terminates cleanly on Celluloid shutdown" do
|
534
|
+
expect(Celluloid::Actor).not_to have_received(:kill)
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
context "when sleeping" do
|
540
|
+
before do
|
541
|
+
actor.async.sleepy 10
|
542
|
+
actor.greet # make sure the actor has started sleeping
|
543
|
+
end
|
544
|
+
|
545
|
+
context "when terminated" do
|
546
|
+
it "logs a debug" do
|
547
|
+
expect(logger).to receive(:debug).with(/^Terminating task: type=:call, meta={:dangerous_suspend=>false, :method_name=>:sleepy}, status=:sleeping/)
|
548
|
+
actor.terminate
|
549
|
+
Specs.sleep_and_wait_until { !actor.alive? }
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
describe "#current_actor" do
|
556
|
+
context "when called on an actor" do
|
557
|
+
let(:actor) { actor_class.new "Roger Daltrey" }
|
558
|
+
|
559
|
+
it "knows the current actor" do
|
560
|
+
expect(actor.current_actor).to eq actor
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
context "when called outside an actor" do
|
565
|
+
specify { expect { Celluloid.current_actor }.to raise_exception(Celluloid::NotActorError) }
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
context :linking do
|
570
|
+
before :each do
|
571
|
+
@kevin = actor_class.new "Kevin Bacon" # Some six degrees action here
|
572
|
+
@charlie = actor_class.new "Charlie Sheen"
|
573
|
+
end
|
574
|
+
|
575
|
+
let(:supervisor_class) do
|
576
|
+
Class.new do # like a boss
|
577
|
+
include CelluloidSpecs.included_module
|
578
|
+
trap_exit :lambaste_subordinate
|
579
|
+
|
580
|
+
def initialize(name)
|
581
|
+
@name = name
|
582
|
+
@subordinate_lambasted = false
|
583
|
+
end
|
584
|
+
|
585
|
+
def subordinate_lambasted?
|
586
|
+
@subordinate_lambasted
|
587
|
+
end
|
588
|
+
|
589
|
+
def lambaste_subordinate(_actor, _reason)
|
590
|
+
@subordinate_lambasted = true
|
591
|
+
end
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
it "links to other actors" do
|
596
|
+
@kevin.link @charlie
|
597
|
+
expect(@kevin.monitoring?(@charlie)).to be_truthy
|
598
|
+
expect(@kevin.linked_to?(@charlie)).to be_truthy
|
599
|
+
expect(@charlie.monitoring?(@kevin)).to be_truthy
|
600
|
+
expect(@charlie.linked_to?(@kevin)).to be_truthy
|
601
|
+
end
|
602
|
+
|
603
|
+
it "unlinks from other actors" do
|
604
|
+
@kevin.link @charlie
|
605
|
+
@kevin.unlink @charlie
|
606
|
+
|
607
|
+
expect(@kevin.monitoring?(@charlie)).to be_falsey
|
608
|
+
expect(@kevin.linked_to?(@charlie)).to be_falsey
|
609
|
+
expect(@charlie.monitoring?(@kevin)).to be_falsey
|
610
|
+
expect(@charlie.linked_to?(@kevin)).to be_falsey
|
611
|
+
end
|
612
|
+
|
613
|
+
it "monitors other actors unidirectionally" do
|
614
|
+
@kevin.monitor @charlie
|
615
|
+
|
616
|
+
expect(@kevin.monitoring?(@charlie)).to be_truthy
|
617
|
+
expect(@kevin.linked_to?(@charlie)).to be_falsey
|
618
|
+
expect(@charlie.monitoring?(@kevin)).to be_falsey
|
619
|
+
expect(@charlie.linked_to?(@kevin)).to be_falsey
|
620
|
+
end
|
621
|
+
|
622
|
+
it "unmonitors other actors" do
|
623
|
+
@kevin.monitor @charlie
|
624
|
+
@kevin.unmonitor @charlie
|
625
|
+
|
626
|
+
expect(@kevin.monitoring?(@charlie)).to be_falsey
|
627
|
+
expect(@kevin.linked_to?(@charlie)).to be_falsey
|
628
|
+
expect(@charlie.monitoring?(@kevin)).to be_falsey
|
629
|
+
expect(@charlie.linked_to?(@kevin)).to be_falsey
|
630
|
+
end
|
631
|
+
|
632
|
+
it "traps exit messages from other actors" do
|
633
|
+
allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
|
634
|
+
chuck = supervisor_class.new "Chuck Lorre"
|
635
|
+
chuck.link @charlie
|
636
|
+
|
637
|
+
expect do
|
638
|
+
@charlie.crash
|
639
|
+
end.to raise_exception(ExampleCrash)
|
640
|
+
|
641
|
+
sleep 0.1 # hax to prevent a race between exit handling and the next call
|
642
|
+
expect(chuck).to be_subordinate_lambasted
|
643
|
+
end
|
644
|
+
|
645
|
+
it "traps exit messages from other actors in subclasses" do
|
646
|
+
allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
|
647
|
+
|
648
|
+
supervisor_subclass = Class.new(supervisor_class)
|
649
|
+
chuck = supervisor_subclass.new "Chuck Lorre"
|
650
|
+
chuck.link @charlie
|
651
|
+
|
652
|
+
expect do
|
653
|
+
@charlie.crash
|
654
|
+
end.to raise_exception(ExampleCrash)
|
655
|
+
|
656
|
+
sleep 0.1 # hax to prevent a race between exit handling and the next call
|
657
|
+
expect(chuck).to be_subordinate_lambasted
|
658
|
+
end
|
659
|
+
|
660
|
+
it "unlinks from a dead linked actor" do
|
661
|
+
allow(logger).to receive(:crash).with("Actor crashed!", ExampleCrash)
|
662
|
+
chuck = supervisor_class.new "Chuck Lorre"
|
663
|
+
chuck.link @charlie
|
664
|
+
|
665
|
+
expect do
|
666
|
+
@charlie.crash
|
667
|
+
end.to raise_exception(ExampleCrash)
|
668
|
+
|
669
|
+
sleep 0.1 # hax to prevent a race between exit handling and the next call
|
670
|
+
expect(chuck.links.count).to be(0)
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
context :signaling do
|
675
|
+
before do
|
676
|
+
@signaler = Class.new do
|
677
|
+
include CelluloidSpecs.included_module
|
678
|
+
|
679
|
+
def initialize
|
680
|
+
@waiting = false
|
681
|
+
@signaled = false
|
682
|
+
end
|
683
|
+
|
684
|
+
def wait_for_signal
|
685
|
+
fail "already signaled" if @signaled
|
686
|
+
|
687
|
+
@waiting = true
|
688
|
+
value = wait :ponycopter
|
689
|
+
|
690
|
+
@waiting = false
|
691
|
+
@signaled = true
|
692
|
+
value
|
693
|
+
end
|
694
|
+
|
695
|
+
def send_signal(value)
|
696
|
+
signal :ponycopter, value
|
697
|
+
end
|
698
|
+
|
699
|
+
def waiting?
|
700
|
+
@waiting
|
701
|
+
end
|
702
|
+
|
703
|
+
def signaled?
|
704
|
+
@signaled
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
it "allows methods within the same object to signal each other" do
|
710
|
+
obj = @signaler.new
|
711
|
+
expect(obj).not_to be_signaled
|
712
|
+
|
713
|
+
obj.async.wait_for_signal
|
714
|
+
expect(obj).not_to be_signaled
|
715
|
+
expect(obj).to be_waiting
|
716
|
+
|
717
|
+
obj.send_signal :foobar
|
718
|
+
expect(obj).to be_signaled
|
719
|
+
expect(obj).not_to be_waiting
|
720
|
+
end
|
721
|
+
|
722
|
+
it "sends values along with signals" do
|
723
|
+
obj = @signaler.new
|
724
|
+
expect(obj).not_to be_signaled
|
725
|
+
|
726
|
+
future = obj.future(:wait_for_signal)
|
727
|
+
|
728
|
+
expect(obj).to be_waiting
|
729
|
+
expect(obj).not_to be_signaled
|
730
|
+
|
731
|
+
expect(obj.send_signal(:foobar)).to be_truthy
|
732
|
+
expect(future.value).to be(:foobar)
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
context :exclusive do
|
737
|
+
subject do
|
738
|
+
Class.new do
|
739
|
+
include CelluloidSpecs.included_module
|
740
|
+
|
741
|
+
attr_reader :tasks
|
742
|
+
|
743
|
+
def initialize
|
744
|
+
@tasks = []
|
745
|
+
end
|
746
|
+
|
747
|
+
def log_task(task)
|
748
|
+
@tasks << task
|
749
|
+
end
|
750
|
+
|
751
|
+
def exclusive_with_block_log_task(task)
|
752
|
+
exclusive do
|
753
|
+
sleep CelluloidSpecs::TIMER_QUANTUM
|
754
|
+
log_task(task)
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
def exclusive_log_task(task)
|
759
|
+
sleep CelluloidSpecs::TIMER_QUANTUM
|
760
|
+
log_task(task)
|
761
|
+
end
|
762
|
+
exclusive :exclusive_log_task
|
763
|
+
|
764
|
+
def check_not_exclusive
|
765
|
+
Celluloid.exclusive?
|
766
|
+
end
|
767
|
+
|
768
|
+
def check_exclusive
|
769
|
+
exclusive { Celluloid.exclusive? }
|
770
|
+
end
|
771
|
+
|
772
|
+
def nested_exclusive_example
|
773
|
+
exclusive { exclusive { nil }; Celluloid.exclusive? }
|
774
|
+
end
|
775
|
+
end.new
|
776
|
+
end
|
777
|
+
|
778
|
+
it "executes methods in the proper order with block form" do
|
779
|
+
subject.async.exclusive_with_block_log_task(:one)
|
780
|
+
subject.async.log_task(:two)
|
781
|
+
sleep CelluloidSpecs::TIMER_QUANTUM * 2
|
782
|
+
expect(subject.tasks).to eq([:one, :two])
|
783
|
+
end
|
784
|
+
|
785
|
+
it "executes methods in the proper order with a class-level annotation" do
|
786
|
+
subject.async.exclusive_log_task :one
|
787
|
+
subject.async.log_task :two
|
788
|
+
sleep CelluloidSpecs::TIMER_QUANTUM * 2
|
789
|
+
expect(subject.tasks).to eq([:one, :two])
|
790
|
+
end
|
791
|
+
|
792
|
+
it "knows when it's in exclusive mode" do
|
793
|
+
expect(subject.check_not_exclusive).to be_falsey
|
794
|
+
expect(subject.check_exclusive).to be_truthy
|
795
|
+
end
|
796
|
+
|
797
|
+
it "remains in exclusive mode inside nested blocks" do
|
798
|
+
expect(subject.nested_exclusive_example).to be_truthy
|
799
|
+
end
|
800
|
+
end
|
801
|
+
|
802
|
+
context "exclusive classes" do
|
803
|
+
subject do
|
804
|
+
Class.new do
|
805
|
+
include CelluloidSpecs.included_module
|
806
|
+
exclusive
|
807
|
+
|
808
|
+
attr_reader :tasks
|
809
|
+
|
810
|
+
def initialize
|
811
|
+
@tasks = []
|
812
|
+
end
|
813
|
+
|
814
|
+
def eat_donuts
|
815
|
+
sleep CelluloidSpecs::TIMER_QUANTUM
|
816
|
+
@tasks << "donuts"
|
817
|
+
end
|
818
|
+
|
819
|
+
def drink_coffee
|
820
|
+
@tasks << "coffee"
|
821
|
+
end
|
822
|
+
end
|
823
|
+
end
|
824
|
+
|
825
|
+
context "with two async methods called" do
|
826
|
+
let(:actor) { subject.new }
|
827
|
+
|
828
|
+
before do
|
829
|
+
actor.async.eat_donuts
|
830
|
+
actor.async.drink_coffee
|
831
|
+
sleep CelluloidSpecs::TIMER_QUANTUM * 2
|
832
|
+
end
|
833
|
+
|
834
|
+
it "executes in an exclusive order" do
|
835
|
+
expect(actor.tasks).to eq(%w(donuts coffee))
|
836
|
+
end
|
837
|
+
end
|
838
|
+
end
|
839
|
+
|
840
|
+
context :receiving do
|
841
|
+
before do
|
842
|
+
@receiver = Class.new do
|
843
|
+
include CelluloidSpecs.included_module
|
844
|
+
execute_block_on_receiver :signal_myself
|
845
|
+
|
846
|
+
def signal_myself(obj, &block)
|
847
|
+
current_actor.mailbox << obj
|
848
|
+
receive(&block)
|
849
|
+
end
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
let(:receiver) { @receiver.new }
|
854
|
+
let(:message) { Object.new }
|
855
|
+
|
856
|
+
it "allows unconditional receive" do
|
857
|
+
expect(receiver.signal_myself(message)).to eq(message)
|
858
|
+
end
|
859
|
+
|
860
|
+
it "allows arbitrary selective receive" do
|
861
|
+
received_obj = receiver.signal_myself(message) { |o| o == message }
|
862
|
+
expect(received_obj).to eq(message)
|
863
|
+
end
|
864
|
+
|
865
|
+
context "when exceeding a given time out" do
|
866
|
+
let(:interval) { 0.1 }
|
867
|
+
|
868
|
+
it "times out" do
|
869
|
+
# Barely didn't make it once on MRI, so attempting to "unrefactor"
|
870
|
+
started_at = Time.now
|
871
|
+
result = receiver.receive(interval) { false }
|
872
|
+
ended_at = Time.now - started_at
|
873
|
+
|
874
|
+
expect(result).to_not be
|
875
|
+
expect(ended_at).to be_within(CelluloidSpecs::TIMER_QUANTUM).of interval
|
876
|
+
end
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
context :timers do
|
881
|
+
let(:actor) do
|
882
|
+
Class.new do
|
883
|
+
include CelluloidSpecs.included_module
|
884
|
+
|
885
|
+
def initialize
|
886
|
+
@sleeping = false
|
887
|
+
@fired = false
|
888
|
+
end
|
889
|
+
|
890
|
+
def do_sleep(n)
|
891
|
+
@sleeping = true
|
892
|
+
sleep n
|
893
|
+
@sleeping = false
|
894
|
+
end
|
895
|
+
|
896
|
+
def sleeping?
|
897
|
+
@sleeping
|
898
|
+
end
|
899
|
+
|
900
|
+
def fire_after(n)
|
901
|
+
after(n) { @fired = true }
|
902
|
+
end
|
903
|
+
|
904
|
+
def fire_every(n)
|
905
|
+
@fired = 0
|
906
|
+
every(n) { @fired += 1 }
|
907
|
+
end
|
908
|
+
|
909
|
+
def fired?
|
910
|
+
!!@fired
|
911
|
+
end
|
912
|
+
|
913
|
+
attr_reader :fired
|
914
|
+
end.new
|
915
|
+
end
|
916
|
+
|
917
|
+
let(:interval) { CelluloidSpecs::TIMER_QUANTUM * 10 }
|
918
|
+
let(:sleep_interval) { interval + CelluloidSpecs::TIMER_QUANTUM } # wonky! #/
|
919
|
+
|
920
|
+
it "suspends execution of a method (but not the actor) for a given time" do
|
921
|
+
# Sleep long enough to ensure we're actually seeing behavior when asleep
|
922
|
+
# but not so long as to delay the test suite unnecessarily
|
923
|
+
started_at = Time.now
|
924
|
+
|
925
|
+
future = actor.future(:do_sleep, interval)
|
926
|
+
sleep(interval / 2) # wonky! :/
|
927
|
+
expect(actor).to be_sleeping
|
928
|
+
|
929
|
+
future.value
|
930
|
+
# I got 0.558 (in a slighly busy MRI) which is outside 0.05 of 0.5, so let's use (0.05 * 2)
|
931
|
+
expect(Time.now - started_at).to be_within(CelluloidSpecs::TIMER_QUANTUM * 2).of interval
|
932
|
+
end
|
933
|
+
|
934
|
+
it "schedules timers which fire in the future" do
|
935
|
+
actor.fire_after(interval)
|
936
|
+
expect(actor).not_to be_fired
|
937
|
+
|
938
|
+
sleep sleep_interval
|
939
|
+
expect(actor).to be_fired
|
940
|
+
end
|
941
|
+
|
942
|
+
it "schedules recurring timers which fire in the future" do
|
943
|
+
actor.fire_every(interval)
|
944
|
+
expect(actor.fired).to be_zero
|
945
|
+
|
946
|
+
sleep sleep_interval
|
947
|
+
expect(actor.fired).to be 1
|
948
|
+
|
949
|
+
2.times { sleep sleep_interval }
|
950
|
+
expect(actor.fired).to be 3
|
951
|
+
end
|
952
|
+
|
953
|
+
it "cancels timers before they fire" do
|
954
|
+
timer = actor.fire_after(interval)
|
955
|
+
expect(actor).not_to be_fired
|
956
|
+
timer.cancel
|
957
|
+
|
958
|
+
sleep sleep_interval
|
959
|
+
expect(actor).not_to be_fired
|
960
|
+
end
|
961
|
+
|
962
|
+
it "allows delays from outside the actor" do
|
963
|
+
fired = false
|
964
|
+
|
965
|
+
actor.after(interval) { fired = true }
|
966
|
+
expect(fired).to be_falsey
|
967
|
+
|
968
|
+
sleep sleep_interval
|
969
|
+
expect(fired).to be_truthy
|
970
|
+
end
|
971
|
+
end
|
972
|
+
|
973
|
+
context :tasks do
|
974
|
+
let(:actor) do
|
975
|
+
Class.new do
|
976
|
+
include CelluloidSpecs.included_module
|
977
|
+
attr_reader :blocker
|
978
|
+
|
979
|
+
def initialize
|
980
|
+
@blocker = Class.new do
|
981
|
+
include Celluloid
|
982
|
+
|
983
|
+
def block
|
984
|
+
wait :unblock
|
985
|
+
end
|
986
|
+
|
987
|
+
def unblock
|
988
|
+
signal :unblock
|
989
|
+
end
|
990
|
+
end.new
|
991
|
+
end
|
992
|
+
|
993
|
+
def blocking_call
|
994
|
+
@blocker.block
|
995
|
+
end
|
996
|
+
end.new
|
997
|
+
end
|
998
|
+
|
999
|
+
it "knows which tasks are waiting on calls to other actors" do
|
1000
|
+
tasks = actor.tasks
|
1001
|
+
expect(tasks.size).to be 1
|
1002
|
+
|
1003
|
+
actor.future(:blocking_call)
|
1004
|
+
sleep 0.1 # hax! waiting for ^^^ call to actually start
|
1005
|
+
|
1006
|
+
tasks = actor.tasks
|
1007
|
+
expect(tasks.size).to be 2
|
1008
|
+
|
1009
|
+
blocking_task = tasks.find { |t| t.status != :running }
|
1010
|
+
expect(blocking_task).to be_a task_klass
|
1011
|
+
expect(blocking_task.status).to be :callwait
|
1012
|
+
|
1013
|
+
actor.blocker.unblock
|
1014
|
+
sleep 0.1 # hax again :(
|
1015
|
+
expect(actor.tasks.size).to be 1
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
context :mailbox_class do
|
1020
|
+
class ExampleMailbox < Celluloid::Mailbox; end
|
1021
|
+
|
1022
|
+
subject do
|
1023
|
+
Class.new do
|
1024
|
+
include CelluloidSpecs.included_module
|
1025
|
+
mailbox_class ExampleMailbox
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
it "uses user-specified mailboxes" do
|
1030
|
+
expect(subject.new.mailbox).to be_a ExampleMailbox
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
it "retains custom mailboxes when subclassed" do
|
1034
|
+
subclass = Class.new(subject)
|
1035
|
+
expect(subclass.new.mailbox).to be_a ExampleMailbox
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
context :mailbox_size do
|
1040
|
+
subject do
|
1041
|
+
Class.new do
|
1042
|
+
include CelluloidSpecs.included_module
|
1043
|
+
mailbox_size 100
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
it "configures the mailbox limit" do
|
1048
|
+
expect(subject.new.mailbox.max_size).to eq(100)
|
1049
|
+
end
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
context "#proxy_class" do
|
1053
|
+
subject do
|
1054
|
+
Class.new do
|
1055
|
+
include CelluloidSpecs.included_module
|
1056
|
+
|
1057
|
+
klass = Class.new(Celluloid::Proxy::Cell) do
|
1058
|
+
def subclass_proxy?
|
1059
|
+
true
|
1060
|
+
end
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
proxy_class klass
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
it "uses user-specified proxy" do
|
1068
|
+
expect { subject.new.subclass_proxy? }.to_not raise_error
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
it "retains custom proxy when subclassed" do
|
1072
|
+
subclass = Class.new(subject)
|
1073
|
+
expect(subclass.new.subclass_proxy?).to be(true)
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
context "when overriding a actor's method" do
|
1077
|
+
subject do
|
1078
|
+
Class.new do
|
1079
|
+
include CelluloidSpecs.included_module
|
1080
|
+
|
1081
|
+
klass = Class.new(Celluloid::Proxy::Cell) do
|
1082
|
+
def dividing_3_by(number)
|
1083
|
+
fail ArgumentError, "<facepalm>" if number.zero?
|
1084
|
+
super
|
1085
|
+
end
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
def dividing_3_by(number)
|
1089
|
+
3 / number
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
proxy_class klass
|
1093
|
+
end.new
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
context "when invoked with an invalid parameter for that method" do
|
1097
|
+
it "calls the overloaded method" do
|
1098
|
+
expect { subject.dividing_3_by(0) }.to raise_error(ArgumentError, "<facepalm>")
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
it "does not crash the actor" do
|
1102
|
+
subject.dividing_3_by(0) rescue ArgumentError
|
1103
|
+
expect(subject.dividing_3_by(3)).to eq(1)
|
1104
|
+
end
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
context "when it includes method checking" do
|
1109
|
+
module MethodChecking
|
1110
|
+
def method_missing(meth, *args)
|
1111
|
+
return super if [:__send__, :respond_to?, :method, :class, :__class__].include? meth
|
1112
|
+
|
1113
|
+
unmet_requirement = nil
|
1114
|
+
|
1115
|
+
arity = method(meth).arity
|
1116
|
+
if arity >= 0
|
1117
|
+
unmet_requirement = arity.to_s if args.size != arity
|
1118
|
+
elsif arity < -1
|
1119
|
+
mandatory_args = -arity - 1
|
1120
|
+
unmet_requirement = "#{mandatory_args}+" if args.size < mandatory_args
|
1121
|
+
end
|
1122
|
+
fail ArgumentError, "wrong number of arguments (#{args.size} for #{unmet_requirement})" if unmet_requirement
|
1123
|
+
|
1124
|
+
super
|
1125
|
+
end
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
subject do
|
1129
|
+
Class.new do
|
1130
|
+
include CelluloidSpecs.included_module
|
1131
|
+
|
1132
|
+
klass = Class.new(Celluloid::Proxy::Cell) do
|
1133
|
+
include MethodChecking
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
def madness
|
1137
|
+
"This is madness!"
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
def this_is_not_madness(word1, word2, word3, *_args)
|
1141
|
+
fail "This is madness!" unless [word1, word2, word3] == [:this, :is, :Sparta]
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
proxy_class klass
|
1145
|
+
end.new
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
context "when invoking a non-existing method" do
|
1149
|
+
it "raises a NoMethodError" do
|
1150
|
+
expect { subject.method_to_madness }.to raise_error(NoMethodError)
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
it "does not crash the actor" do
|
1154
|
+
subject.method_to_madness rescue NoMethodError
|
1155
|
+
expect(subject.madness).to eq("This is madness!")
|
1156
|
+
end
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
context "when invoking a existing method with incorrect args" do
|
1160
|
+
context "with too many arguments" do
|
1161
|
+
it "raises an ArgumentError" do
|
1162
|
+
expect { subject.madness(:Sparta) }.to raise_error(ArgumentError, "wrong number of arguments (1 for 0)")
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
it "does not crash the actor" do
|
1166
|
+
subject.madness(:Sparta) rescue ArgumentError
|
1167
|
+
expect(subject.madness).to eq("This is madness!")
|
1168
|
+
end
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
context "with not enough mandatory arguments" do
|
1172
|
+
it "raises an ArgumentError" do
|
1173
|
+
expect { subject.this_is_not_madness(:this, :is) }.to raise_error(ArgumentError, "wrong number of arguments (2 for 3+)")
|
1174
|
+
end
|
1175
|
+
end
|
1176
|
+
end
|
1177
|
+
end
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
context :task_class do
|
1181
|
+
class ExampleTask < Celluloid.task_class; end
|
1182
|
+
|
1183
|
+
subject do
|
1184
|
+
Class.new do
|
1185
|
+
include CelluloidSpecs.included_module
|
1186
|
+
task_class ExampleTask
|
1187
|
+
end
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
it "overrides the task class" do
|
1191
|
+
expect(subject.new.tasks.first).to be_a ExampleTask
|
1192
|
+
end
|
1193
|
+
|
1194
|
+
it "retains custom task classes when subclassed" do
|
1195
|
+
subclass = Class.new(subject)
|
1196
|
+
expect(subclass.new.tasks.first).to be_a ExampleTask
|
1197
|
+
end
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
context :timeouts do
|
1201
|
+
let :actor_class do
|
1202
|
+
Class.new do
|
1203
|
+
include CelluloidSpecs.included_module
|
1204
|
+
|
1205
|
+
def name
|
1206
|
+
sleep 0.5
|
1207
|
+
:foo
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
def ask_name_with_timeout(other, duration)
|
1211
|
+
timeout(duration) { other.name }
|
1212
|
+
end
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
let(:a1) { actor_class.new }
|
1217
|
+
let(:a2) { actor_class.new }
|
1218
|
+
|
1219
|
+
it "allows timing out tasks, raising Celluloid::Task::TimeoutError" do
|
1220
|
+
allow(logger).to receive(:crash).with("Actor crashed!", Celluloid::Task::TimeoutError)
|
1221
|
+
expect { a1.ask_name_with_timeout a2, 0.3 }.to raise_error(Celluloid::Task::TimeoutError)
|
1222
|
+
Specs.sleep_and_wait_until { !a1.alive? }
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
it "does not raise when it completes in time" do
|
1226
|
+
expect(a1.ask_name_with_timeout(a2, 0.6)).to eq(:foo)
|
1227
|
+
end
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
context "raw message sends" do
|
1231
|
+
it "logs on unhandled messages" do
|
1232
|
+
expect(logger).to receive(:debug).with("Discarded message (unhandled): first")
|
1233
|
+
actor.mailbox << :first
|
1234
|
+
sleep CelluloidSpecs::TIMER_QUANTUM
|
1235
|
+
end
|
1236
|
+
end
|
1237
|
+
end
|