slave 1.0.0 → 1.1.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 +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
|