slave 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +164 -26
- data/README.tmpl +83 -0
- data/doc/classes/(@object = Object.new).html +117 -0
- data/doc/classes/(o = Object.new).html +117 -0
- data/doc/classes/@object.html +117 -0
- data/doc/classes/Slave.html +458 -225
- data/doc/classes/Slave/Heartbeat.html +200 -199
- data/doc/classes/o.html +117 -0
- data/doc/classes/object.html +117 -0
- data/doc/created.rid +1 -1
- data/doc/dot/f_0.jpg +0 -0
- data/doc/dot/f_1.dot +9 -0
- data/doc/dot/f_1.jpg +0 -0
- data/doc/files/README.html +200 -45
- data/doc/files/lib/slave_rb.html +4 -2
- data/doc/fr_class_index.html +1 -0
- data/doc/fr_method_index.html +23 -19
- data/gen_readme.rb +32 -0
- data/lib/{slave-0.2.0.rb → slave-1.0.0.rb} +185 -60
- data/lib/slave.rb +185 -60
- data/{sample → samples}/a.rb +7 -10
- data/samples/b.rb +22 -0
- data/samples/c.rb +21 -0
- data/samples/d.rb +9 -0
- data/samples/e.rb +11 -0
- data/slave-1.0.0.gem +0 -0
- data/test.old/slave.rb +21 -0
- metadata +19 -7
- data/sample/b.rb +0 -8
- data/sample/c.rb +0 -109
data/doc/files/lib/slave_rb.html
CHANGED
@@ -56,7 +56,7 @@
|
|
56
56
|
</tr>
|
57
57
|
<tr class="top-aligned-row">
|
58
58
|
<td><strong>Last Update:</strong></td>
|
59
|
-
<td>
|
59
|
+
<td>Fri Oct 13 13:24:25 MDT 2006</td>
|
60
60
|
</tr>
|
61
61
|
</table>
|
62
62
|
</div>
|
@@ -68,7 +68,8 @@
|
|
68
68
|
<div id="contextContent">
|
69
69
|
<div id="diagram">
|
70
70
|
<map name="map">
|
71
|
-
<area shape="RECT" coords="
|
71
|
+
<area shape="RECT" coords="123,98,195,50" href="../../classes/o.html" alt="o">
|
72
|
+
<area shape="RECT" coords="27,98,99,50" href="../../classes/Slave.html" alt="Slave">
|
72
73
|
</map>
|
73
74
|
<img src="../../dot/f_1.jpg" usemap="#map" border=0 alt="TopLevel">
|
74
75
|
</div>
|
@@ -96,6 +97,7 @@
|
|
96
97
|
|
97
98
|
Class <a href="../../classes/Slave.html" class="link">Slave</a><br />
|
98
99
|
::Class <a href="../../classes/Slave/Heartbeat.html" class="link">Slave::Heartbeat</a><br />
|
100
|
+
Class <a href="../../classes/o.html" class="link">o</a><br />
|
99
101
|
|
100
102
|
</div>
|
101
103
|
|
data/doc/fr_class_index.html
CHANGED
data/doc/fr_method_index.html
CHANGED
@@ -20,25 +20,29 @@
|
|
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#
|
24
|
-
<a href="classes/Slave/Heartbeat.html#
|
25
|
-
<a href="classes/Slave.html#
|
26
|
-
<a href="classes/Slave.html#M000002">
|
27
|
-
<a href="classes/Slave.html#
|
28
|
-
<a href="classes/Slave.html#
|
29
|
-
<a href="classes/Slave.html#
|
30
|
-
<a href="classes/Slave
|
31
|
-
<a href="classes/Slave.html#M000003">
|
32
|
-
<a href="classes/Slave/Heartbeat.html#
|
33
|
-
<a href="classes/Slave
|
34
|
-
<a href="classes/Slave.html#
|
35
|
-
<a href="classes/Slave/Heartbeat.html#
|
36
|
-
<a href="classes/Slave
|
37
|
-
<a href="classes/Slave
|
38
|
-
<a href="classes/Slave.html#
|
39
|
-
<a href="classes/Slave/Heartbeat.html#M000019">
|
40
|
-
<a href="classes/Slave.html#
|
41
|
-
<a href="classes/Slave.html#
|
23
|
+
<a href="classes/Slave/Heartbeat.html#M000021">child_start (Slave::Heartbeat)</a><br />
|
24
|
+
<a href="classes/Slave/Heartbeat.html#M000018">child_start (Slave::Heartbeat)</a><br />
|
25
|
+
<a href="classes/Slave.html#M000012">default (Slave)</a><br />
|
26
|
+
<a href="classes/Slave.html#M000002">default (Slave)</a><br />
|
27
|
+
<a href="classes/Slave.html#M000006">detach (Slave)</a><br />
|
28
|
+
<a href="classes/Slave.html#M000004">fork (Slave)</a><br />
|
29
|
+
<a href="classes/Slave.html#M000011">gen_psname (Slave)</a><br />
|
30
|
+
<a href="classes/Slave.html#M000013">getopts (Slave)</a><br />
|
31
|
+
<a href="classes/Slave.html#M000003">getopts (Slave)</a><br />
|
32
|
+
<a href="classes/Slave/Heartbeat.html#M000015">new (Slave::Heartbeat)</a><br />
|
33
|
+
<a href="classes/Slave.html#M000005">new (Slave)</a><br />
|
34
|
+
<a href="classes/Slave/Heartbeat.html#M000020">parent_start (Slave::Heartbeat)</a><br />
|
35
|
+
<a href="classes/Slave/Heartbeat.html#M000017">parent_start (Slave::Heartbeat)</a><br />
|
36
|
+
<a href="classes/Slave.html#M000009">shutdown (Slave)</a><br />
|
37
|
+
<a href="classes/Slave.html#M000010">shutdown? (Slave)</a><br />
|
38
|
+
<a href="classes/Slave/Heartbeat.html#M000016">start (Slave::Heartbeat)</a><br />
|
39
|
+
<a href="classes/Slave/Heartbeat.html#M000019">start (Slave::Heartbeat)</a><br />
|
40
|
+
<a href="classes/Slave/Heartbeat.html#M000022">stop (Slave::Heartbeat)</a><br />
|
41
|
+
<a href="classes/Slave.html#M000014">trace (Slave)</a><br />
|
42
|
+
<a href="classes/Slave/Heartbeat.html#M000023">trace (Slave::Heartbeat)</a><br />
|
43
|
+
<a href="classes/Slave.html#M000001">version (Slave)</a><br />
|
44
|
+
<a href="classes/Slave.html#M000007">wait (Slave)</a><br />
|
45
|
+
<a href="classes/Slave.html#M000008">wait2 (Slave)</a><br />
|
42
46
|
</div>
|
43
47
|
</div>
|
44
48
|
</body>
|
data/gen_readme.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
$VERBOSE=nil
|
4
|
+
|
5
|
+
def indent s, n = 2
|
6
|
+
ws = ' ' * n
|
7
|
+
s.gsub %r/^/, ws
|
8
|
+
end
|
9
|
+
|
10
|
+
template = IO::read 'README.tmpl'
|
11
|
+
|
12
|
+
samples = ''
|
13
|
+
prompt = '~ > '
|
14
|
+
|
15
|
+
Dir['sample*/*'].sort.each do |sample|
|
16
|
+
samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
|
17
|
+
|
18
|
+
cmd = "cat #{ sample }"
|
19
|
+
samples << indent(prompt + cmd, 2) << "\n\n"
|
20
|
+
samples << indent(`#{ cmd }`, 4) << "\n"
|
21
|
+
|
22
|
+
cmd = "ruby #{ sample }"
|
23
|
+
samples << indent(prompt + cmd, 2) << "\n\n"
|
24
|
+
|
25
|
+
cmd = "ruby -Ilib #{ sample }"
|
26
|
+
samples << indent(`#{ cmd } 2>&1`, 4) << "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
#samples.gsub! %r/^/, ' '
|
30
|
+
|
31
|
+
readme = template.gsub %r/^\s*@samples\s*$/, samples
|
32
|
+
print readme
|
@@ -20,14 +20,24 @@ require 'fcntl'
|
|
20
20
|
# end
|
21
21
|
# end
|
22
22
|
#
|
23
|
-
# slave = Slave.new Server.new
|
23
|
+
# slave = Slave.new 'object' => Server.new
|
24
24
|
# server = slave.object
|
25
25
|
#
|
26
26
|
# p server.add_two(40) #=> 42
|
27
|
+
#
|
28
|
+
# two other methods of providing server objects exist:
|
29
|
+
#
|
30
|
+
# a) server = Server.new "this is called the parent" }
|
31
|
+
# Slave.new(:object=>server){|s| puts "#{ s.inspect } passed to block in child process"}
|
32
|
+
#
|
33
|
+
# b) Slave.new{ Server.new "this is called only in the child" }
|
34
|
+
#
|
35
|
+
# of the two 'b' is preferred.
|
27
36
|
#
|
28
37
|
class Slave
|
29
38
|
#--{{{
|
30
|
-
VERSION = '0.
|
39
|
+
VERSION = '1.0.0'
|
40
|
+
def self.version() VERSION end
|
31
41
|
#
|
32
42
|
# config
|
33
43
|
#
|
@@ -59,14 +69,27 @@ require 'fcntl'
|
|
59
69
|
# on STDERR
|
60
70
|
attr :debug, true
|
61
71
|
|
62
|
-
#
|
63
|
-
def
|
72
|
+
# get a default value
|
73
|
+
def default key
|
64
74
|
#--{{{
|
65
|
-
|
66
|
-
|
67
|
-
|
75
|
+
send key
|
76
|
+
#--}}}
|
77
|
+
end
|
78
|
+
|
79
|
+
def getopts opts
|
80
|
+
#--{{{
|
81
|
+
raise ArgumentError, opts.class unless
|
82
|
+
opts.respond_to?('has_key?') and opts.respond_to?('[]')
|
83
|
+
|
84
|
+
lambda do |key, *defval|
|
85
|
+
defval = defval.shift
|
86
|
+
keys = [key, key.to_s, key.to_s.intern]
|
87
|
+
key = keys.detect{|k| opts.has_key? k } and break opts[key]
|
88
|
+
defval
|
89
|
+
end
|
68
90
|
#--}}}
|
69
91
|
end
|
92
|
+
|
70
93
|
# just fork with out silly warnings
|
71
94
|
def fork &block
|
72
95
|
#--{{{
|
@@ -82,87 +105,126 @@ require 'fcntl'
|
|
82
105
|
#--}}}
|
83
106
|
end
|
84
107
|
|
85
|
-
|
108
|
+
|
86
109
|
attr :obj
|
110
|
+
attr :socket_creation_attempts
|
111
|
+
attr :pulse_rate
|
112
|
+
attr :debug
|
87
113
|
attr :psname
|
114
|
+
attr :at_exit
|
115
|
+
|
116
|
+
attr :shutdown
|
117
|
+
attr :status
|
118
|
+
attr :object
|
88
119
|
attr :pid
|
89
120
|
attr :ppid
|
90
121
|
attr :uri
|
91
|
-
attr :pulse_rate
|
92
122
|
attr :socket
|
93
|
-
attr :debug
|
94
|
-
attr :status
|
95
123
|
|
96
124
|
#
|
97
|
-
#
|
98
|
-
# '
|
125
|
+
# opts may contain the keys 'object', 'socket_creation_attempts',
|
126
|
+
# 'pulse_rate', 'psname', 'dumped', or 'debug'
|
99
127
|
#
|
100
|
-
def initialize
|
128
|
+
def initialize opts = {}, &block
|
101
129
|
#--{{{
|
102
|
-
|
103
|
-
obj.nil? and block.nil?
|
130
|
+
getopt = getopts opts
|
104
131
|
|
105
|
-
@obj =
|
132
|
+
@obj = getopt['object']
|
133
|
+
@socket_creation_attempts = getopt['socket_creation_attempts'] || default('socket_creation_attempts')
|
134
|
+
@pulse_rate = getopt['pulse_rate'] || default('pulse_rate')
|
135
|
+
@debug = getopt['debug'] || default('debug')
|
136
|
+
@psname = getopt['psname']
|
137
|
+
@at_exit = getopt['at_exit']
|
138
|
+
@dumped = getopt['dumped']
|
106
139
|
|
107
|
-
|
108
|
-
|
109
|
-
@debug = getval('debug', opts)
|
110
|
-
@psname = getval('psname', opts) || gen_psname(@obj)
|
111
|
-
|
112
|
-
trace{ "socket_creation_attempts <#{ @socket_creation_attempts }>" }
|
113
|
-
trace{ "pulse_rate <#{ @pulse_rate }>" }
|
114
|
-
trace{ "psname <#{ @psname }>" }
|
140
|
+
raise ArgumentError, 'no slave object!' if
|
141
|
+
@obj.nil? and block.nil?
|
115
142
|
|
116
143
|
@shutdown = false
|
117
144
|
@waiter = @status = nil
|
118
145
|
|
119
146
|
@heartbeat = Heartbeat::new @pulse_rate, @debug
|
120
147
|
@r, @w = IO::pipe
|
148
|
+
@r2, @w2 = IO::pipe
|
149
|
+
|
150
|
+
# weird syntax because dot/rdoc chokes on this!?!?
|
151
|
+
init_failure = lambda do |e|
|
152
|
+
o = Object.new
|
153
|
+
class << o
|
154
|
+
attr_accessor '__slave_object_failure__'
|
155
|
+
end
|
156
|
+
o.__slave_object_failure__ = Marshal.dump [e.class, e.message, e.backtrace]
|
157
|
+
@object = o
|
158
|
+
end
|
159
|
+
|
121
160
|
#
|
122
161
|
# child
|
123
162
|
#
|
124
163
|
unless((@pid = Slave::fork))
|
125
164
|
e = nil
|
126
165
|
begin
|
127
|
-
|
128
|
-
|
129
|
-
@
|
166
|
+
Kernel.at_exit{ Kernel.exit! }
|
167
|
+
|
168
|
+
if @obj
|
169
|
+
@object = @obj
|
170
|
+
else
|
171
|
+
begin
|
172
|
+
@object = block.call
|
173
|
+
rescue Exception => e
|
174
|
+
init_failure[e]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
if block and @obj
|
179
|
+
begin
|
180
|
+
block[@obj]
|
181
|
+
rescue Exception => e
|
182
|
+
init_failure[e]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
$0 = (@psname ||= gen_psname(@object))
|
187
|
+
unless @dumped or @object.respond_to?('__slave_object_failure__')
|
188
|
+
@object.extend DRbUndumped
|
189
|
+
end
|
190
|
+
|
191
|
+
@ppid, @pid = Process::ppid, Process::pid
|
130
192
|
|
131
193
|
@r.close
|
194
|
+
@r2.close
|
132
195
|
@socket = nil
|
133
196
|
@uri = nil
|
134
197
|
|
135
|
-
tmpdir = Dir::tmpdir
|
136
|
-
basename = File::basename @psname
|
137
|
-
|
138
|
-
server = @obj || block.call
|
198
|
+
tmpdir, basename = Dir::tmpdir, File::basename(@psname)
|
139
199
|
|
140
200
|
@socket_creation_attempts.times do |attempt|
|
201
|
+
se = nil
|
141
202
|
begin
|
142
203
|
s = File::join(tmpdir, "#{ basename }_#{ attempt }")
|
143
204
|
u = "drbunix://#{ s }"
|
144
|
-
DRb::start_service u,
|
205
|
+
DRb::start_service u, @object
|
145
206
|
@socket = s
|
146
207
|
@uri = u
|
147
208
|
trace{ "child - socket <#{ @socket }>" }
|
148
209
|
trace{ "child - uri <#{ @uri }>" }
|
149
210
|
break
|
150
|
-
rescue Errno::EADDRINUSE
|
211
|
+
rescue Errno::EADDRINUSE => se
|
151
212
|
nil
|
152
213
|
end
|
153
214
|
end
|
154
215
|
|
155
216
|
if @socket and @uri
|
156
217
|
@heartbeat.start
|
157
|
-
|
158
|
-
@w.close
|
218
|
+
|
159
219
|
trap('SIGUSR2') do
|
160
220
|
# @heartbeat.stop rescue nil
|
161
221
|
DBb::thread.kill rescue nil
|
162
222
|
FileUtils::rm_f @socket rescue nil
|
163
|
-
exit
|
223
|
+
exit
|
164
224
|
end
|
165
|
-
|
225
|
+
|
226
|
+
@w.write @socket
|
227
|
+
@w.close
|
166
228
|
DRb::thread.join
|
167
229
|
else
|
168
230
|
@w.close
|
@@ -171,22 +233,36 @@ require 'fcntl'
|
|
171
233
|
trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
|
172
234
|
ensure
|
173
235
|
status = e.respond_to?('status') ? e.status : 1
|
174
|
-
exit
|
236
|
+
exit(status)
|
175
237
|
end
|
176
238
|
#
|
177
239
|
# parent
|
178
240
|
#
|
179
241
|
else
|
180
|
-
#Process::detach @pid
|
181
242
|
detach
|
182
243
|
@w.close
|
244
|
+
@w2.close
|
183
245
|
@socket = @r.read
|
184
246
|
@r.close
|
185
247
|
|
186
248
|
trace{ "parent - socket <#{ @socket }>" }
|
187
249
|
|
250
|
+
if @at_exit
|
251
|
+
@at_exit_thread = Thread.new{
|
252
|
+
Thread.current.abort_on_exception = true
|
253
|
+
|
254
|
+
@r2.read rescue 42
|
255
|
+
|
256
|
+
if @at_exit.respond_to? 'call'
|
257
|
+
@at_exit.call self
|
258
|
+
else
|
259
|
+
send @at_exit.to_s, self
|
260
|
+
end
|
261
|
+
}
|
262
|
+
end
|
263
|
+
|
188
264
|
if @socket and File::exist? @socket
|
189
|
-
at_exit{ FileUtils::rm_f @socket }
|
265
|
+
Kernel.at_exit{ FileUtils::rm_f @socket }
|
190
266
|
@uri = "drbunix://#{ socket }"
|
191
267
|
trace{ "parent - uri <#{ @uri }>" }
|
192
268
|
@heartbeat.start
|
@@ -195,6 +271,12 @@ require 'fcntl'
|
|
195
271
|
#
|
196
272
|
DRb::start_service('druby://localhost:0', nil) unless DRb::thread
|
197
273
|
@object = DRbObject::new nil, @uri
|
274
|
+
if @object.respond_to? '__slave_object_failure__'
|
275
|
+
c, m, bt = Marshal.load @object.__slave_object_failure__
|
276
|
+
(e = c.new(m)).set_backtrace bt
|
277
|
+
raise e
|
278
|
+
end
|
279
|
+
@psname ||= gen_psname(@object)
|
198
280
|
else
|
199
281
|
raise "failed to find slave socket <#{ @socket }>"
|
200
282
|
end
|
@@ -202,34 +284,70 @@ require 'fcntl'
|
|
202
284
|
#--}}}
|
203
285
|
end
|
204
286
|
#
|
205
|
-
# starts a thread to
|
287
|
+
# starts a thread to collect the child status and sets up at_exit handler to
|
288
|
+
# prevent zombies. the at_exit handler is canceled if the thread is able to
|
289
|
+
# collect the status
|
206
290
|
#
|
207
291
|
def detach
|
208
292
|
#--{{{
|
209
|
-
|
210
|
-
|
293
|
+
reap = lambda do |cid|
|
294
|
+
begin
|
295
|
+
@status = Process::waitpid2(cid).last
|
296
|
+
rescue Exception => e
|
297
|
+
m, c, b = e.message, e.class, e.backtrace.join("\n")
|
298
|
+
warn "#{ m } (#{ c })\n#{ b }" unless e.is_a? Errno::ECHILD
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
Kernel.at_exit do
|
303
|
+
shutdown rescue nil
|
304
|
+
reap[@pid] rescue nil
|
305
|
+
end
|
306
|
+
|
307
|
+
@waiter =
|
308
|
+
Thread.new do
|
309
|
+
begin
|
310
|
+
@status = Process::waitpid2(@pid).last
|
311
|
+
ensure
|
312
|
+
reap = lambda{|cid| 'no-op' }
|
313
|
+
end
|
314
|
+
end
|
211
315
|
#--}}}
|
212
316
|
end
|
213
317
|
#
|
214
|
-
# wait for slave to finish
|
318
|
+
# wait for slave to finish. if the keyword 'non_block'=>true is given a
|
319
|
+
# thread is returned to do the waiting in an async fashion. eg
|
215
320
|
#
|
216
|
-
|
321
|
+
# thread = slave.wait(:non_block=>true){|value| "background <#{ value }>"}
|
322
|
+
#
|
323
|
+
def wait opts = {}, &b
|
217
324
|
#--{{{
|
218
|
-
|
325
|
+
b ||= lambda{|exit_status|}
|
326
|
+
non_block = getopts(opts)['non_block']
|
327
|
+
non_block ? Thread.new{ b[ @waiter.value ] } : b[ @waiter.value ]
|
219
328
|
#--}}}
|
220
329
|
end
|
221
330
|
alias :wait2 :wait
|
222
331
|
#
|
223
|
-
# stops the heartbeat thread and kills the child process
|
332
|
+
# stops the heartbeat thread and kills the child process - give the key
|
333
|
+
# 'quiet' to ignore errors shutting down, including having already shutdown
|
224
334
|
#
|
225
|
-
def shutdown
|
335
|
+
def shutdown opts = {}
|
226
336
|
#--{{{
|
227
|
-
|
228
|
-
@
|
229
|
-
|
230
|
-
|
231
|
-
|
337
|
+
quiet = getopts(opts)['quiet']
|
338
|
+
raise "already shutdown" if @shutdown unless quiet
|
339
|
+
failure = lambda{ raise $! unless quiet }
|
340
|
+
@heartbeat.stop rescue failure.call
|
341
|
+
Process::kill('SIGUSR2', @pid) rescue failure.call
|
232
342
|
@shutdown = true
|
343
|
+
#--}}}
|
344
|
+
end
|
345
|
+
#
|
346
|
+
# true
|
347
|
+
#
|
348
|
+
def shutdown?
|
349
|
+
#--{{{
|
350
|
+
@shutdown
|
233
351
|
#--}}}
|
234
352
|
end
|
235
353
|
#
|
@@ -237,15 +355,23 @@ require 'fcntl'
|
|
237
355
|
#
|
238
356
|
def gen_psname obj
|
239
357
|
#--{{{
|
240
|
-
"#{ obj.class }
|
358
|
+
"#{ obj.class }_#{ obj.object_id }_#{ Process::ppid }_#{ Process::pid }".downcase.gsub(%r/\s+/,'_')
|
359
|
+
#--}}}
|
360
|
+
end
|
361
|
+
#
|
362
|
+
# see docs for Slave.default
|
363
|
+
#
|
364
|
+
def default key
|
365
|
+
#--{{{
|
366
|
+
self.class.default key
|
241
367
|
#--}}}
|
242
368
|
end
|
243
369
|
#
|
244
|
-
# see docs for Slave.
|
370
|
+
# see docs for Slave.getopts
|
245
371
|
#
|
246
|
-
def
|
372
|
+
def getopts opts
|
247
373
|
#--{{{
|
248
|
-
self.class.
|
374
|
+
self.class.getopts opts
|
249
375
|
#--}}}
|
250
376
|
end
|
251
377
|
#
|
@@ -256,7 +382,6 @@ require 'fcntl'
|
|
256
382
|
STDERR.puts(yield) if @debug and STDERR.tty?
|
257
383
|
#--}}}
|
258
384
|
end
|
259
|
-
|
260
385
|
#
|
261
386
|
# the Heartbeat class is essentially wrapper over an IPC channel that sends
|
262
387
|
# a ping on the channel indicating process health. if either end of the
|
@@ -380,7 +505,7 @@ require 'fcntl'
|
|
380
505
|
@pipe.read
|
381
506
|
trace{ "child read." }
|
382
507
|
trace{ "child exiting." }
|
383
|
-
exit
|
508
|
+
exit
|
384
509
|
rescue => e
|
385
510
|
cur.raise e
|
386
511
|
ensure
|