slave 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +90 -17
- data/README.tmpl +29 -12
- data/doc/classes/Slave.html +430 -298
- data/doc/classes/Slave/LifeLine.html +406 -0
- data/doc/classes/Slave/ThreadSafe.html +284 -0
- data/doc/classes/Slave/ThreadSafeHash.html +151 -0
- data/doc/created.rid +1 -1
- data/doc/files/README.html +88 -18
- data/doc/files/lib/slave_rb.html +6 -2
- data/doc/fr_class_index.html +3 -1
- data/doc/fr_method_index.html +19 -11
- data/lib/slave-1.1.0.rb +623 -0
- data/lib/slave.rb +293 -203
- data/samples/a.rb +3 -1
- data/samples/f.rb +13 -0
- data/samples/g.rb +19 -0
- metadata +26 -30
- data/doc/classes/(@object = Object.new).html +0 -117
- data/doc/classes/(o = Object.new).html +0 -117
- data/doc/classes/@object.html +0 -117
- data/doc/classes/Slave/Heartbeat.html +0 -458
- data/doc/classes/object.html +0 -117
- data/doc/dot/f_2.dot +0 -29
- data/doc/dot/f_2.jpg +0 -0
- data/doc/files/VERSION.html +0 -107
- data/lib/slave-1.0.0.rb +0 -533
- data/slave-1.0.0.gem +0 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
4
|
+
"DTD/xhtml1-transitional.dtd">
|
5
|
+
|
6
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
7
|
+
<head>
|
8
|
+
<title>Class: Slave::ThreadSafeHash</title>
|
9
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
10
|
+
<meta http-equiv="Content-Script-Type" content="text/javascript" />
|
11
|
+
<link rel="stylesheet" href="../.././rdoc-style.css" type="text/css" media="screen" />
|
12
|
+
<script type="text/javascript">
|
13
|
+
// <![CDATA[
|
14
|
+
|
15
|
+
function popupCode( url ) {
|
16
|
+
window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
|
17
|
+
}
|
18
|
+
|
19
|
+
function toggleCode( id ) {
|
20
|
+
if ( document.getElementById )
|
21
|
+
elem = document.getElementById( id );
|
22
|
+
else if ( document.all )
|
23
|
+
elem = eval( "document.all." + id );
|
24
|
+
else
|
25
|
+
return false;
|
26
|
+
|
27
|
+
elemStyle = elem.style;
|
28
|
+
|
29
|
+
if ( elemStyle.display != "block" ) {
|
30
|
+
elemStyle.display = "block"
|
31
|
+
} else {
|
32
|
+
elemStyle.display = "none"
|
33
|
+
}
|
34
|
+
|
35
|
+
return true;
|
36
|
+
}
|
37
|
+
|
38
|
+
// Make codeblocks hidden by default
|
39
|
+
document.writeln( "<style type=\"text/css\">div.method-source-code { display: none }</style>" )
|
40
|
+
|
41
|
+
// ]]>
|
42
|
+
</script>
|
43
|
+
|
44
|
+
</head>
|
45
|
+
<body>
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
<div id="classHeader">
|
50
|
+
<h1>Slave::ThreadSafeHash <sup class="type-note">(Class)</sup></h1>
|
51
|
+
<table class="header-table">
|
52
|
+
<tr class="top-aligned-row">
|
53
|
+
<td><strong>In:</strong></td>
|
54
|
+
<td>
|
55
|
+
<a href="../../files/lib/slave_rb.html">
|
56
|
+
lib/slave.rb
|
57
|
+
</a>
|
58
|
+
<br />
|
59
|
+
</td>
|
60
|
+
</tr>
|
61
|
+
|
62
|
+
<tr class="top-aligned-row">
|
63
|
+
<td><strong>Parent:</strong></td>
|
64
|
+
<td>
|
65
|
+
Hash
|
66
|
+
</td>
|
67
|
+
</tr>
|
68
|
+
</table>
|
69
|
+
</div>
|
70
|
+
<!-- banner header -->
|
71
|
+
|
72
|
+
<div id="bodyContent">
|
73
|
+
|
74
|
+
|
75
|
+
<div id="contextContent">
|
76
|
+
<div id="diagram">
|
77
|
+
<map name="map">
|
78
|
+
<area shape="RECT" coords="123,98,195,50" href="../o.html" alt="o">
|
79
|
+
<area shape="RECT" coords="27,98,99,50" href="../Slave.html" alt="Slave">
|
80
|
+
</map>
|
81
|
+
<img src="../../dot/f_1.jpg" usemap="#map" border=0 alt="TopLevel">
|
82
|
+
</div>
|
83
|
+
|
84
|
+
<div id="description">
|
85
|
+
<p>
|
86
|
+
a simple thread safe hash used to map object_id to a set of file
|
87
|
+
descriptors in the <a href="LifeLine.html">LifeLine</a> class. see
|
88
|
+
LifeLine::FDS
|
89
|
+
</p>
|
90
|
+
|
91
|
+
</div>
|
92
|
+
|
93
|
+
|
94
|
+
<div id="method-list">
|
95
|
+
<h2 class="section-bar">Methods</h2>
|
96
|
+
|
97
|
+
<div class="name-list">
|
98
|
+
<a href="#M000031">new</a>
|
99
|
+
</div>
|
100
|
+
</div>
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
</div>
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
<!-- if includes -->
|
112
|
+
|
113
|
+
|
114
|
+
<!-- if method_list -->
|
115
|
+
<div id="methods">
|
116
|
+
<h2 class="section-bar">Public Class methods</h2>
|
117
|
+
|
118
|
+
<div id="method-M000031" class="method-detail">
|
119
|
+
<a name="M000031"></a>
|
120
|
+
|
121
|
+
<div class="method-heading">
|
122
|
+
<a href="#M000031" class="method-signature">
|
123
|
+
<span class="method-name">new</span><span class="method-args">(*a, &b)</span>
|
124
|
+
</a>
|
125
|
+
</div>
|
126
|
+
|
127
|
+
<div class="method-description">
|
128
|
+
<p><a class="source-toggle" href="#"
|
129
|
+
onclick="toggleCode('M000031-source');return false;">[Source]</a></p>
|
130
|
+
<div class="method-source-code" id="M000031-source">
|
131
|
+
<pre>
|
132
|
+
<span class="ruby-comment cmt"># File lib/slave.rb, line 156</span>
|
133
|
+
156: <span class="ruby-keyword kw">def</span> <span class="ruby-keyword kw">self</span>.<span class="ruby-identifier">new</span>(<span class="ruby-operator">*</span><span class="ruby-identifier">a</span>, <span class="ruby-operator">&</span><span class="ruby-identifier">b</span>) <span class="ruby-constant">ThreadSafe</span>.<span class="ruby-identifier">new</span>(<span class="ruby-keyword kw">super</span>) <span class="ruby-keyword kw">end</span>
|
134
|
+
</pre>
|
135
|
+
</div>
|
136
|
+
</div>
|
137
|
+
</div>
|
138
|
+
|
139
|
+
|
140
|
+
</div>
|
141
|
+
|
142
|
+
|
143
|
+
</div>
|
144
|
+
|
145
|
+
|
146
|
+
<div id="validator-badges">
|
147
|
+
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
|
148
|
+
</div>
|
149
|
+
|
150
|
+
</body>
|
151
|
+
</html>
|
data/doc/created.rid
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
Tue Nov 28 09:47:34 MST 2006
|
data/doc/files/README.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>Tue Nov 28 09:47:31 MST 2006</td>
|
60
60
|
</tr>
|
61
61
|
</table>
|
62
62
|
</div>
|
@@ -87,23 +87,27 @@ SYNOPSIS
|
|
87
87
|
|
88
88
|
typical usage:
|
89
89
|
|
90
|
-
|
90
|
+
slave = Slave::new{ AnyObject.new }
|
91
91
|
|
92
|
-
slave
|
92
|
+
slave.object #=> handle on drb object
|
93
|
+
slave.uri #=> uri of the drb object
|
94
|
+
slave.socket #=> unix domain socket path for drb object
|
95
|
+
slave.psname #=> title shown in ps/top
|
93
96
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
p slave.psname # title shown in ps/top
|
97
|
+
object = slave.object
|
98
|
+
|
99
|
+
value = object.any_method #=> use the object normally
|
98
100
|
|
99
101
|
slaves may be configured via the environment, the Slave class, or via the
|
100
102
|
ctor for object itself. attributes which may be configured include
|
101
103
|
|
102
|
-
*
|
103
|
-
*
|
104
|
-
*
|
105
|
-
*
|
106
|
-
*
|
104
|
+
* object : specify the slave object. otherwise block value is used.
|
105
|
+
* socket_creation_attempts : specify how many attempts to create a unix domain socket will be made
|
106
|
+
* debug : turn on some logging to STDERR
|
107
|
+
* psname : specify the name that will appear in 'top' ($0)
|
108
|
+
* at_exit : specify a lambda to be called in the *parent* when the child dies
|
109
|
+
* dumped : specify that the slave object should *not* be DRbUndumped (default is DRbUndumped)
|
110
|
+
* threadsafe : wrap the slave object with ThreadSafe to implement gross thread safety
|
107
111
|
</pre>
|
108
112
|
<p>
|
109
113
|
URIS
|
@@ -116,9 +120,21 @@ URIS
|
|
116
120
|
HISTORY
|
117
121
|
</p>
|
118
122
|
<pre>
|
119
|
-
|
123
|
+
1.1.0:
|
124
|
+
- replaced HeartBeat class with LifeLine.
|
125
|
+
|
126
|
+
- __HUGE__ cleanup of file descriptor/fork management with tons of help
|
127
|
+
from skaar and ezra. thanks guys!
|
128
|
+
|
129
|
+
- introduced Slave.object method used to return any object directory from
|
130
|
+
a child process. see samples/g.rb.
|
131
|
+
|
132
|
+
- indroduced keyword to automatically make slave objects threadsafe.
|
133
|
+
remember that your slave object must be threadsafe because they are
|
134
|
+
being server via DRb!!!
|
120
135
|
|
121
136
|
1.0.0:
|
137
|
+
- THIS RELEASE IS !! NOT !! BACKWARD COMPATIBLE. NOTE NEW CTOR SYNTAX.
|
122
138
|
|
123
139
|
- detach method also sets up at_exit handler. extra protection from
|
124
140
|
zombies.
|
@@ -179,10 +195,12 @@ SAMPLES
|
|
179
195
|
end
|
180
196
|
|
181
197
|
slave = Slave.new :object => Server.new
|
182
|
-
server = slave.object
|
183
198
|
|
199
|
+
server = slave.object
|
184
200
|
p server.add_two(40) #=> 42
|
185
201
|
|
202
|
+
slave.shutdown
|
203
|
+
|
186
204
|
~ > ruby samples/a.rb
|
187
205
|
|
188
206
|
42
|
@@ -217,7 +235,7 @@ SAMPLES
|
|
217
235
|
~ > ruby samples/b.rb
|
218
236
|
|
219
237
|
:postgresql
|
220
|
-
./lib/slave.rb:
|
238
|
+
./lib/slave.rb:460:in `initialize': undefined method `typo' for #<Server:0xb7565694> (NoMethodError)
|
221
239
|
from samples/b.rb:22:in `new'
|
222
240
|
from samples/b.rb:22
|
223
241
|
|
@@ -249,9 +267,9 @@ SAMPLES
|
|
249
267
|
|
250
268
|
~ > ruby samples/c.rb
|
251
269
|
|
252
|
-
|
253
|
-
|
254
|
-
./lib/slave.rb:
|
270
|
+
14387
|
271
|
+
14388
|
272
|
+
./lib/slave.rb:460:in `initialize': undefined local variable or method `fubar' for main:Object (NameError)
|
255
273
|
from samples/c.rb:21:in `new'
|
256
274
|
from samples/c.rb:21
|
257
275
|
|
@@ -271,6 +289,7 @@ SAMPLES
|
|
271
289
|
|
272
290
|
~ > ruby samples/d.rb
|
273
291
|
|
292
|
+
"child"
|
274
293
|
"parent"
|
275
294
|
|
276
295
|
<========< samples/e.rb >========>
|
@@ -292,6 +311,57 @@ SAMPLES
|
|
292
311
|
~ > ruby samples/e.rb
|
293
312
|
|
294
313
|
"child"
|
314
|
+
|
315
|
+
<========< samples/f.rb >========>
|
316
|
+
|
317
|
+
~ > cat samples/f.rb
|
318
|
+
|
319
|
+
require 'slave'
|
320
|
+
#
|
321
|
+
# slaves created previously are visible to newly created slaves - in this
|
322
|
+
# example the child process of slave_a communicates directly with the child
|
323
|
+
# process of slave_a
|
324
|
+
#
|
325
|
+
slave_a = Slave.new{ Array.new }
|
326
|
+
slave_b = Slave.new{ slave_a.object }
|
327
|
+
|
328
|
+
a, b = slave_b.object, slave_a.object
|
329
|
+
|
330
|
+
b << 42
|
331
|
+
puts a #=> 42
|
332
|
+
|
333
|
+
~ > ruby samples/f.rb
|
334
|
+
|
335
|
+
42
|
336
|
+
|
337
|
+
<========< samples/g.rb >========>
|
338
|
+
|
339
|
+
~ > cat samples/g.rb
|
340
|
+
|
341
|
+
require 'slave'
|
342
|
+
#
|
343
|
+
# Slave.object can used when you want to construct an object in another
|
344
|
+
# process. in otherwords you want to fork a process and retrieve a single
|
345
|
+
# returned object from that process as opposed to setting up a server.
|
346
|
+
#
|
347
|
+
this = Process.pid
|
348
|
+
that = Slave.object{ Process.pid }
|
349
|
+
|
350
|
+
p 'this' => this, 'that' => that
|
351
|
+
|
352
|
+
#
|
353
|
+
# any object can be returned and it can be returned asychronously via a thread
|
354
|
+
#
|
355
|
+
thread = Slave.object(:async => true){ sleep 2 and [ Process.pid, Time.now ] }
|
356
|
+
this = [ Process.pid, Time.now ]
|
357
|
+
that = thread.value
|
358
|
+
|
359
|
+
p 'this' => this, 'that' => that
|
360
|
+
|
361
|
+
~ > ruby samples/g.rb
|
362
|
+
|
363
|
+
{"that"=>14406, "this"=>14405}
|
364
|
+
{"that"=>[14407, Tue Nov 28 09:47:31 MST 2006], "this"=>[14405, Tue Nov 28 09:47:29 MST 2006]}
|
295
365
|
</pre>
|
296
366
|
|
297
367
|
</div>
|
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>Tue Nov 28 09:47:22 MST 2006</td>
|
60
60
|
</tr>
|
61
61
|
</table>
|
62
62
|
</div>
|
@@ -84,6 +84,8 @@
|
|
84
84
|
tmpdir
|
85
85
|
tempfile
|
86
86
|
fcntl
|
87
|
+
socket
|
88
|
+
sync
|
87
89
|
</div>
|
88
90
|
</div>
|
89
91
|
|
@@ -96,7 +98,9 @@
|
|
96
98
|
<h2 class="section-bar">Classes and Modules</h2>
|
97
99
|
|
98
100
|
Class <a href="../../classes/Slave.html" class="link">Slave</a><br />
|
99
|
-
::Class <a href="../../classes/Slave/
|
101
|
+
::Class <a href="../../classes/Slave/LifeLine.html" class="link">Slave::LifeLine</a><br />
|
102
|
+
::Class <a href="../../classes/Slave/ThreadSafe.html" class="link">Slave::ThreadSafe</a><br />
|
103
|
+
::Class <a href="../../classes/Slave/ThreadSafeHash.html" class="link">Slave::ThreadSafeHash</a><br />
|
100
104
|
Class <a href="../../classes/o.html" class="link">o</a><br />
|
101
105
|
|
102
106
|
</div>
|
data/doc/fr_class_index.html
CHANGED
@@ -21,7 +21,9 @@
|
|
21
21
|
<h1 class="section-bar">Classes</h1>
|
22
22
|
<div id="index-entries">
|
23
23
|
<a href="classes/Slave.html">Slave</a><br />
|
24
|
-
<a href="classes/Slave/
|
24
|
+
<a href="classes/Slave/LifeLine.html">Slave::LifeLine</a><br />
|
25
|
+
<a href="classes/Slave/ThreadSafe.html">Slave::ThreadSafe</a><br />
|
26
|
+
<a href="classes/Slave/ThreadSafeHash.html">Slave::ThreadSafeHash</a><br />
|
25
27
|
<a href="classes/o.html">o</a><br />
|
26
28
|
</div>
|
27
29
|
</div>
|
data/doc/fr_method_index.html
CHANGED
@@ -20,26 +20,34 @@
|
|
20
20
|
<div id="index">
|
21
21
|
<h1 class="section-bar">Methods</h1>
|
22
22
|
<div id="index-entries">
|
23
|
-
<a href="classes/Slave/
|
24
|
-
<a href="classes/Slave/
|
25
|
-
<a href="classes/Slave.html#
|
23
|
+
<a href="classes/Slave/LifeLine.html#M000026">catch (Slave::LifeLine)</a><br />
|
24
|
+
<a href="classes/Slave/ThreadSafe.html#M000022">class (Slave::ThreadSafe)</a><br />
|
25
|
+
<a href="classes/Slave/LifeLine.html#M000030">cling (Slave::LifeLine)</a><br />
|
26
|
+
<a href="classes/Slave/LifeLine.html#M000027">close_unused_sockets_after_forking (Slave::LifeLine)</a><br />
|
27
|
+
<a href="classes/Slave/LifeLine.html#M000028">cut (Slave::LifeLine)</a><br />
|
26
28
|
<a href="classes/Slave.html#M000002">default (Slave)</a><br />
|
29
|
+
<a href="classes/Slave.html#M000012">default (Slave)</a><br />
|
27
30
|
<a href="classes/Slave.html#M000006">detach (Slave)</a><br />
|
31
|
+
<a href="classes/Slave/ThreadSafe.html#M000018">ex (Slave::ThreadSafe)</a><br />
|
28
32
|
<a href="classes/Slave.html#M000004">fork (Slave)</a><br />
|
29
33
|
<a href="classes/Slave.html#M000011">gen_psname (Slave)</a><br />
|
30
|
-
<a href="classes/Slave.html#M000013">getopts (Slave)</a><br />
|
31
34
|
<a href="classes/Slave.html#M000003">getopts (Slave)</a><br />
|
32
|
-
<a href="classes/Slave
|
35
|
+
<a href="classes/Slave.html#M000013">getopts (Slave)</a><br />
|
36
|
+
<a href="classes/Slave/ThreadSafe.html#M000021">inspect (Slave::ThreadSafe)</a><br />
|
37
|
+
<a href="classes/Slave/ThreadSafe.html#M000019">method_missing (Slave::ThreadSafe)</a><br />
|
33
38
|
<a href="classes/Slave.html#M000005">new (Slave)</a><br />
|
34
|
-
<a href="classes/Slave/
|
35
|
-
<a href="classes/Slave/
|
39
|
+
<a href="classes/Slave/ThreadSafe.html#M000017">new (Slave::ThreadSafe)</a><br />
|
40
|
+
<a href="classes/Slave/ThreadSafeHash.html#M000031">new (Slave::ThreadSafeHash)</a><br />
|
41
|
+
<a href="classes/Slave/LifeLine.html#M000023">new (Slave::LifeLine)</a><br />
|
42
|
+
<a href="classes/Slave.html#M000015">object (Slave)</a><br />
|
43
|
+
<a href="classes/Slave.html#M000016">object (Slave)</a><br />
|
44
|
+
<a href="classes/Slave/LifeLine.html#M000029">on_cut (Slave::LifeLine)</a><br />
|
45
|
+
<a href="classes/Slave/LifeLine.html#M000024">owner? (Slave::LifeLine)</a><br />
|
46
|
+
<a href="classes/Slave/ThreadSafe.html#M000020">respond_to? (Slave::ThreadSafe)</a><br />
|
36
47
|
<a href="classes/Slave.html#M000009">shutdown (Slave)</a><br />
|
37
48
|
<a href="classes/Slave.html#M000010">shutdown? (Slave)</a><br />
|
38
|
-
<a href="classes/Slave/
|
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 />
|
49
|
+
<a href="classes/Slave/LifeLine.html#M000025">throw (Slave::LifeLine)</a><br />
|
41
50
|
<a href="classes/Slave.html#M000014">trace (Slave)</a><br />
|
42
|
-
<a href="classes/Slave/Heartbeat.html#M000023">trace (Slave::Heartbeat)</a><br />
|
43
51
|
<a href="classes/Slave.html#M000001">version (Slave)</a><br />
|
44
52
|
<a href="classes/Slave.html#M000007">wait (Slave)</a><br />
|
45
53
|
<a href="classes/Slave.html#M000008">wait2 (Slave)</a><br />
|
data/lib/slave-1.1.0.rb
ADDED
@@ -0,0 +1,623 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'fcntl'
|
6
|
+
require 'socket'
|
7
|
+
require 'sync'
|
8
|
+
|
9
|
+
# TODO - lifeline need close-on-exec set in it!
|
10
|
+
|
11
|
+
#
|
12
|
+
# the Slave class encapsulates the work of setting up a drb server in another
|
13
|
+
# process running on localhost via unix domain sockets. the slave process is
|
14
|
+
# attached to it's parent via a LifeLine which is designed such that the slave
|
15
|
+
# cannot out-live it's parent and become a zombie, even if the parent dies and
|
16
|
+
# early death, such as by 'kill -9'. the concept and purpose of the Slave
|
17
|
+
# class is to be able to setup any server object in another process so easily
|
18
|
+
# that using a multi-process, drb/ipc, based design is as easy, or easier,
|
19
|
+
# than a multi-threaded one. eg
|
20
|
+
#
|
21
|
+
# class Server
|
22
|
+
# def add_two n
|
23
|
+
# n + 2
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# slave = Slave.new 'object' => Server.new
|
28
|
+
# server = slave.object
|
29
|
+
#
|
30
|
+
# p server.add_two(40) #=> 42
|
31
|
+
#
|
32
|
+
# two other methods of providing server objects exist:
|
33
|
+
#
|
34
|
+
# a) server = Server.new "this is called the parent" }
|
35
|
+
# Slave.new(:object=>server){|s| puts "#{ s.inspect } passed to block in child process"}
|
36
|
+
#
|
37
|
+
# b) Slave.new{ Server.new "this is called only in the child" }
|
38
|
+
#
|
39
|
+
# of the two 'b' is preferred.
|
40
|
+
#
|
41
|
+
class Slave
|
42
|
+
#--{{{
|
43
|
+
VERSION = '1.1.0'
|
44
|
+
def self.version() VERSION end
|
45
|
+
#
|
46
|
+
# env config
|
47
|
+
#
|
48
|
+
DEFAULT_SOCKET_CREATION_ATTEMPTS = Integer(ENV['SLAVE_SOCKET_CREATION_ATTEMPTS'] || 42)
|
49
|
+
DEFAULT_DEBUG = (ENV['SLAVE_DEBUG'] ? true : false)
|
50
|
+
DEFAULT_THREADSAFE = (ENV['SLAVE_THREADSAFE'] ? true : false)
|
51
|
+
#
|
52
|
+
# class initialization
|
53
|
+
#
|
54
|
+
@socket_creation_attempts = DEFAULT_SOCKET_CREATION_ATTEMPTS
|
55
|
+
@debug = DEFAULT_DEBUG
|
56
|
+
@threadsafe = DEFAULT_THREADSAFE
|
57
|
+
#
|
58
|
+
# class methods
|
59
|
+
#
|
60
|
+
class << self
|
61
|
+
#--{{{
|
62
|
+
# defineds how many attempts will be made to create a temporary unix domain
|
63
|
+
# socket
|
64
|
+
attr :socket_creation_attempts, true
|
65
|
+
|
66
|
+
# if this is true and you are running from a terminal information is printed
|
67
|
+
# on STDERR
|
68
|
+
attr :debug, true
|
69
|
+
|
70
|
+
# if this is true all slave objects will be wrapped such that any call
|
71
|
+
# to the object is threadsafe. if you do not use this you must ensure
|
72
|
+
# that your objects are threadsafe __yourself__ as this is required of
|
73
|
+
# any object acting as a drb server
|
74
|
+
attr :threadsafe, true
|
75
|
+
|
76
|
+
# get a default value
|
77
|
+
def default key
|
78
|
+
#--{{{
|
79
|
+
send key
|
80
|
+
#--}}}
|
81
|
+
end
|
82
|
+
|
83
|
+
def getopts opts
|
84
|
+
#--{{{
|
85
|
+
raise ArgumentError, opts.class unless
|
86
|
+
opts.respond_to?('has_key?') and opts.respond_to?('[]')
|
87
|
+
|
88
|
+
lambda do |key, *defval|
|
89
|
+
defval = defval.shift
|
90
|
+
keys = [key, key.to_s, key.to_s.intern]
|
91
|
+
key = keys.detect{|k| opts.has_key? k } and break opts[key]
|
92
|
+
defval
|
93
|
+
end
|
94
|
+
#--}}}
|
95
|
+
end
|
96
|
+
|
97
|
+
# just fork with out silly warnings
|
98
|
+
def fork &block
|
99
|
+
#--{{{
|
100
|
+
v = $VERBOSE
|
101
|
+
begin
|
102
|
+
$VERBOSE = nil
|
103
|
+
Process::fork &block
|
104
|
+
ensure
|
105
|
+
$VERBOSE = v
|
106
|
+
end
|
107
|
+
#--}}}
|
108
|
+
end
|
109
|
+
#--}}}
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# helper classes
|
114
|
+
#
|
115
|
+
|
116
|
+
#
|
117
|
+
# ThreadSafe is a delegate wrapper class used for implementing gross thread
|
118
|
+
# safety around existing objects. when an object is wrapped with this class
|
119
|
+
# as
|
120
|
+
#
|
121
|
+
# ts = ThreadSafe.new{ AnyObject.new }
|
122
|
+
#
|
123
|
+
# then ts can be used exactly as the normal object would have been, only all
|
124
|
+
# calls are now thread safe. this is the mechanism behind the
|
125
|
+
# 'threadsafe'/:threadsafe keyword to Slave#initialize
|
126
|
+
#
|
127
|
+
class ThreadSafe
|
128
|
+
#--{{{
|
129
|
+
instance_methods.each{|m| undef_method unless m[%r/__/]}
|
130
|
+
def initialize object
|
131
|
+
@object = object
|
132
|
+
@sync = Sync.new
|
133
|
+
end
|
134
|
+
def ex
|
135
|
+
@sync.synchronize{ yield }
|
136
|
+
end
|
137
|
+
def method_missing m, *a, &b
|
138
|
+
ex{ @object.send m, *a, &b }
|
139
|
+
end
|
140
|
+
def respond_to? m
|
141
|
+
ex{ @object.respond_to? m }
|
142
|
+
end
|
143
|
+
def inspect
|
144
|
+
ex{ @object.inspect }
|
145
|
+
end
|
146
|
+
def class
|
147
|
+
ex{ @object.class }
|
148
|
+
end
|
149
|
+
#--}}}
|
150
|
+
end
|
151
|
+
#
|
152
|
+
# a simple thread safe hash used to map object_id to a set of file
|
153
|
+
# descriptors in the LifeLine class. see LifeLine::FDS
|
154
|
+
#
|
155
|
+
class ThreadSafeHash < Hash
|
156
|
+
def self.new(*a, &b) ThreadSafe.new(super) end
|
157
|
+
end
|
158
|
+
#
|
159
|
+
# the LifeLine class is used to communitacte between child and parent
|
160
|
+
# processes and to prevent child processes from ever becoming zombies or
|
161
|
+
# otherwise abandoned by their parents. the basic concept is that a socket
|
162
|
+
# pair is setup between child and parent. the child process, because it is
|
163
|
+
# a Slave, sets up a handler such that, should it's socket ever grow stale,
|
164
|
+
# will exit the process. this class replaces the HeartBeat class from
|
165
|
+
# previous Slave versions.
|
166
|
+
#
|
167
|
+
class LifeLine
|
168
|
+
#--{{{
|
169
|
+
FDS = ThreadSafeHash.new
|
170
|
+
|
171
|
+
def initialize
|
172
|
+
@pair = Socket.pair Socket::AF_UNIX, Socket::SOCK_STREAM, 0
|
173
|
+
@owner = Process.pid
|
174
|
+
@pid = nil
|
175
|
+
@socket = nil
|
176
|
+
@object_id = object_id
|
177
|
+
|
178
|
+
@fds = @pair.map{|s| s.fileno}
|
179
|
+
oid, fds = @object_id, @fds
|
180
|
+
FDS[oid] = fds
|
181
|
+
ObjectSpace.define_finalizer(self){ FDS.delete oid }
|
182
|
+
end
|
183
|
+
|
184
|
+
def owner?
|
185
|
+
Process.pid == @owner
|
186
|
+
end
|
187
|
+
|
188
|
+
def throw *ignored
|
189
|
+
raise unless owner?
|
190
|
+
@pair[-1].close
|
191
|
+
@pair[-1] = nil
|
192
|
+
@pid = Process.pid
|
193
|
+
@socket = @pair[0]
|
194
|
+
@socket.sync = true
|
195
|
+
end
|
196
|
+
|
197
|
+
def catch *ignored
|
198
|
+
raise if owner?
|
199
|
+
@pair[0].close
|
200
|
+
@pair[0] = nil
|
201
|
+
@pid = Process.pid
|
202
|
+
@socket = @pair[-1]
|
203
|
+
@socket.sync = true
|
204
|
+
close_unused_sockets_after_forking
|
205
|
+
end
|
206
|
+
|
207
|
+
def close_unused_sockets_after_forking
|
208
|
+
begin
|
209
|
+
to_delete = []
|
210
|
+
begin
|
211
|
+
FDS.each do |oid, fds|
|
212
|
+
next if oid == @object_id
|
213
|
+
begin
|
214
|
+
IO.for_fd(fds.first).close
|
215
|
+
rescue Exception => e
|
216
|
+
STDERR.puts "#{ e.message } (#{ e.class })\n#{ e.backtrace.join 10.chr }"
|
217
|
+
ensure
|
218
|
+
to_delete << oid
|
219
|
+
end
|
220
|
+
end
|
221
|
+
ensure
|
222
|
+
FDS.ex{ to_delete.each{|oid| FDS.delete oid rescue 42} }
|
223
|
+
end
|
224
|
+
GC.start
|
225
|
+
rescue Exception => e
|
226
|
+
42
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def cut
|
231
|
+
raise unless owner?
|
232
|
+
raise unless @socket
|
233
|
+
@socket.close rescue nil
|
234
|
+
FDS.delete object_id
|
235
|
+
end
|
236
|
+
alias_method "release", "cut"
|
237
|
+
|
238
|
+
DELEGATED = %w( puts gets read write close flush each )
|
239
|
+
|
240
|
+
DELEGATED.each do |m|
|
241
|
+
code = <<-code
|
242
|
+
def #{ m }(*a, &b)
|
243
|
+
raise unless @socket
|
244
|
+
@socket.#{ m } *a, &b
|
245
|
+
end
|
246
|
+
code
|
247
|
+
module_eval code, __FILE__, __LINE__
|
248
|
+
end
|
249
|
+
|
250
|
+
def on_cut &b
|
251
|
+
at_exit{ begin; b.call; ensure; b = nil; end if b}
|
252
|
+
Thread.new(Thread.current){|current|
|
253
|
+
Thread.current.abort_on_exception = true
|
254
|
+
begin
|
255
|
+
each{|*a|}
|
256
|
+
rescue Exception
|
257
|
+
current.raise $!
|
258
|
+
42
|
259
|
+
ensure
|
260
|
+
begin; b.call; ensure; b = nil; end if b
|
261
|
+
end
|
262
|
+
}
|
263
|
+
end
|
264
|
+
|
265
|
+
def cling &b
|
266
|
+
on_cut{ begin; b.call if b; ensure; Kernel.exit; end }.join
|
267
|
+
end
|
268
|
+
#--}}}
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# attrs
|
273
|
+
#
|
274
|
+
attr :obj
|
275
|
+
attr :socket_creation_attempts
|
276
|
+
attr :debug
|
277
|
+
attr :psname
|
278
|
+
attr :at_exit
|
279
|
+
attr :dumped
|
280
|
+
|
281
|
+
attr :shutdown
|
282
|
+
attr :status
|
283
|
+
attr :object
|
284
|
+
attr :pid
|
285
|
+
attr :ppid
|
286
|
+
attr :uri
|
287
|
+
attr :socket
|
288
|
+
#
|
289
|
+
# sets up a child process serving any object as a DRb server running locally
|
290
|
+
# on unix domain sockets. the child process has a LifeLine established
|
291
|
+
# between it and the parent, making it impossible for the child to outlive
|
292
|
+
# the parent (become a zombie). the object to serve is specfied either
|
293
|
+
# directly using the 'object'/:object keyword
|
294
|
+
#
|
295
|
+
# Slave.new :object => MyServer.new
|
296
|
+
#
|
297
|
+
# or, preferably, using the block form
|
298
|
+
#
|
299
|
+
# Slave.new{ MyServer.new }
|
300
|
+
#
|
301
|
+
# when the block form is used the object is contructed in the child process
|
302
|
+
# itself. this is quite advantageous if the child object consumes resources
|
303
|
+
# or opens file handles (db connections, etc). by contructing the object in
|
304
|
+
# the child any resources are consumed from the child's address space and
|
305
|
+
# things like open file handles will not be carried into subsequent child
|
306
|
+
# processes (via standard unix fork semantics). in the event that a block
|
307
|
+
# is specified but the object cannot be constructed and, instead, throws and
|
308
|
+
# Exception, that exception will be propogated to the parent process.
|
309
|
+
#
|
310
|
+
# opts may contain the following keys, as either strings or symbols
|
311
|
+
#
|
312
|
+
# object : specify the slave object. otherwise block value is used.
|
313
|
+
# socket_creation_attempts : specify how many attempts to create a unix domain socket will be made
|
314
|
+
# debug : turn on some logging to STDERR
|
315
|
+
# psname : specify the name that will appear in 'top' ($0)
|
316
|
+
# at_exit : specify a lambda to be called in the *parent* when the child dies
|
317
|
+
# dumped : specify that the slave object should *not* be DRbUndumped (default is DRbUndumped)
|
318
|
+
# threadsafe : wrap the slave object with ThreadSafe to implement gross thread safety
|
319
|
+
#
|
320
|
+
def initialize opts = {}, &block
|
321
|
+
#--{{{
|
322
|
+
getopt = getopts opts
|
323
|
+
|
324
|
+
@obj = getopt['object']
|
325
|
+
@socket_creation_attempts = getopt['socket_creation_attempts'] || default('socket_creation_attempts')
|
326
|
+
@debug = getopt['debug'] || default('debug')
|
327
|
+
@psname = getopt['psname']
|
328
|
+
@at_exit = getopt['at_exit']
|
329
|
+
@dumped = getopt['dumped']
|
330
|
+
@threadsafe = getopt['threadsafe'] || default('threadsafe')
|
331
|
+
|
332
|
+
raise ArgumentError, 'no slave object or slave object block provided!' if
|
333
|
+
@obj.nil? and block.nil?
|
334
|
+
|
335
|
+
@shutdown = false
|
336
|
+
@waiter = @status = nil
|
337
|
+
@lifeline = LifeLine.new
|
338
|
+
|
339
|
+
# weird syntax because dot/rdoc chokes on this!?!?
|
340
|
+
init_failure = lambda do |e|
|
341
|
+
trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
|
342
|
+
o = Object.new
|
343
|
+
class << o
|
344
|
+
attr_accessor '__slave_object_failure__'
|
345
|
+
end
|
346
|
+
o.__slave_object_failure__ = Marshal.dump [e.class, e.message, e.backtrace]
|
347
|
+
@object = o
|
348
|
+
end
|
349
|
+
|
350
|
+
#
|
351
|
+
# child
|
352
|
+
#
|
353
|
+
unless((@pid = Slave::fork))
|
354
|
+
e = nil
|
355
|
+
begin
|
356
|
+
Kernel.at_exit{ Kernel.exit! }
|
357
|
+
@lifeline.catch
|
358
|
+
|
359
|
+
if @obj
|
360
|
+
@object = @obj
|
361
|
+
else
|
362
|
+
begin
|
363
|
+
@object = block.call
|
364
|
+
rescue Exception => e
|
365
|
+
init_failure[e]
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
if block and @obj
|
370
|
+
begin
|
371
|
+
block[@obj]
|
372
|
+
rescue Exception => e
|
373
|
+
init_failure[e]
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
$0 = (@psname ||= gen_psname(@object))
|
378
|
+
|
379
|
+
unless @dumped or @object.respond_to?('__slave_object_failure__')
|
380
|
+
@object.extend DRbUndumped
|
381
|
+
end
|
382
|
+
|
383
|
+
if @threadsafe
|
384
|
+
@object = ThreadSafe.new @object
|
385
|
+
end
|
386
|
+
|
387
|
+
@ppid, @pid = Process::ppid, Process::pid
|
388
|
+
@socket = nil
|
389
|
+
@uri = nil
|
390
|
+
|
391
|
+
tmpdir, basename = Dir::tmpdir, File::basename(@psname)
|
392
|
+
|
393
|
+
@socket_creation_attempts.times do |attempt|
|
394
|
+
se = nil
|
395
|
+
begin
|
396
|
+
s = File::join(tmpdir, "#{ basename }_#{ attempt }_#{ rand }")
|
397
|
+
u = "drbunix://#{ s }"
|
398
|
+
DRb::start_service u, @object
|
399
|
+
@socket = s
|
400
|
+
@uri = u
|
401
|
+
trace{ "child - socket <#{ @socket }>" }
|
402
|
+
trace{ "child - uri <#{ @uri }>" }
|
403
|
+
break
|
404
|
+
rescue Errno::EADDRINUSE => se
|
405
|
+
nil
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
if @socket and @uri
|
410
|
+
trap('SIGUSR2') do
|
411
|
+
DBb::thread.kill rescue nil
|
412
|
+
FileUtils::rm_f @socket rescue nil
|
413
|
+
exit
|
414
|
+
end
|
415
|
+
|
416
|
+
@lifeline.puts @socket
|
417
|
+
@lifeline.cling
|
418
|
+
else
|
419
|
+
@lifeline.release
|
420
|
+
warn "slave(#{ $$ }) could not create socket!"
|
421
|
+
exit
|
422
|
+
end
|
423
|
+
rescue Exception => e
|
424
|
+
trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
|
425
|
+
ensure
|
426
|
+
status = e.respond_to?('status') ? e.status : 1
|
427
|
+
exit(status)
|
428
|
+
end
|
429
|
+
#
|
430
|
+
# parent
|
431
|
+
#
|
432
|
+
else
|
433
|
+
detach
|
434
|
+
@lifeline.throw
|
435
|
+
|
436
|
+
buf = @lifeline.gets
|
437
|
+
raise "failed to find slave socket" if buf.nil? or buf.strip.empty?
|
438
|
+
@socket = buf.strip
|
439
|
+
trace{ "parent - socket <#{ @socket }>" }
|
440
|
+
|
441
|
+
if @at_exit
|
442
|
+
@at_exit_thread = @lifeline.on_cut{
|
443
|
+
@at_exit.respond_to?('call') ? @at_exit.call(self) : send(@at_exit.to_s, self)
|
444
|
+
}
|
445
|
+
end
|
446
|
+
|
447
|
+
if @socket and File::exist? @socket
|
448
|
+
Kernel.at_exit{ FileUtils::rm_f @socket }
|
449
|
+
@uri = "drbunix://#{ socket }"
|
450
|
+
trace{ "parent - uri <#{ @uri }>" }
|
451
|
+
#
|
452
|
+
# starting drb on localhost avoids dns lookups!
|
453
|
+
#
|
454
|
+
DRb::start_service('druby://localhost:0', nil) unless DRb::thread
|
455
|
+
@object = DRbObject::new nil, @uri
|
456
|
+
if @object.respond_to? '__slave_object_failure__'
|
457
|
+
c, m, bt = Marshal.load @object.__slave_object_failure__
|
458
|
+
(e = c.new(m)).set_backtrace bt
|
459
|
+
trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
|
460
|
+
raise e
|
461
|
+
end
|
462
|
+
@psname ||= gen_psname(@object)
|
463
|
+
else
|
464
|
+
raise "failed to find slave socket <#{ @socket }>"
|
465
|
+
end
|
466
|
+
end
|
467
|
+
#--}}}
|
468
|
+
end
|
469
|
+
#
|
470
|
+
# starts a thread to collect the child status and sets up at_exit handler to
|
471
|
+
# prevent zombies. the at_exit handler is canceled if the thread is able to
|
472
|
+
# collect the status
|
473
|
+
#
|
474
|
+
def detach
|
475
|
+
#--{{{
|
476
|
+
reap = lambda do |cid|
|
477
|
+
begin
|
478
|
+
@status = Process::waitpid2(cid).last
|
479
|
+
rescue Exception => e
|
480
|
+
m, c, b = e.message, e.class, e.backtrace.join("\n")
|
481
|
+
warn "#{ m } (#{ c })\n#{ b }" unless e.is_a? Errno::ECHILD
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
Kernel.at_exit do
|
486
|
+
shutdown rescue nil
|
487
|
+
reap[@pid] rescue nil
|
488
|
+
end
|
489
|
+
|
490
|
+
@waiter =
|
491
|
+
Thread.new do
|
492
|
+
begin
|
493
|
+
@status = Process::waitpid2(@pid).last
|
494
|
+
ensure
|
495
|
+
reap = lambda{|cid| 'no-op' }
|
496
|
+
end
|
497
|
+
end
|
498
|
+
#--}}}
|
499
|
+
end
|
500
|
+
#
|
501
|
+
# wait for slave to finish. if the keyword 'non_block'=>true is given a
|
502
|
+
# thread is returned to do the waiting in an async fashion. eg
|
503
|
+
#
|
504
|
+
# thread = slave.wait(:non_block=>true){|value| "background <#{ value }>"}
|
505
|
+
#
|
506
|
+
def wait opts = {}, &b
|
507
|
+
#--{{{
|
508
|
+
b ||= lambda{|exit_status|}
|
509
|
+
non_block = getopts(opts)['non_block']
|
510
|
+
non_block ? Thread.new{ b[ @waiter.value ] } : b[ @waiter.value ]
|
511
|
+
#--}}}
|
512
|
+
end
|
513
|
+
alias :wait2 :wait
|
514
|
+
#
|
515
|
+
# cuts the lifeline and kills the child process - give the key 'quiet' to
|
516
|
+
# ignore errors shutting down, including having already shutdown
|
517
|
+
#
|
518
|
+
def shutdown opts = {}
|
519
|
+
#--{{{
|
520
|
+
quiet = getopts(opts)['quiet']
|
521
|
+
raise "already shutdown" if @shutdown unless quiet
|
522
|
+
begin; Process::kill 'SIGUSR2', @pid; rescue Exception => e; end
|
523
|
+
begin; @lifeline.cut; rescue Exception; end
|
524
|
+
raise e if e unless quiet
|
525
|
+
@shutdown = true
|
526
|
+
#--}}}
|
527
|
+
end
|
528
|
+
#
|
529
|
+
# true
|
530
|
+
#
|
531
|
+
def shutdown?
|
532
|
+
#--{{{
|
533
|
+
@shutdown
|
534
|
+
#--}}}
|
535
|
+
end
|
536
|
+
#
|
537
|
+
# generate a default name to appear in ps/top
|
538
|
+
#
|
539
|
+
def gen_psname obj
|
540
|
+
#--{{{
|
541
|
+
"slave_#{ obj.class }_#{ obj.object_id }_#{ Process::ppid }_#{ Process::pid }".downcase.gsub(%r/\s+/,'_')
|
542
|
+
#--}}}
|
543
|
+
end
|
544
|
+
#
|
545
|
+
# see docs for Slave.default
|
546
|
+
#
|
547
|
+
def default key
|
548
|
+
#--{{{
|
549
|
+
self.class.default key
|
550
|
+
#--}}}
|
551
|
+
end
|
552
|
+
#
|
553
|
+
# see docs for Slave.getopts
|
554
|
+
#
|
555
|
+
def getopts opts
|
556
|
+
#--{{{
|
557
|
+
self.class.getopts opts
|
558
|
+
#--}}}
|
559
|
+
end
|
560
|
+
#
|
561
|
+
# debugging output - ENV['SLAVE_DEBUG']=1 to enable
|
562
|
+
#
|
563
|
+
def trace
|
564
|
+
#--{{{
|
565
|
+
if @debug
|
566
|
+
STDERR.puts yield
|
567
|
+
STDERR.flush
|
568
|
+
end
|
569
|
+
#--}}}
|
570
|
+
end
|
571
|
+
|
572
|
+
#
|
573
|
+
# a simple convenience method which returns an *object* from another
|
574
|
+
# process. the object returned is the result of the supplied block. eg
|
575
|
+
#
|
576
|
+
# object = Slave.object{ processor_intensive_object_built_in_child_process() }
|
577
|
+
#
|
578
|
+
# eg.
|
579
|
+
#
|
580
|
+
# the call can be made asynchronous via the 'async'/:async keyword
|
581
|
+
#
|
582
|
+
# thread = Slave.object(:async=>true){ long_processor_intensive_object_built_in_child_process() }
|
583
|
+
#
|
584
|
+
# # go on about your coding business then, later
|
585
|
+
#
|
586
|
+
# object = thread.value
|
587
|
+
#
|
588
|
+
def self.object opts = {}, &b
|
589
|
+
#--{{{
|
590
|
+
l = lambda{ begin; b.call; ensure; exit; end }
|
591
|
+
|
592
|
+
async = opts.delete('async') || opts.delete(:async)
|
593
|
+
|
594
|
+
opts['object'] = opts[:object] = l
|
595
|
+
opts['dumped'] = opts[:dumped] = true
|
596
|
+
|
597
|
+
slave = Slave.new opts
|
598
|
+
|
599
|
+
async ? Thread.new{ slave.object.call } : slave.object.call
|
600
|
+
#--}}}
|
601
|
+
end
|
602
|
+
def self.object opts = {}, &b
|
603
|
+
#--{{{
|
604
|
+
async = opts.delete('async') || opts.delete(:async)
|
605
|
+
|
606
|
+
opts['object'] = opts[:object] = lambda(&b)
|
607
|
+
opts['dumped'] = opts[:dumped] = true
|
608
|
+
|
609
|
+
slave = Slave.new opts
|
610
|
+
|
611
|
+
value = lambda do |slave|
|
612
|
+
begin
|
613
|
+
slave.object.call
|
614
|
+
ensure
|
615
|
+
slave.shutdown
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
async ? Thread.new{ value[slave] } : value[slave]
|
620
|
+
#--}}}
|
621
|
+
end
|
622
|
+
#--}}}
|
623
|
+
end # class Slave
|