slave 1.2.2 → 1.3.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 +16 -11
- data/Rakefile +14 -8
- data/lib/slave.rb +66 -81
- data/{README.tmpl → readme.erb} +4 -1
- data/samples/a.rb +2 -2
- data/slave.gemspec +42 -0
- metadata +8 -31
- data/doc/classes/Slave.html +0 -997
- data/doc/classes/Slave/LifeLine.html +0 -419
- data/doc/classes/Slave/ThreadSafe.html +0 -292
- data/doc/classes/Slave/ThreadSafeHash.html +0 -158
- data/doc/classes/o.html +0 -117
- data/doc/created.rid +0 -1
- data/doc/dot/f_0.dot +0 -14
- data/doc/dot/f_0.jpg +0 -0
- data/doc/dot/f_1.dot +0 -29
- data/doc/dot/f_1.jpg +0 -0
- data/doc/files/README.html +0 -411
- data/doc/files/lib/slave_rb.html +0 -120
- data/doc/fr_class_index.html +0 -30
- data/doc/fr_file_index.html +0 -28
- data/doc/fr_method_index.html +0 -56
- data/doc/index.html +0 -24
- data/doc/rdoc-style.css +0 -208
- data/gemspec.rb +0 -23
- data/gen_readme.rb +0 -32
- data/install.rb +0 -206
- data/rdoc.cmd +0 -1
- data/test.old/slave.rb +0 -21
data/README
CHANGED
@@ -38,6 +38,9 @@ URIS
|
|
38
38
|
http://codeforpeople.com/lib/ruby/slave
|
39
39
|
|
40
40
|
HISTORY
|
41
|
+
1.3.0:
|
42
|
+
- fixes for 1.9.2 (undef object_id)
|
43
|
+
- fixes for osx (too long socket names)
|
41
44
|
|
42
45
|
1.2.1:
|
43
46
|
- jruby/ThreadSafe patches from skaar and ez. using slave.rb with jruby,
|
@@ -105,18 +108,19 @@ HISTORY
|
|
105
108
|
|
106
109
|
SAMPLES
|
107
110
|
|
111
|
+
|
108
112
|
<========< samples/a.rb >========>
|
109
113
|
|
110
114
|
~ > cat samples/a.rb
|
111
115
|
|
112
116
|
require 'slave'
|
113
|
-
|
117
|
+
|
114
118
|
# simple usage is simply to stand up a server object as a slave. you do not
|
115
119
|
# need to wait for the server, join it, etc. it will die when the parent
|
116
120
|
# process dies - even under 'kill -9' conditions
|
117
121
|
#
|
118
122
|
class Server
|
119
|
-
def add_two
|
123
|
+
def add_two(n)
|
120
124
|
n + 2
|
121
125
|
end
|
122
126
|
end
|
@@ -163,9 +167,9 @@ SAMPLES
|
|
163
167
|
~ > ruby samples/b.rb
|
164
168
|
|
165
169
|
:postgresql
|
166
|
-
samples/b.rb:22: undefined method `typo' for #<Server:
|
167
|
-
from ./lib/slave.rb:
|
168
|
-
from ./lib/slave.rb:
|
170
|
+
samples/b.rb:22: undefined method `typo' for #<Server:0x10030aa60> (NoMethodError)
|
171
|
+
from ./lib/slave.rb:367:in `[]'
|
172
|
+
from ./lib/slave.rb:367:in `initialize'
|
169
173
|
from samples/b.rb:22:in `new'
|
170
174
|
from samples/b.rb:22
|
171
175
|
|
@@ -198,11 +202,11 @@ SAMPLES
|
|
198
202
|
|
199
203
|
~ > ruby samples/c.rb
|
200
204
|
|
201
|
-
|
202
|
-
|
205
|
+
48871
|
206
|
+
48872
|
203
207
|
samples/c.rb:21: undefined local variable or method `fubar' for main:Object (NameError)
|
204
|
-
from ./lib/slave.rb:
|
205
|
-
from ./lib/slave.rb:
|
208
|
+
from ./lib/slave.rb:359:in `call'
|
209
|
+
from ./lib/slave.rb:359:in `initialize'
|
206
210
|
from samples/c.rb:21:in `new'
|
207
211
|
from samples/c.rb:21
|
208
212
|
|
@@ -297,6 +301,7 @@ SAMPLES
|
|
297
301
|
|
298
302
|
~ > ruby samples/g.rb
|
299
303
|
|
300
|
-
{"that"=>
|
301
|
-
{"that"=>[
|
304
|
+
{"that"=>48890, "this"=>48889}
|
305
|
+
{"that"=>[48891, Mon Oct 10 08:21:47 -0600 2011], "this"=>[48889, Mon Oct 10 08:21:45 -0600 2011]}
|
306
|
+
|
302
307
|
|
data/Rakefile
CHANGED
@@ -187,6 +187,10 @@ task :readme do
|
|
187
187
|
samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n"
|
188
188
|
end
|
189
189
|
|
190
|
+
@lib = lib
|
191
|
+
@version = version
|
192
|
+
@samples = samples
|
193
|
+
|
190
194
|
template =
|
191
195
|
if test(?e, 'readme.erb')
|
192
196
|
Template{ IO.read('readme.erb') }
|
@@ -304,19 +308,21 @@ BEGIN {
|
|
304
308
|
#
|
305
309
|
module Util
|
306
310
|
def indent(s, n = 2)
|
307
|
-
|
308
|
-
|
309
|
-
s
|
311
|
+
margin = ' ' * Integer(n)
|
312
|
+
unindent(s).gsub!(%r/^/, margin)
|
313
|
+
s
|
310
314
|
end
|
311
315
|
|
312
316
|
def unindent(s)
|
313
|
-
|
317
|
+
margin = nil
|
314
318
|
s.each_line do |line|
|
315
|
-
|
316
|
-
|
319
|
+
next if line =~ %r/^\s*$/
|
320
|
+
margin = line[%r/^\s*/] and break
|
321
|
+
end
|
322
|
+
s.gsub!(%r/^#{ margin }/, "") if margin
|
323
|
+
s
|
317
324
|
end
|
318
|
-
|
319
|
-
end
|
325
|
+
|
320
326
|
extend self
|
321
327
|
end
|
322
328
|
|
data/lib/slave.rb
CHANGED
@@ -8,7 +8,7 @@ require 'sync'
|
|
8
8
|
|
9
9
|
# TODO - lifeline need close-on-exec set in it!
|
10
10
|
|
11
|
-
|
11
|
+
|
12
12
|
# the Slave class encapsulates the work of setting up a drb server in another
|
13
13
|
# process running on localhost via unix domain sockets. the slave process is
|
14
14
|
# attached to it's parent via a LifeLine which is designed such that the slave
|
@@ -39,49 +39,48 @@ require 'sync'
|
|
39
39
|
# of the two 'b' is preferred.
|
40
40
|
#
|
41
41
|
class Slave
|
42
|
-
|
43
|
-
VERSION = '1.2.2'
|
42
|
+
VERSION = '1.3.0'
|
44
43
|
def self.version() VERSION end
|
45
|
-
|
44
|
+
|
46
45
|
# env config
|
47
46
|
#
|
48
47
|
DEFAULT_SOCKET_CREATION_ATTEMPTS = Integer(ENV['SLAVE_SOCKET_CREATION_ATTEMPTS'] || 42)
|
49
48
|
DEFAULT_DEBUG = (ENV['SLAVE_DEBUG'] ? true : false)
|
50
49
|
DEFAULT_THREADSAFE = (ENV['SLAVE_THREADSAFE'] ? true : false)
|
51
|
-
|
50
|
+
|
52
51
|
# class initialization
|
53
52
|
#
|
54
53
|
@socket_creation_attempts = DEFAULT_SOCKET_CREATION_ATTEMPTS
|
55
54
|
@debug = DEFAULT_DEBUG
|
56
55
|
@threadsafe = DEFAULT_THREADSAFE
|
57
|
-
|
56
|
+
|
58
57
|
# class methods
|
59
58
|
#
|
60
59
|
class << self
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
# defineds how many attempts will be made to create a temporary unix domain
|
61
|
+
# socket
|
62
|
+
#
|
64
63
|
attr :socket_creation_attempts, true
|
65
64
|
|
66
|
-
|
67
|
-
|
65
|
+
# if this is true and you are running from a terminal information is printed
|
66
|
+
# on STDERR
|
67
|
+
#
|
68
68
|
attr :debug, true
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
+
#
|
74
75
|
attr :threadsafe, true
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
# get a default value
|
78
|
+
#
|
79
|
+
def default(key)
|
79
80
|
send key
|
80
|
-
#--}}}
|
81
81
|
end
|
82
82
|
|
83
|
-
def getopts
|
84
|
-
#--{{{
|
83
|
+
def getopts(opts)
|
85
84
|
raise ArgumentError, opts.class unless
|
86
85
|
opts.respond_to?('has_key?') and opts.respond_to?('[]')
|
87
86
|
|
@@ -91,12 +90,11 @@ require 'sync'
|
|
91
90
|
key = keys.detect{|k| opts.has_key? k } and break opts[key]
|
92
91
|
defval
|
93
92
|
end
|
94
|
-
#--}}}
|
95
93
|
end
|
96
94
|
|
97
|
-
|
98
|
-
|
99
|
-
|
95
|
+
# just fork with out silly warnings
|
96
|
+
#
|
97
|
+
def fork(&b)
|
100
98
|
v = $VERBOSE
|
101
99
|
begin
|
102
100
|
$VERBOSE = nil
|
@@ -104,16 +102,14 @@ require 'sync'
|
|
104
102
|
ensure
|
105
103
|
$VERBOSE = v
|
106
104
|
end
|
107
|
-
#--}}}
|
108
105
|
end
|
109
|
-
#--}}}
|
110
106
|
end
|
111
107
|
|
112
|
-
|
108
|
+
|
113
109
|
# helper classes
|
114
110
|
#
|
115
111
|
|
116
|
-
|
112
|
+
|
117
113
|
# ThreadSafe is a delegate wrapper class used for implementing gross thread
|
118
114
|
# safety around existing objects. when an object is wrapped with this class
|
119
115
|
# as
|
@@ -125,37 +121,42 @@ require 'sync'
|
|
125
121
|
# 'threadsafe'/:threadsafe keyword to Slave#initialize
|
126
122
|
#
|
127
123
|
class ThreadSafe
|
128
|
-
|
129
|
-
|
130
|
-
def initialize
|
124
|
+
instance_methods.each{|m| undef_method m.to_sym unless m =~ %r/\A__|\Aobject_id\Z/}
|
125
|
+
|
126
|
+
def initialize(object)
|
131
127
|
@object = object
|
132
128
|
@sync = Sync.new
|
133
129
|
end
|
130
|
+
|
134
131
|
def ex
|
135
132
|
@sync.synchronize{ yield }
|
136
133
|
end
|
134
|
+
|
137
135
|
def method_missing m, *a, &b
|
138
136
|
ex{ @object.send m, *a, &b }
|
139
137
|
end
|
138
|
+
|
140
139
|
def respond_to? *a, &b
|
141
140
|
ex{ @object.respond_to? *a, &b }
|
142
141
|
end
|
142
|
+
|
143
143
|
def inspect
|
144
144
|
ex{ @object.inspect }
|
145
145
|
end
|
146
|
+
|
146
147
|
def class
|
147
148
|
ex{ @object.class }
|
148
149
|
end
|
149
|
-
#--}}}
|
150
150
|
end
|
151
|
-
|
151
|
+
|
152
|
+
|
152
153
|
# a simple thread safe hash used to map object_id to a set of file
|
153
154
|
# descriptors in the LifeLine class. see LifeLine::FDS
|
154
155
|
#
|
155
156
|
class ThreadSafeHash < Hash
|
156
157
|
def self.new(*a, &b) ThreadSafe.new(super) end
|
157
158
|
end
|
158
|
-
|
159
|
+
|
159
160
|
# the LifeLine class is used to communitacte between child and parent
|
160
161
|
# processes and to prevent child processes from ever becoming zombies or
|
161
162
|
# otherwise abandoned by their parents. the basic concept is that a socket
|
@@ -165,11 +166,10 @@ require 'sync'
|
|
165
166
|
# previous Slave versions.
|
166
167
|
#
|
167
168
|
class LifeLine
|
168
|
-
#--{{{
|
169
169
|
FDS = ThreadSafeHash.new
|
170
170
|
|
171
171
|
def initialize
|
172
|
-
@pair = Socket.pair
|
172
|
+
@pair = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
|
173
173
|
@owner = Process.pid
|
174
174
|
@pid = nil
|
175
175
|
@socket = nil
|
@@ -185,7 +185,7 @@ require 'sync'
|
|
185
185
|
Process.pid == @owner
|
186
186
|
end
|
187
187
|
|
188
|
-
def throw
|
188
|
+
def throw(*ignored)
|
189
189
|
raise unless owner?
|
190
190
|
@pair[-1].close
|
191
191
|
@pair[-1] = nil
|
@@ -194,7 +194,7 @@ require 'sync'
|
|
194
194
|
@socket.sync = true
|
195
195
|
end
|
196
196
|
|
197
|
-
def catch
|
197
|
+
def catch(*ignored)
|
198
198
|
raise if owner?
|
199
199
|
@pair[0].close
|
200
200
|
@pair[0] = nil
|
@@ -264,10 +264,9 @@ require 'sync'
|
|
264
264
|
def cling &b
|
265
265
|
on_cut{ begin; b.call if b; ensure; Kernel.exit; end }.join
|
266
266
|
end
|
267
|
-
#--}}}
|
268
267
|
end
|
269
268
|
|
270
|
-
|
269
|
+
|
271
270
|
# attrs
|
272
271
|
#
|
273
272
|
attr :obj
|
@@ -283,7 +282,7 @@ require 'sync'
|
|
283
282
|
attr :ppid
|
284
283
|
attr :uri
|
285
284
|
attr :socket
|
286
|
-
|
285
|
+
|
287
286
|
# sets up a child process serving any object as a DRb server running locally
|
288
287
|
# on unix domain sockets. the child process has a LifeLine established
|
289
288
|
# between it and the parent, making it impossible for the child to outlive
|
@@ -316,7 +315,6 @@ require 'sync'
|
|
316
315
|
# threadsafe : wrap the slave object with ThreadSafe to implement gross thread safety
|
317
316
|
#
|
318
317
|
def initialize opts = {}, &block
|
319
|
-
#--{{{
|
320
318
|
getopt = getopts opts
|
321
319
|
|
322
320
|
@obj = getopt['object']
|
@@ -345,7 +343,7 @@ require 'sync'
|
|
345
343
|
@object = o
|
346
344
|
end
|
347
345
|
|
348
|
-
|
346
|
+
|
349
347
|
# child
|
350
348
|
#
|
351
349
|
unless((@pid = Slave::fork))
|
@@ -382,16 +380,23 @@ require 'sync'
|
|
382
380
|
@object = ThreadSafe.new @object
|
383
381
|
end
|
384
382
|
|
385
|
-
@ppid, @pid = Process
|
383
|
+
@ppid, @pid = Process.ppid, Process.pid
|
386
384
|
@socket = nil
|
387
385
|
@uri = nil
|
388
386
|
|
389
|
-
tmpdir,
|
387
|
+
tmpdir = test(?d, '/tmp') ? '/tmp' : Dir.tmpdir
|
388
|
+
basename = File.basename(@psname)
|
390
389
|
|
391
390
|
@socket_creation_attempts.times do |attempt|
|
392
391
|
se = nil
|
393
392
|
begin
|
394
|
-
s =
|
393
|
+
s =
|
394
|
+
if attempt > 0
|
395
|
+
File.join(tmpdir, "#{ basename }_#{ attempt }")
|
396
|
+
else
|
397
|
+
File.join(tmpdir, "#{ basename }")
|
398
|
+
end
|
399
|
+
raise("#{ s } is too long!") if s.size > 103
|
395
400
|
u = "drbunix://#{ s }"
|
396
401
|
DRb::start_service u, @object
|
397
402
|
@socket = s
|
@@ -411,7 +416,7 @@ require 'sync'
|
|
411
416
|
exit
|
412
417
|
end
|
413
418
|
|
414
|
-
@lifeline.puts
|
419
|
+
@lifeline.puts(@socket)
|
415
420
|
@lifeline.cling
|
416
421
|
else
|
417
422
|
@lifeline.release
|
@@ -424,7 +429,7 @@ require 'sync'
|
|
424
429
|
status = e.respond_to?('status') ? e.status : 1
|
425
430
|
exit(status)
|
426
431
|
end
|
427
|
-
|
432
|
+
|
428
433
|
# parent
|
429
434
|
#
|
430
435
|
else
|
@@ -446,10 +451,10 @@ require 'sync'
|
|
446
451
|
Kernel.at_exit{ FileUtils::rm_f @socket }
|
447
452
|
@uri = "drbunix://#{ socket }"
|
448
453
|
trace{ "parent - uri <#{ @uri }>" }
|
449
|
-
|
454
|
+
|
450
455
|
# starting drb on localhost avoids dns lookups!
|
451
456
|
#
|
452
|
-
DRb::start_service('druby://
|
457
|
+
DRb::start_service('druby://0.0.0.0:0', nil) unless DRb::thread
|
453
458
|
@object = DRbObject::new nil, @uri
|
454
459
|
if @object.respond_to? '__slave_object_failure__'
|
455
460
|
c, m, bt = Marshal.load @object.__slave_object_failure__
|
@@ -462,15 +467,13 @@ require 'sync'
|
|
462
467
|
raise "failed to find slave socket <#{ @socket }>"
|
463
468
|
end
|
464
469
|
end
|
465
|
-
#--}}}
|
466
470
|
end
|
467
|
-
|
471
|
+
|
468
472
|
# starts a thread to collect the child status and sets up at_exit handler to
|
469
473
|
# prevent zombies. the at_exit handler is canceled if the thread is able to
|
470
474
|
# collect the status
|
471
475
|
#
|
472
476
|
def detach
|
473
|
-
#--{{{
|
474
477
|
reap = lambda do |cid|
|
475
478
|
begin
|
476
479
|
@status = Process::waitpid2(cid).last
|
@@ -493,81 +496,66 @@ require 'sync'
|
|
493
496
|
reap = lambda{|cid| 'no-op' }
|
494
497
|
end
|
495
498
|
end
|
496
|
-
#--}}}
|
497
499
|
end
|
498
|
-
|
500
|
+
|
499
501
|
# wait for slave to finish. if the keyword 'non_block'=>true is given a
|
500
502
|
# thread is returned to do the waiting in an async fashion. eg
|
501
503
|
#
|
502
504
|
# thread = slave.wait(:non_block=>true){|value| "background <#{ value }>"}
|
503
505
|
#
|
504
506
|
def wait opts = {}, &b
|
505
|
-
#--{{{
|
506
507
|
b ||= lambda{|exit_status|}
|
507
508
|
non_block = getopts(opts)['non_block']
|
508
509
|
non_block ? Thread.new{ b[ @waiter.value ] } : b[ @waiter.value ]
|
509
|
-
#--}}}
|
510
510
|
end
|
511
511
|
alias :wait2 :wait
|
512
|
-
|
512
|
+
|
513
513
|
# cuts the lifeline and kills the child process - give the key 'quiet' to
|
514
514
|
# ignore errors shutting down, including having already shutdown
|
515
515
|
#
|
516
516
|
def shutdown opts = {}
|
517
|
-
#--{{{
|
518
517
|
quiet = getopts(opts)['quiet']
|
519
518
|
raise "already shutdown" if @shutdown unless quiet
|
520
519
|
begin; Process::kill 'SIGUSR2', @pid; rescue Exception => e; end
|
521
520
|
begin; @lifeline.cut; rescue Exception; end
|
522
521
|
raise e if e unless quiet
|
523
522
|
@shutdown = true
|
524
|
-
#--}}}
|
525
523
|
end
|
526
|
-
|
524
|
+
|
527
525
|
# true
|
528
526
|
#
|
529
527
|
def shutdown?
|
530
|
-
#--{{{
|
531
528
|
@shutdown
|
532
|
-
#--}}}
|
533
529
|
end
|
534
|
-
|
530
|
+
|
535
531
|
# generate a default name to appear in ps/top
|
536
532
|
#
|
537
533
|
def gen_psname obj
|
538
|
-
|
539
|
-
"slave_#{ obj.class }_#{ obj.object_id }_#{ Process::ppid }_#{ Process::pid }".downcase.gsub(%r/\s+/,'_')
|
540
|
-
#--}}}
|
534
|
+
"slave_#{ obj.class }_#{ obj.object_id }_#{ Process.ppid }_#{ Process.pid }".downcase.gsub(%r/\s+/,'_')
|
541
535
|
end
|
542
|
-
|
536
|
+
|
543
537
|
# see docs for Slave.default
|
544
538
|
#
|
545
539
|
def default key
|
546
|
-
#--{{{
|
547
540
|
self.class.default key
|
548
|
-
#--}}}
|
549
541
|
end
|
550
|
-
|
542
|
+
|
551
543
|
# see docs for Slave.getopts
|
552
544
|
#
|
553
545
|
def getopts opts
|
554
|
-
#--{{{
|
555
546
|
self.class.getopts opts
|
556
|
-
#--}}}
|
557
547
|
end
|
558
|
-
|
548
|
+
|
559
549
|
# debugging output - ENV['SLAVE_DEBUG']=1 to enable
|
560
550
|
#
|
561
551
|
def trace
|
562
|
-
#--{{{
|
563
552
|
if @debug
|
564
553
|
STDERR.puts yield
|
565
554
|
STDERR.flush
|
566
555
|
end
|
567
|
-
#--}}}
|
568
556
|
end
|
569
557
|
|
570
|
-
|
558
|
+
|
571
559
|
# a simple convenience method which returns an *object* from another
|
572
560
|
# process. the object returned is the result of the supplied block. eg
|
573
561
|
#
|
@@ -584,7 +572,6 @@ require 'sync'
|
|
584
572
|
# object = thread.value
|
585
573
|
#
|
586
574
|
def self.object opts = {}, &b
|
587
|
-
#--{{{
|
588
575
|
async = opts.delete('async') || opts.delete(:async)
|
589
576
|
|
590
577
|
opts['object'] = opts[:object] = lambda(&b)
|
@@ -601,7 +588,5 @@ require 'sync'
|
|
601
588
|
end
|
602
589
|
|
603
590
|
async ? Thread.new{ value[slave] } : value[slave]
|
604
|
-
#--}}}
|
605
591
|
end
|
606
|
-
#--}}}
|
607
592
|
end # class Slave
|