slave 0.2.0 → 1.0.0

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