forked from eventmachine/eventmachine
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtest_spawn.rb
322 lines (279 loc) · 7.35 KB
/
test_spawn.rb
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
317
318
319
320
321
322
# $Id$
#
# Author:: Francis Cianfrocca (gmail: blackhedd)
# Homepage:: http://rubyeventmachine.com
# Date:: 25 Aug 2007
#
# See EventMachine and EventMachine::Connection for documentation and
# usage examples.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
# Gmail: blackhedd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either: 1) the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version; or 2) Ruby's License.
#
# See the file COPYING for complete licensing information.
#
#---------------------------------------------------------------------------
#
#
#
#
$:.unshift "../lib"
require 'eventmachine'
require 'test/unit'
class TestSpawn < Test::Unit::TestCase
# Spawn a process that simply stops the reactor.
# Assert that the notification runs after the block that calls it.
#
def test_stop
x = nil
EM.run {
s = EM.spawn {EM.stop}
s.notify
x = true
}
assert x
end
# Pass a parameter to a spawned process.
#
def test_parms
val = 5
EM.run {
s = EM.spawn {|v| val *= v; EM.stop}
s.notify 3
}
assert_equal( 15, val )
end
# Pass multiple parameters to a spawned process.
#
def test_multiparms
val = 5
EM.run {
s = EM.spawn {|v1,v2| val *= (v1 + v2); EM.stop}
s.notify 3,4
}
assert_equal( 35, val )
end
# This test demonstrates that a notification does not happen immediately,
# but rather is scheduled sometime after the current code path completes.
#
def test_race
x = 0
EM.run {
s = EM.spawn {x *= 2; EM.stop}
s.notify
x = 2
}
assert_equal( 4, x)
end
# Spawn a process and notify it 25 times to run fibonacci
# on a pair of global variables.
#
def test_fibonacci
x = 1
y = 1
EM.run {
s = EM.spawn {x,y = y,x+y}
25.times {s.notify}
t = EM.spawn {EM.stop}
t.notify
}
assert_equal( 121393, x)
assert_equal( 196418, y)
end
# This one spawns 25 distinct processes, and notifies each one once,
# rather than notifying a single process 25 times.
#
def test_another_fibonacci
x = 1
y = 1
EM.run {
25.times {
s = EM.spawn {x,y = y,x+y}
s.notify
}
t = EM.spawn {EM.stop}
t.notify
}
assert_equal( 121393, x)
assert_equal( 196418, y)
end
# Make a chain of processes that notify each other in turn
# with intermediate fibonacci results. The final process in
# the chain stops the loop and returns the result.
#
def test_fibonacci_chain
a,b = nil
EM.run {
nextpid = EM.spawn {|x,y|
a,b = x,y
EM.stop
}
25.times {
n = nextpid
nextpid = EM.spawn {|x,y| n.notify( y, x+y )}
}
nextpid.notify( 1, 1 )
}
assert_equal( 121393, a)
assert_equal( 196418, b)
end
# EM#yield gives a spawed process to yield control to other processes
# (in other words, to stop running), and to specify a different code block
# that will run on its next notification.
#
def test_yield
a = 0
EM.run {
n = EM.spawn {
a += 10
EM.yield {
a += 20
EM.yield {
a += 30
EM.stop
}
}
}
n.notify
n.notify
n.notify
}
assert_equal( 60, a )
end
# EM#yield_and_notify behaves like EM#yield, except that it also notifies the
# yielding process. This may sound trivial, since the yield block will run very
# shortly after with no action by the program, but this actually can be very useful,
# because it causes the reactor core to execute once before the yielding process
# gets control back. So it can be used to allow heavily-used network connections
# to clear buffers, or allow other processes to process their notifications.
#
# Notice in this test code that only a simple notify is needed at the bottom
# of the initial block. Even so, all of the yielded blocks will execute.
#
def test_yield_and_notify
a = 0
EM.run {
n = EM.spawn {
a += 10
EM.yield_and_notify {
a += 20
EM.yield_and_notify {
a += 30
EM.stop
}
}
}
n.notify
}
assert_equal( 60, a )
end
# resume is an alias for notify.
#
def test_resume
EM.run {
n = EM.spawn {EM.stop}
n.resume
}
assert true
end
# run is an idiomatic alias for notify.
#
def test_run
EM.run {
(EM.spawn {EM.stop}).run
}
assert true
end
# Clones the ping-pong example from the Erlang tutorial, in much less code.
# Illustrates that a spawned block executes in the context of a SpawnableObject.
# (Meaning, we can pass self as a parameter to another process that can then
# notify us.)
#
def test_ping_pong
n_pongs = 0
EM.run {
pong = EM.spawn {|x, ping|
n_pongs += 1
ping.notify( x-1 )
}
ping = EM.spawn {|x|
if x > 0
pong.notify x, self
else
EM.stop
end
}
ping.notify 3
}
assert_equal( 3, n_pongs )
end
# Illustrates that you can call notify inside a notification, and it will cause
# the currently-executing process to be re-notified. Of course, the new notification
# won't run until sometime after the current one completes.
#
def test_self_notify
n = 0
EM.run {
pid = EM.spawn {|x|
if x > 0
n += x
notify( x-1 )
else
EM.stop
end
}
pid.notify 3
}
assert_equal( 6, n )
end
# Illustrates that the block passed to #spawn executes in the context of a
# SpawnedProcess object, NOT in the local context. This can often be deceptive.
#
class BlockScopeTest
attr_reader :var
def run
# The following line correctly raises a NameError.
# The problem is that the programmer expected the spawned block to
# execute in the local context, but it doesn't.
#
# (EM.spawn { do_something }).notify ### NO! BAD!
# The following line correctly passes self as a parameter to the
# notified process.
#
(EM.spawn {|obj| obj.do_something }).notify(self)
# Here's another way to do it. This works because "myself" is bound
# in the local scope, unlike "self," so the spawned block sees it.
#
myself = self
(EM.spawn { myself.do_something }).notify
# And we end the loop.
# This is a tangential point, but observe that #notify never blocks.
# It merely appends a message to the internal queue of a spawned process
# and returns. As it turns out, the reactor processes notifications for ALL
# spawned processes in the order that #notify is called. So there is a
# reasonable expectation that the process which stops the reactor will
# execute after the previous ones in this method. HOWEVER, this is NOT
# a documented behavior and is subject to change.
#
(EM.spawn {EM.stop}).notify
end
def do_something
@var ||= 0
@var += 100
end
end
def test_block_scope
bs = BlockScopeTest.new
EM.run {
bs.run
}
assert_equal( 200, bs.var )
end
end