slave 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/doc/created.rid CHANGED
@@ -1 +1 @@
1
- Fri Nov 12 12:27:08 MST 2004
1
+ Thu Jun 08 15:55:38 MDT 2006
data/doc/dot/f_1.dot CHANGED
@@ -3,12 +3,27 @@ digraph TopLevel {
3
3
  bgcolor = lightcyan1
4
4
  fontname = Arial
5
5
  fontsize = 8
6
- label = "VERSION"
6
+ label = "lib/slave.rb"
7
7
  node [
8
8
  fontname = Arial,
9
9
  fontsize = 8,
10
10
  color = black
11
11
  ]
12
12
 
13
+ subgraph cluster_1 {
14
+ fontname = Arial
15
+ color = red
16
+ label = "lib/slave.rb"
17
+ Slave [
18
+ fontcolor = black,
19
+ URL = "classes/Slave.html",
20
+ shape = ellipse,
21
+ color = palegoldenrod,
22
+ style = filled,
23
+ label = "Slave"
24
+ ]
25
+
26
+ }
27
+
13
28
  }
14
29
 
data/doc/dot/f_1.jpg CHANGED
Binary file
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Fri Nov 12 12:27:06 MST 2004</td>
59
+ <td>Thu Jun 08 14:54:53 MDT 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Fri Nov 12 12:24:23 MST 2004</td>
59
+ <td>Thu Jun 08 15:54:54 MDT 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -70,7 +70,7 @@
70
70
  <map name="map">
71
71
  <area shape="RECT" coords="28,88,99,40" href="../../classes/Slave.html" alt="Slave">
72
72
  </map>
73
- <img src="../../dot/f_2.jpg" usemap="#map" border=0 alt="TopLevel">
73
+ <img src="../../dot/f_1.jpg" usemap="#map" border=0 alt="TopLevel">
74
74
  </div>
75
75
 
76
76
 
@@ -82,6 +82,7 @@
82
82
  fileutils&nbsp;&nbsp;
83
83
  tmpdir&nbsp;&nbsp;
84
84
  tempfile&nbsp;&nbsp;
85
+ fcntl&nbsp;&nbsp;
85
86
  </div>
86
87
  </div>
87
88
 
@@ -21,7 +21,6 @@
21
21
  <h1 class="section-bar">Files</h1>
22
22
  <div id="index-entries">
23
23
  <a href="files/README.html">README</a><br />
24
- <a href="files/VERSION.html">VERSION</a><br />
25
24
  <a href="files/lib/slave_rb.html">lib/slave.rb</a><br />
26
25
  </div>
27
26
  </div>
@@ -20,19 +20,25 @@
20
20
  <div id="index">
21
21
  <h1 class="section-bar">Methods</h1>
22
22
  <div id="index-entries">
23
- <a href="classes/Slave/Heartbeat.html#M000011">child_start (Slave::Heartbeat)</a><br />
23
+ <a href="classes/Slave/Heartbeat.html#M000017">child_start (Slave::Heartbeat)</a><br />
24
+ <a href="classes/Slave/Heartbeat.html#M000014">child_start (Slave::Heartbeat)</a><br />
25
+ <a href="classes/Slave.html#M000004">detach (Slave)</a><br />
24
26
  <a href="classes/Slave.html#M000002">fork (Slave)</a><br />
25
- <a href="classes/Slave.html#M000005">gen_psname (Slave)</a><br />
26
- <a href="classes/Slave.html#M000006">getval (Slave)</a><br />
27
+ <a href="classes/Slave.html#M000008">gen_psname (Slave)</a><br />
27
28
  <a href="classes/Slave.html#M000001">getval (Slave)</a><br />
28
- <a href="classes/Slave/Heartbeat.html#M000008">new (Slave::Heartbeat)</a><br />
29
+ <a href="classes/Slave.html#M000009">getval (Slave)</a><br />
30
+ <a href="classes/Slave/Heartbeat.html#M000011">new (Slave::Heartbeat)</a><br />
29
31
  <a href="classes/Slave.html#M000003">new (Slave)</a><br />
30
- <a href="classes/Slave/Heartbeat.html#M000010">parent_start (Slave::Heartbeat)</a><br />
31
- <a href="classes/Slave.html#M000004">shutdown (Slave)</a><br />
32
- <a href="classes/Slave/Heartbeat.html#M000009">start (Slave::Heartbeat)</a><br />
33
- <a href="classes/Slave/Heartbeat.html#M000012">stop (Slave::Heartbeat)</a><br />
34
- <a href="classes/Slave.html#M000007">trace (Slave)</a><br />
35
- <a href="classes/Slave/Heartbeat.html#M000013">trace (Slave::Heartbeat)</a><br />
32
+ <a href="classes/Slave/Heartbeat.html#M000016">parent_start (Slave::Heartbeat)</a><br />
33
+ <a href="classes/Slave/Heartbeat.html#M000013">parent_start (Slave::Heartbeat)</a><br />
34
+ <a href="classes/Slave.html#M000007">shutdown (Slave)</a><br />
35
+ <a href="classes/Slave/Heartbeat.html#M000012">start (Slave::Heartbeat)</a><br />
36
+ <a href="classes/Slave/Heartbeat.html#M000015">start (Slave::Heartbeat)</a><br />
37
+ <a href="classes/Slave/Heartbeat.html#M000018">stop (Slave::Heartbeat)</a><br />
38
+ <a href="classes/Slave.html#M000010">trace (Slave)</a><br />
39
+ <a href="classes/Slave/Heartbeat.html#M000019">trace (Slave::Heartbeat)</a><br />
40
+ <a href="classes/Slave.html#M000005">wait (Slave)</a><br />
41
+ <a href="classes/Slave.html#M000006">wait2 (Slave)</a><br />
36
42
  </div>
37
43
  </div>
38
44
  </body>
@@ -2,13 +2,43 @@ require 'drb/drb'
2
2
  require 'fileutils'
3
3
  require 'tmpdir'
4
4
  require 'tempfile'
5
+ require 'fcntl'
6
+
7
+ class Pipe
8
+ attr 'r'
9
+ attr 'w'
10
+ def initialize
11
+ @r, @w = IO.pipe
12
+ end
13
+ end
14
+
15
+ class Lifeline
16
+ class Error < ::StandardError
17
+ def initialize
18
+ end
19
+ end
20
+ def initialize
21
+ @pid = Process.pid
22
+ @pipe = Pipe.new
23
+ end
24
+ def throw
25
+ end
26
+ def cling
27
+ @w.close
28
+ Thread.new(@r, Thread.current) do |r, t|
29
+ r.read rescue t.raise($!)
30
+ end
31
+ end
32
+ end
5
33
 
6
34
  #
7
35
  # the Slave class encapsulates the work of setting up a drb server in another
8
- # process.
36
+ # process.
9
37
  #
10
38
  class Slave
11
39
  #--{{{
40
+ VERSION = '0.0.1'
41
+
12
42
  DEFAULT_SOCKET_CREATION_ATTEMPTS =
13
43
  Integer(ENV['SLAVE_SOCKET_CREATION_ATTEMPTS'] || 42)
14
44
 
@@ -68,9 +98,11 @@ require 'tempfile'
68
98
  attr :socket
69
99
  attr :debug
70
100
 
101
+ #
71
102
  # 'obj' can be any object and 'opts' may contain the keys
72
103
  # 'socket_creation_attempts', 'pulse_rate', 'psname', or 'debug'
73
- def initialize obj, opts = {}
104
+ #
105
+ def initialize obj = nil, opts = {}, &block
74
106
  #--{{{
75
107
  @obj = obj
76
108
 
@@ -85,7 +117,7 @@ require 'tempfile'
85
117
 
86
118
  @shutdown = false
87
119
 
88
- @heartbeat = Heartbeat::new @pulse_rate, @debug
120
+ # @heartbeat = Heartbeat::new @pulse_rate, @debug
89
121
  @r, @w = IO::pipe
90
122
  #
91
123
  # child
@@ -119,15 +151,16 @@ require 'tempfile'
119
151
  end
120
152
 
121
153
  if @socket and @uri
122
- @heartbeat.start
154
+ # @heartbeat.start
123
155
  @w.write @socket
124
156
  @w.close
125
157
  trap('SIGUSR2') do
126
- @heartbeat.stop rescue nil
158
+ # @heartbeat.stop rescue nil
127
159
  DBb::thread.kill rescue nil
128
160
  FileUtils::rm_f @socket rescue nil
129
161
  exit!
130
162
  end
163
+ block[obj] if block
131
164
  DRb::thread.join
132
165
  else
133
166
  @w.close
@@ -152,7 +185,7 @@ require 'tempfile'
152
185
  at_exit{ FileUtils::rm_f @socket }
153
186
  @uri = "drbunix://#{ socket }"
154
187
  trace{ "parent - uri <#{ @uri }>" }
155
- @heartbeat.start
188
+ # @heartbeat.start
156
189
  #
157
190
  # starting drb on localhost avoids dns lookups!
158
191
  #
@@ -164,30 +197,38 @@ require 'tempfile'
164
197
  end
165
198
  #--}}}
166
199
  end
200
+ #
167
201
  # stops the heartbeat thread and kills the child process
202
+ #
168
203
  def shutdown
169
204
  #--{{{
170
205
  raise "already shutdown" if @shutdown
171
- @heartbeat.stop rescue nil
206
+ # @heartbeat.stop rescue nil
172
207
  Process::kill('SIGUSR2', @pid) rescue nil
173
208
  Process::kill('SIGTERM', @pid) rescue nil
174
209
  FileUtils::rm_f @socket
175
210
  @shutdown = true
176
211
  #--}}}
177
212
  end
213
+ #
178
214
  # generate a default name to appear in ps/top
215
+ #
179
216
  def gen_psname obj
180
217
  #--{{{
181
218
  "#{ obj.class }_slave_of_#{ Process::pid }".downcase
182
219
  #--}}}
183
220
  end
184
- # see docs for class.getval
221
+ #
222
+ # see docs for Slave.getval
223
+ #
185
224
  def getval key, opts = {}
186
225
  #--{{{
187
226
  self.class.getval key
188
227
  #--}}}
189
228
  end
229
+ #
190
230
  # debugging output
231
+ #
191
232
  def trace
192
233
  #--{{{
193
234
  STDERR.puts(yield) if @debug and STDERR.tty?
@@ -223,6 +264,7 @@ require 'tempfile'
223
264
  if Process::pid == @pid
224
265
  @w.close
225
266
  @pipe = @r
267
+ @pipe.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
226
268
  parent_start
227
269
  else
228
270
  @r.close
@@ -273,6 +315,58 @@ require 'tempfile'
273
315
  end
274
316
  #--}}}
275
317
  end
318
+
319
+ def start
320
+ #--{{{
321
+ if Process::pid == @pid
322
+ @r.close
323
+ @pipe = @w
324
+ @pipe.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
325
+ parent_start
326
+ else
327
+ @w.close
328
+ @pipe = @r
329
+ child_start
330
+ end
331
+ @beating = true
332
+ #--}}}
333
+ end
334
+ def parent_start
335
+ #--{{{
336
+ @whoami = 'parent'
337
+ @thread =
338
+ Thread::new(Thread::current) do |cur|
339
+ begin
340
+ sleep
341
+ rescue => e
342
+ cur.raise e
343
+ ensure
344
+ @pipe.close rescue nil
345
+ end
346
+ end
347
+ #--}}}
348
+ end
349
+ def child_start
350
+ #--{{{
351
+ @whoami = 'child'
352
+ @pid = Process::pid
353
+ @ppid = Process::ppid
354
+ @thread =
355
+ Thread::new(Thread::current) do |cur|
356
+ begin
357
+ trace{ "child reading..." }
358
+ @pipe.read
359
+ trace{ "child read." }
360
+ trace{ "child exiting." }
361
+ exit!
362
+ rescue => e
363
+ cur.raise e
364
+ ensure
365
+ @pipe.close rescue nil
366
+ end
367
+ end
368
+ #--}}}
369
+ end
276
370
  def stop
277
371
  #--{{{
278
372
  raise "not beating" unless @beating
data/lib/slave.rb CHANGED
@@ -2,13 +2,35 @@ require 'drb/drb'
2
2
  require 'fileutils'
3
3
  require 'tmpdir'
4
4
  require 'tempfile'
5
+ require 'fcntl'
5
6
 
6
7
  #
7
8
  # the Slave class encapsulates the work of setting up a drb server in another
8
- # process.
9
+ # process running on localhost. the slave process is attached to it's parent
10
+ # via a Heartbeat which is designed such that the slave cannot out-live it's
11
+ # parent and become a zombie, even if the parent dies and early death, such as
12
+ # by 'kill -9'. the concept and purpose of the Slave class is to be able to
13
+ # setup any server object in another process so easily that using a
14
+ # multi-process, drb/ipc, based design is as easy, or easier, than a
15
+ # multi-threaded one. eg
16
+ #
17
+ # class Server
18
+ # def add_two n
19
+ # n + 2
20
+ # end
21
+ # end
22
+ #
23
+ # slave = Slave.new Server.new
24
+ # server = slave.object
25
+ #
26
+ # p server.add_two(40) #=> 42
9
27
  #
10
28
  class Slave
11
29
  #--{{{
30
+ VERSION = '0.0.1'
31
+ #
32
+ # config
33
+ #
12
34
  DEFAULT_SOCKET_CREATION_ATTEMPTS =
13
35
  Integer(ENV['SLAVE_SOCKET_CREATION_ATTEMPTS'] || 42)
14
36
 
@@ -21,7 +43,9 @@ require 'tempfile'
21
43
  @socket_creation_attempts = DEFAULT_SOCKET_CREATION_ATTEMPTS
22
44
  @pulse_rate = DEFAULT_PULSE_RATE
23
45
  @debug = DEFAULT_DEBUG
24
-
46
+ #
47
+ # class methods
48
+ #
25
49
  class << self
26
50
  #--{{{
27
51
  # defineds how many attempts will be made to create a temporary unix domain
@@ -67,10 +91,13 @@ require 'tempfile'
67
91
  attr :pulse_rate
68
92
  attr :socket
69
93
  attr :debug
94
+ attr :status
70
95
 
71
- # 'obj' can be any object and 'opts' may contain the keys
72
- # 'socket_creation_attempts', 'pulse_rate', 'psname', or 'debug'
73
- def initialize obj, opts = {}
96
+ #
97
+ # 'obj' can be any object and 'opts' may contain the keys
98
+ # 'socket_creation_attempts', 'pulse_rate', 'psname', or 'debug'
99
+ #
100
+ def initialize obj = nil, opts = {}, &block
74
101
  #--{{{
75
102
  @obj = obj
76
103
 
@@ -84,6 +111,7 @@ require 'tempfile'
84
111
  trace{ "psname <#{ @psname }>" }
85
112
 
86
113
  @shutdown = false
114
+ @waiter = @status = nil
87
115
 
88
116
  @heartbeat = Heartbeat::new @pulse_rate, @debug
89
117
  @r, @w = IO::pipe
@@ -91,6 +119,7 @@ require 'tempfile'
91
119
  # child
92
120
  #
93
121
  unless((@pid = Slave::fork))
122
+ e = nil
94
123
  begin
95
124
  $0 = @psname
96
125
  @pid = Process::pid
@@ -123,25 +152,28 @@ require 'tempfile'
123
152
  @w.write @socket
124
153
  @w.close
125
154
  trap('SIGUSR2') do
126
- @heartbeat.stop rescue nil
155
+ # @heartbeat.stop rescue nil
127
156
  DBb::thread.kill rescue nil
128
157
  FileUtils::rm_f @socket rescue nil
129
158
  exit!
130
159
  end
160
+ block[obj] if block
131
161
  DRb::thread.join
132
162
  else
133
163
  @w.close
134
164
  end
135
- rescue => e
165
+ rescue Exception => e
136
166
  trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
137
167
  ensure
138
- exit!
168
+ status = e.respond_to?('status') ? e.status : 1
169
+ exit!(status)
139
170
  end
140
171
  #
141
172
  # parent
142
173
  #
143
174
  else
144
- Process::detach @pid
175
+ #Process::detach @pid
176
+ detach
145
177
  @w.close
146
178
  @socket = @r.read
147
179
  @r.close
@@ -164,7 +196,27 @@ require 'tempfile'
164
196
  end
165
197
  #--}}}
166
198
  end
167
- # stops the heartbeat thread and kills the child process
199
+ #
200
+ # starts a thread to attempt collecting the child status
201
+ #
202
+ def detach
203
+ #--{{{
204
+ @waiter =
205
+ Thread.new{ @status = Process::waitpid2(@pid).last }
206
+ #--}}}
207
+ end
208
+ #
209
+ # wait for slave to finish
210
+ #
211
+ def wait
212
+ #--{{{
213
+ @waiter.value
214
+ #--}}}
215
+ end
216
+ alias :wait2 :wait
217
+ #
218
+ # stops the heartbeat thread and kills the child process
219
+ #
168
220
  def shutdown
169
221
  #--{{{
170
222
  raise "already shutdown" if @shutdown
@@ -175,19 +227,25 @@ require 'tempfile'
175
227
  @shutdown = true
176
228
  #--}}}
177
229
  end
178
- # generate a default name to appear in ps/top
230
+ #
231
+ # generate a default name to appear in ps/top
232
+ #
179
233
  def gen_psname obj
180
234
  #--{{{
181
- "#{ obj.class }_slave_of_#{ Process::pid }".downcase
235
+ "#{ obj.class }_slave_of_#{ Process::pid }".downcase.gsub(%r/\s*/,'_')
182
236
  #--}}}
183
237
  end
184
- # see docs for class.getval
238
+ #
239
+ # see docs for Slave.getval
240
+ #
185
241
  def getval key, opts = {}
186
242
  #--{{{
187
243
  self.class.getval key
188
244
  #--}}}
189
245
  end
190
- # debugging output
246
+ #
247
+ # debugging output - ENV['SLAVE_DEBUG']=1 to enable
248
+ #
191
249
  def trace
192
250
  #--{{{
193
251
  STDERR.puts(yield) if @debug and STDERR.tty?
@@ -195,11 +253,11 @@ require 'tempfile'
195
253
  end
196
254
 
197
255
  #
198
- # the Heartbeat class is essentially wrapper over an IPC channel that sends a
199
- # ping on the channel indicating process health. if either end of the channel
200
- # is detached the ping will fail and an error will be raised. in this was it
201
- # is ensured that Slave object cannot continue to live without their parent
202
- # being alive.
256
+ # the Heartbeat class is essentially wrapper over an IPC channel that sends
257
+ # a ping on the channel indicating process health. if either end of the
258
+ # channel is detached the ping will fail and an error will be raised. in
259
+ # this way it is ensured that Slave objects cannot continue to live without
260
+ # their parent being alive.
203
261
  #
204
262
  class Heartbeat
205
263
  #--{{{
@@ -223,6 +281,7 @@ require 'tempfile'
223
281
  if Process::pid == @pid
224
282
  @w.close
225
283
  @pipe = @r
284
+ @pipe.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
226
285
  parent_start
227
286
  else
228
287
  @r.close
@@ -273,6 +332,58 @@ require 'tempfile'
273
332
  end
274
333
  #--}}}
275
334
  end
335
+
336
+ def start
337
+ #--{{{
338
+ if Process::pid == @pid
339
+ @r.close
340
+ @pipe = @w
341
+ @pipe.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
342
+ parent_start
343
+ else
344
+ @w.close
345
+ @pipe = @r
346
+ child_start
347
+ end
348
+ @beating = true
349
+ #--}}}
350
+ end
351
+ def parent_start
352
+ #--{{{
353
+ @whoami = 'parent'
354
+ @thread =
355
+ Thread::new(Thread::current) do |cur|
356
+ begin
357
+ sleep
358
+ rescue => e
359
+ cur.raise e
360
+ ensure
361
+ @pipe.close rescue nil
362
+ end
363
+ end
364
+ #--}}}
365
+ end
366
+ def child_start
367
+ #--{{{
368
+ @whoami = 'child'
369
+ @pid = Process::pid
370
+ @ppid = Process::ppid
371
+ @thread =
372
+ Thread::new(Thread::current) do |cur|
373
+ begin
374
+ trace{ "child reading..." }
375
+ @pipe.read
376
+ trace{ "child read." }
377
+ trace{ "child exiting." }
378
+ exit!
379
+ rescue => e
380
+ cur.raise e
381
+ ensure
382
+ @pipe.close rescue nil
383
+ end
384
+ end
385
+ #--}}}
386
+ end
276
387
  def stop
277
388
  #--{{{
278
389
  raise "not beating" unless @beating
data/rdoc.cmd CHANGED
@@ -1 +1 @@
1
- rdoc -a -d -F -S -m README -I jpg -N README VERSION lib/slave.rb
1
+ rdoc -a -d -F -S -m README -I jpg -N README lib/slave.rb
data/sample/a.rb CHANGED
@@ -1,102 +1,19 @@
1
- $:.unshift 'lib'
2
- $:.unshift '../lib'
3
-
4
1
  require 'slave'
5
2
 
6
- class Incrementer
7
- attr :decrementer, true
8
- def increment n
9
- n + 1
10
- end
11
- def decrement n
12
- @decrementer.decrement n
13
- end
14
- end
15
-
16
- class Decrementer
17
- attr :incrementer, true
18
- def increment n
19
- @incrementer.increment n
20
- end
21
- def decrement n
22
- n - 1
23
- end
24
- end
25
-
26
-
27
- #
28
- #
29
- # here we set up a triangle of communicating processes
30
3
  #
31
- # incrementer------decrementer
32
- # \ /
33
- # \ /
34
- # \ /
35
- # \ /
36
- # \ /
37
- # parent
4
+ # simple usage is simply to stand up a server object as a slave. you do not
5
+ # need to wait for the server, join it, etc. it will die when the parent
6
+ # process dies - even under 'kill -9' conditions
38
7
  #
39
- #
40
-
41
- incrementer_slave = Slave::new Incrementer::new
42
- incrementer = incrementer_slave.object
43
8
 
44
- puts '---'
45
- puts 'incrementer :'
46
- puts " sockect : #{ incrementer_slave.socket.inspect }"
47
- puts " uri : #{ incrementer_slave.uri.inspect }"
48
- puts " pid : #{ incrementer_slave.pid.inspect }"
49
- puts
50
-
51
- decrementer_slave = Slave::new Decrementer::new
52
- decrementer = decrementer_slave.object
53
-
54
- puts '---'
55
- puts 'decrementer :'
56
- puts " sockect : #{ decrementer_slave.socket.inspect }"
57
- puts " uri : #{ decrementer_slave.uri.inspect }"
58
- puts " pid : #{ decrementer_slave.pid.inspect }"
59
- puts
60
-
61
- #
62
- # connect incrementer and decrementer
63
- #
64
-
65
- incrementer.decrementer = decrementer
66
- decrementer.incrementer = incrementer
67
-
68
- #
69
- # now we can call methods on each drb object, and they can also call methods on
70
- # each other
71
- #
72
-
73
- n = 0
74
-
75
- n = incrementer.increment n
76
- p n #=> 1
77
-
78
- n = decrementer.decrement n
79
- p n #=> 0
80
-
81
- n = decrementer.increment n
82
- p n #=> 1
83
-
84
- n = incrementer.decrement n
85
- p n #=> 0
86
-
87
- #
88
- # we can explicitly shutdown certain slaves
89
- #
9
+ class Server
10
+ def add_two n
11
+ n + 2
12
+ end
13
+ end
90
14
 
91
- incrementer_slave.shutdown
92
- n = decrementer.decrement 43
93
- p n #=> 42
15
+ slave = Slave.new Server.new
16
+ server = slave.object
94
17
 
95
- #
96
- # Slaves cannot live beyond their parent so we simply exit and all living slaves
97
- # will eventually die. how long it takes to die is determined by the
98
- # pulse_rate - the next time the Heartbeat trys to ping the parent the Slave
99
- # will die.
100
- #
18
+ p server.add_two(40) #=> 42
101
19
 
102
- exit
data/sample/b.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'slave'
2
+
3
+ #
4
+ # you can wait for a slave to finish if required
5
+ #
6
+ slave = Slave.new{ p $$; sleep 2; exit 42 }
7
+
8
+ p slave.wait