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.
@@ -1,117 +0,0 @@
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: object</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>object <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
- </table>
63
- </div>
64
- <!-- banner header -->
65
-
66
- <div id="bodyContent">
67
-
68
-
69
- <div id="contextContent">
70
- <div id="diagram">
71
- <map name="map">
72
- <area shape="RECT" coords="123,98,195,50" href="Slave.html" alt="Slave">
73
- <area shape="RECT" coords="27,98,99,50" href="object.html" alt="object">
74
- </map>
75
- <img src="../dot/f_1.jpg" usemap="#map" border=0 alt="TopLevel">
76
- </div>
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
- <div id="attribute-list">
85
- <h2 class="section-bar">Attributes</h2>
86
-
87
- <div class="name-list">
88
- <table>
89
- <tr class="top-aligned-row context-row">
90
- <td class="context-item-name">__slave_object_failure__</td>
91
- <td class="context-item-value">&nbsp;[RW]&nbsp;</td>
92
- <td class="context-item-desc"></td>
93
- </tr>
94
- </table>
95
- </div>
96
- </div>
97
-
98
-
99
- </div>
100
-
101
-
102
-
103
- <!-- if includes -->
104
-
105
-
106
- <!-- if method_list -->
107
-
108
-
109
- </div>
110
-
111
-
112
- <div id="validator-badges">
113
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
114
- </div>
115
-
116
- </body>
117
- </html>
data/doc/dot/f_2.dot DELETED
@@ -1,29 +0,0 @@
1
- digraph TopLevel {
2
- compound = true
3
- bgcolor = lightcyan1
4
- fontname = Arial
5
- fontsize = 8
6
- label = "lib/slave.rb"
7
- node [
8
- fontname = Arial,
9
- fontsize = 8,
10
- color = black
11
- ]
12
-
13
- subgraph cluster_1 {
14
- fontname = Arial
15
- color = red
16
- label = "lib/slave.rb"
17
- Slave [
18
- fontcolor = black,
19
- URL = "classes/Slave.html",
20
- shape = ellipse,
21
- color = palegoldenrod,
22
- style = filled,
23
- label = "Slave"
24
- ]
25
-
26
- }
27
-
28
- }
29
-
data/doc/dot/f_2.jpg DELETED
Binary file
@@ -1,107 +0,0 @@
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>File: VERSION</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="fileHeader">
50
- <h1>VERSION</h1>
51
- <table class="header-table">
52
- <tr class="top-aligned-row">
53
- <td><strong>Path:</strong></td>
54
- <td>VERSION
55
- </td>
56
- </tr>
57
- <tr class="top-aligned-row">
58
- <td><strong>Last Update:</strong></td>
59
- <td>Fri Nov 12 11:53:15 MST 2004</td>
60
- </tr>
61
- </table>
62
- </div>
63
- <!-- banner header -->
64
-
65
- <div id="bodyContent">
66
-
67
-
68
- <div id="contextContent">
69
- <div id="diagram">
70
- <map name="map">
71
- </map>
72
- <img src="../dot/f_1.jpg" usemap="#map" border=0 alt="TopLevel">
73
- </div>
74
-
75
- <div id="description">
76
- <p>
77
- 0.0.0
78
- </p>
79
-
80
- </div>
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
- </div>
90
-
91
-
92
-
93
- <!-- if includes -->
94
-
95
-
96
- <!-- if method_list -->
97
-
98
-
99
- </div>
100
-
101
-
102
- <div id="validator-badges">
103
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
104
- </div>
105
-
106
- </body>
107
- </html>
data/lib/slave-1.0.0.rb DELETED
@@ -1,533 +0,0 @@
1
- require 'drb/drb'
2
- require 'fileutils'
3
- require 'tmpdir'
4
- require 'tempfile'
5
- require 'fcntl'
6
-
7
- #
8
- # the Slave class encapsulates the work of setting up a drb server in another
9
- # process running on localhost. the slave process is attached to it's parent
10
- # via a Heartbeat which is designed such that the slave cannot out-live it's
11
- # parent and become a zombie, even if the parent dies and early death, such as
12
- # by 'kill -9'. the concept and purpose of the Slave class is to be able to
13
- # setup any server object in another process so easily that using a
14
- # multi-process, drb/ipc, based design is as easy, or easier, than a
15
- # multi-threaded one. eg
16
- #
17
- # class Server
18
- # def add_two n
19
- # n + 2
20
- # end
21
- # end
22
- #
23
- # slave = Slave.new 'object' => Server.new
24
- # server = slave.object
25
- #
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.
36
- #
37
- class Slave
38
- #--{{{
39
- VERSION = '1.0.0'
40
- def self.version() VERSION end
41
- #
42
- # config
43
- #
44
- DEFAULT_SOCKET_CREATION_ATTEMPTS =
45
- Integer(ENV['SLAVE_SOCKET_CREATION_ATTEMPTS'] || 42)
46
-
47
- DEFAULT_PULSE_RATE =
48
- Float(ENV['SLAVE_PULSE_RATE'] || 8)
49
-
50
- DEFAULT_DEBUG =
51
- (ENV['SLAVE_DEBUG'] ? true : false)
52
-
53
- @socket_creation_attempts = DEFAULT_SOCKET_CREATION_ATTEMPTS
54
- @pulse_rate = DEFAULT_PULSE_RATE
55
- @debug = DEFAULT_DEBUG
56
- #
57
- # class methods
58
- #
59
- class << self
60
- #--{{{
61
- # defineds how many attempts will be made to create a temporary unix domain
62
- # socket
63
- attr :socket_creation_attempts, true
64
-
65
- # defined the rate of pinging in the Heartbeat object
66
- attr :pulse_rate, true
67
-
68
- # if this is true and you are running from a terminal information is printed
69
- # on STDERR
70
- attr :debug, true
71
-
72
- # get a default value
73
- def default key
74
- #--{{{
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
90
- #--}}}
91
- end
92
-
93
- # just fork with out silly warnings
94
- def fork &block
95
- #--{{{
96
- v = $VERBOSE
97
- begin
98
- $VERBOSE = nil
99
- Process::fork &block
100
- ensure
101
- $VERBOSE = v
102
- end
103
- #--}}}
104
- end
105
- #--}}}
106
- end
107
-
108
-
109
- attr :obj
110
- attr :socket_creation_attempts
111
- attr :pulse_rate
112
- attr :debug
113
- attr :psname
114
- attr :at_exit
115
-
116
- attr :shutdown
117
- attr :status
118
- attr :object
119
- attr :pid
120
- attr :ppid
121
- attr :uri
122
- attr :socket
123
-
124
- #
125
- # opts may contain the keys 'object', 'socket_creation_attempts',
126
- # 'pulse_rate', 'psname', 'dumped', or 'debug'
127
- #
128
- def initialize opts = {}, &block
129
- #--{{{
130
- getopt = getopts opts
131
-
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']
139
-
140
- raise ArgumentError, 'no slave object!' if
141
- @obj.nil? and block.nil?
142
-
143
- @shutdown = false
144
- @waiter = @status = nil
145
-
146
- @heartbeat = Heartbeat::new @pulse_rate, @debug
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
-
160
- #
161
- # child
162
- #
163
- unless((@pid = Slave::fork))
164
- e = nil
165
- begin
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
192
-
193
- @r.close
194
- @r2.close
195
- @socket = nil
196
- @uri = nil
197
-
198
- tmpdir, basename = Dir::tmpdir, File::basename(@psname)
199
-
200
- @socket_creation_attempts.times do |attempt|
201
- se = nil
202
- begin
203
- s = File::join(tmpdir, "#{ basename }_#{ attempt }")
204
- u = "drbunix://#{ s }"
205
- DRb::start_service u, @object
206
- @socket = s
207
- @uri = u
208
- trace{ "child - socket <#{ @socket }>" }
209
- trace{ "child - uri <#{ @uri }>" }
210
- break
211
- rescue Errno::EADDRINUSE => se
212
- nil
213
- end
214
- end
215
-
216
- if @socket and @uri
217
- @heartbeat.start
218
-
219
- trap('SIGUSR2') do
220
- # @heartbeat.stop rescue nil
221
- DBb::thread.kill rescue nil
222
- FileUtils::rm_f @socket rescue nil
223
- exit
224
- end
225
-
226
- @w.write @socket
227
- @w.close
228
- DRb::thread.join
229
- else
230
- @w.close
231
- end
232
- rescue Exception => e
233
- trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
234
- ensure
235
- status = e.respond_to?('status') ? e.status : 1
236
- exit(status)
237
- end
238
- #
239
- # parent
240
- #
241
- else
242
- detach
243
- @w.close
244
- @w2.close
245
- @socket = @r.read
246
- @r.close
247
-
248
- trace{ "parent - socket <#{ @socket }>" }
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
-
264
- if @socket and File::exist? @socket
265
- Kernel.at_exit{ FileUtils::rm_f @socket }
266
- @uri = "drbunix://#{ socket }"
267
- trace{ "parent - uri <#{ @uri }>" }
268
- @heartbeat.start
269
- #
270
- # starting drb on localhost avoids dns lookups!
271
- #
272
- DRb::start_service('druby://localhost:0', nil) unless DRb::thread
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)
280
- else
281
- raise "failed to find slave socket <#{ @socket }>"
282
- end
283
- end
284
- #--}}}
285
- end
286
- #
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
290
- #
291
- def detach
292
- #--{{{
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
315
- #--}}}
316
- end
317
- #
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
320
- #
321
- # thread = slave.wait(:non_block=>true){|value| "background <#{ value }>"}
322
- #
323
- def wait opts = {}, &b
324
- #--{{{
325
- b ||= lambda{|exit_status|}
326
- non_block = getopts(opts)['non_block']
327
- non_block ? Thread.new{ b[ @waiter.value ] } : b[ @waiter.value ]
328
- #--}}}
329
- end
330
- alias :wait2 :wait
331
- #
332
- # stops the heartbeat thread and kills the child process - give the key
333
- # 'quiet' to ignore errors shutting down, including having already shutdown
334
- #
335
- def shutdown opts = {}
336
- #--{{{
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
342
- @shutdown = true
343
- #--}}}
344
- end
345
- #
346
- # true
347
- #
348
- def shutdown?
349
- #--{{{
350
- @shutdown
351
- #--}}}
352
- end
353
- #
354
- # generate a default name to appear in ps/top
355
- #
356
- def gen_psname obj
357
- #--{{{
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
367
- #--}}}
368
- end
369
- #
370
- # see docs for Slave.getopts
371
- #
372
- def getopts opts
373
- #--{{{
374
- self.class.getopts opts
375
- #--}}}
376
- end
377
- #
378
- # debugging output - ENV['SLAVE_DEBUG']=1 to enable
379
- #
380
- def trace
381
- #--{{{
382
- STDERR.puts(yield) if @debug and STDERR.tty?
383
- #--}}}
384
- end
385
- #
386
- # the Heartbeat class is essentially wrapper over an IPC channel that sends
387
- # a ping on the channel indicating process health. if either end of the
388
- # channel is detached the ping will fail and an error will be raised. in
389
- # this way it is ensured that Slave objects cannot continue to live without
390
- # their parent being alive.
391
- #
392
- class Heartbeat
393
- #--{{{
394
- def initialize pulse_rate = 4.2, debug = false
395
- #--{{{
396
- @pulse_rate = Float pulse_rate
397
- @debug = debug
398
- @r, @w = IO::pipe
399
- @pid = Process::pid
400
- @ppid = Process::ppid
401
- @cid = nil
402
- @thread = nil
403
- @ppid = nil
404
- @whoami = nil
405
- @beating = nil
406
- @pipe = nil
407
- #--}}}
408
- end
409
- def start
410
- #--{{{
411
- if Process::pid == @pid
412
- @w.close
413
- @pipe = @r
414
- @pipe.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
415
- parent_start
416
- else
417
- @r.close
418
- @pipe = @w
419
- child_start
420
- end
421
- @beating = true
422
- #--}}}
423
- end
424
- def parent_start
425
- #--{{{
426
- @whoami = 'parent'
427
- @thread =
428
- Thread::new(Thread::current) do |cur|
429
- begin
430
- loop do
431
- buf = @pipe.gets
432
- trace{ "<#{ @whoami }> <#{ @pid }> gets <#{ buf.inspect }>" }
433
- @cid = Integer buf.strip if @cid.nil? and buf =~ %r/^\s*\d+\s*$/
434
- end
435
- rescue => e
436
- cur.raise e
437
- ensure
438
- @pipe.close rescue nil
439
- end
440
- end
441
- #--}}}
442
- end
443
- def child_start
444
- #--{{{
445
- @whoami = 'child'
446
- @pid = Process::pid
447
- @ppid = Process::ppid
448
- @thread =
449
- Thread::new(Thread::current) do |cur|
450
- begin
451
- loop do
452
- trace{ "<#{ @whoami }> <#{ @pid }> puts <#{ @pid }>" }
453
- @pipe.puts @pid
454
- Process::kill 0, @ppid
455
- sleep @pulse_rate
456
- end
457
- rescue => e
458
- cur.raise e
459
- ensure
460
- @pipe.close rescue nil
461
- end
462
- end
463
- #--}}}
464
- end
465
-
466
- def start
467
- #--{{{
468
- if Process::pid == @pid
469
- @r.close
470
- @pipe = @w
471
- @pipe.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
472
- parent_start
473
- else
474
- @w.close
475
- @pipe = @r
476
- child_start
477
- end
478
- @beating = true
479
- #--}}}
480
- end
481
- def parent_start
482
- #--{{{
483
- @whoami = 'parent'
484
- @thread =
485
- Thread::new(Thread::current) do |cur|
486
- begin
487
- sleep
488
- rescue => e
489
- cur.raise e
490
- ensure
491
- @pipe.close rescue nil
492
- end
493
- end
494
- #--}}}
495
- end
496
- def child_start
497
- #--{{{
498
- @whoami = 'child'
499
- @pid = Process::pid
500
- @ppid = Process::ppid
501
- @thread =
502
- Thread::new(Thread::current) do |cur|
503
- begin
504
- trace{ "child reading..." }
505
- @pipe.read
506
- trace{ "child read." }
507
- trace{ "child exiting." }
508
- exit
509
- rescue => e
510
- cur.raise e
511
- ensure
512
- @pipe.close rescue nil
513
- end
514
- end
515
- #--}}}
516
- end
517
- def stop
518
- #--{{{
519
- raise "not beating" unless @beating
520
- @thread.kill
521
- @pipe.close rescue nil
522
- @beating = false
523
- #--}}}
524
- end
525
- def trace
526
- #--{{{
527
- STDERR.puts(yield) if @debug and STDERR.tty?
528
- #--}}}
529
- end
530
- #--}}}
531
- end # class Heartbeat
532
- #--}}}
533
- end # class Slave