slave 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|