slave 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>&nbsp;&nbsp;
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, &amp;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">&amp;</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
- Fri Oct 13 13:24:48 MDT 2006
1
+ Tue Nov 28 09:47:34 MST 2006
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Fri Oct 13 13:23:22 MDT 2006</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
- obj = AnyClass::new
90
+ slave = Slave::new{ AnyObject.new }
91
91
 
92
- slave = Slave::new 'object' =&gt; obj
92
+ slave.object #=&gt; handle on drb object
93
+ slave.uri #=&gt; uri of the drb object
94
+ slave.socket #=&gt; unix domain socket path for drb object
95
+ slave.psname #=&gt; title shown in ps/top
93
96
 
94
- p slave.object # handle on drb object
95
- p slave.uri # uri of the drb object
96
- p slave.socket # unix domain socket path for drb object
97
- p slave.psname # title shown in ps/top
97
+ object = slave.object
98
+
99
+ value = object.any_method #=&gt; 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
- * socket_creation_attempts
103
- * pulse_rate
104
- * psname
105
- * debug
106
- * dumped
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
- THIS RELEASE IS !! NOT !! BACKWARD COMPATIBLE. NOTE NEW CTOR SYNTAX.
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 =&gt; Server.new
182
- server = slave.object
183
198
 
199
+ server = slave.object
184
200
  p server.add_two(40) #=&gt; 42
185
201
 
202
+ slave.shutdown
203
+
186
204
  ~ &gt; ruby samples/a.rb
187
205
 
188
206
  42
@@ -217,7 +235,7 @@ SAMPLES
217
235
  ~ &gt; ruby samples/b.rb
218
236
 
219
237
  :postgresql
220
- ./lib/slave.rb:276:in `initialize': undefined method `typo' for #&lt;Server:0xb7573350&gt; (NoMethodError)
238
+ ./lib/slave.rb:460:in `initialize': undefined method `typo' for #&lt;Server:0xb7565694&gt; (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
  ~ &gt; ruby samples/c.rb
251
269
 
252
- 12244
253
- 12245
254
- ./lib/slave.rb:276:in `initialize': undefined local variable or method `fubar' for main:Object (NameError)
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
  ~ &gt; ruby samples/d.rb
273
291
 
292
+ &quot;child&quot;
274
293
  &quot;parent&quot;
275
294
 
276
295
  &lt;========&lt; samples/e.rb &gt;========&gt;
@@ -292,6 +311,57 @@ SAMPLES
292
311
  ~ &gt; ruby samples/e.rb
293
312
 
294
313
  &quot;child&quot;
314
+
315
+ &lt;========&lt; samples/f.rb &gt;========&gt;
316
+
317
+ ~ &gt; 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 &lt;&lt; 42
331
+ puts a #=&gt; 42
332
+
333
+ ~ &gt; ruby samples/f.rb
334
+
335
+ 42
336
+
337
+ &lt;========&lt; samples/g.rb &gt;========&gt;
338
+
339
+ ~ &gt; 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' =&gt; this, 'that' =&gt; that
351
+
352
+ #
353
+ # any object can be returned and it can be returned asychronously via a thread
354
+ #
355
+ thread = Slave.object(:async =&gt; true){ sleep 2 and [ Process.pid, Time.now ] }
356
+ this = [ Process.pid, Time.now ]
357
+ that = thread.value
358
+
359
+ p 'this' =&gt; this, 'that' =&gt; that
360
+
361
+ ~ &gt; ruby samples/g.rb
362
+
363
+ {&quot;that&quot;=&gt;14406, &quot;this&quot;=&gt;14405}
364
+ {&quot;that&quot;=&gt;[14407, Tue Nov 28 09:47:31 MST 2006], &quot;this&quot;=&gt;[14405, Tue Nov 28 09:47:29 MST 2006]}
295
365
  </pre>
296
366
 
297
367
  </div>
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Fri Oct 13 13:24:25 MDT 2006</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&nbsp;&nbsp;
85
85
  tempfile&nbsp;&nbsp;
86
86
  fcntl&nbsp;&nbsp;
87
+ socket&nbsp;&nbsp;
88
+ sync&nbsp;&nbsp;
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
- &nbsp;&nbsp;::Class <a href="../../classes/Slave/Heartbeat.html" class="link">Slave::Heartbeat</a><br />
101
+ &nbsp;&nbsp;::Class <a href="../../classes/Slave/LifeLine.html" class="link">Slave::LifeLine</a><br />
102
+ &nbsp;&nbsp;::Class <a href="../../classes/Slave/ThreadSafe.html" class="link">Slave::ThreadSafe</a><br />
103
+ &nbsp;&nbsp;::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>
@@ -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/Heartbeat.html">Slave::Heartbeat</a><br />
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>
@@ -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/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 />
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/Heartbeat.html#M000015">new (Slave::Heartbeat)</a><br />
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/Heartbeat.html#M000020">parent_start (Slave::Heartbeat)</a><br />
35
- <a href="classes/Slave/Heartbeat.html#M000017">parent_start (Slave::Heartbeat)</a><br />
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/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 />
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 />
@@ -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