slave 0.2.0 → 1.0.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.
@@ -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