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.
- data/README +90 -17
- data/README.tmpl +29 -12
- data/doc/classes/Slave.html +430 -298
- data/doc/classes/Slave/LifeLine.html +406 -0
- data/doc/classes/Slave/ThreadSafe.html +284 -0
- data/doc/classes/Slave/ThreadSafeHash.html +151 -0
- data/doc/created.rid +1 -1
- data/doc/files/README.html +88 -18
- data/doc/files/lib/slave_rb.html +6 -2
- data/doc/fr_class_index.html +3 -1
- data/doc/fr_method_index.html +19 -11
- data/lib/slave-1.1.0.rb +623 -0
- data/lib/slave.rb +293 -203
- data/samples/a.rb +3 -1
- data/samples/f.rb +13 -0
- data/samples/g.rb +19 -0
- metadata +26 -30
- data/doc/classes/(@object = Object.new).html +0 -117
- data/doc/classes/(o = Object.new).html +0 -117
- data/doc/classes/@object.html +0 -117
- data/doc/classes/Slave/Heartbeat.html +0 -458
- data/doc/classes/object.html +0 -117
- data/doc/dot/f_2.dot +0 -29
- data/doc/dot/f_2.jpg +0 -0
- data/doc/files/VERSION.html +0 -107
- data/lib/slave-1.0.0.rb +0 -533
- data/slave-1.0.0.gem +0 -0
data/lib/slave.rb
CHANGED
@@ -3,16 +3,20 @@ require 'fileutils'
|
|
3
3
|
require 'tmpdir'
|
4
4
|
require 'tempfile'
|
5
5
|
require 'fcntl'
|
6
|
+
require 'socket'
|
7
|
+
require 'sync'
|
8
|
+
|
9
|
+
# TODO - lifeline need close-on-exec set in it!
|
6
10
|
|
7
11
|
#
|
8
12
|
# the Slave class encapsulates the work of setting up a drb server in another
|
9
|
-
# process running on localhost. the slave process is
|
10
|
-
# via a
|
11
|
-
# parent and become a zombie, even if the parent dies and
|
12
|
-
# by 'kill -9'. the concept and purpose of the Slave
|
13
|
-
# setup any server object in another process so easily
|
14
|
-
# multi-process, drb/ipc, based design is as easy, or easier,
|
15
|
-
# multi-threaded one. eg
|
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
|
16
20
|
#
|
17
21
|
# class Server
|
18
22
|
# def add_two n
|
@@ -36,23 +40,20 @@ require 'fcntl'
|
|
36
40
|
#
|
37
41
|
class Slave
|
38
42
|
#--{{{
|
39
|
-
VERSION = '1.
|
43
|
+
VERSION = '1.1.0'
|
40
44
|
def self.version() VERSION end
|
41
45
|
#
|
42
|
-
# config
|
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
|
43
53
|
#
|
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
54
|
@socket_creation_attempts = DEFAULT_SOCKET_CREATION_ATTEMPTS
|
54
|
-
@pulse_rate = DEFAULT_PULSE_RATE
|
55
55
|
@debug = DEFAULT_DEBUG
|
56
|
+
@threadsafe = DEFAULT_THREADSAFE
|
56
57
|
#
|
57
58
|
# class methods
|
58
59
|
#
|
@@ -62,13 +63,16 @@ require 'fcntl'
|
|
62
63
|
# socket
|
63
64
|
attr :socket_creation_attempts, true
|
64
65
|
|
65
|
-
# defined the rate of pinging in the Heartbeat object
|
66
|
-
attr :pulse_rate, true
|
67
|
-
|
68
66
|
# if this is true and you are running from a terminal information is printed
|
69
67
|
# on STDERR
|
70
68
|
attr :debug, true
|
71
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
|
+
|
72
76
|
# get a default value
|
73
77
|
def default key
|
74
78
|
#--{{{
|
@@ -105,13 +109,174 @@ require 'fcntl'
|
|
105
109
|
#--}}}
|
106
110
|
end
|
107
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
|
108
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
|
+
#
|
109
274
|
attr :obj
|
110
275
|
attr :socket_creation_attempts
|
111
|
-
attr :pulse_rate
|
112
276
|
attr :debug
|
113
277
|
attr :psname
|
114
278
|
attr :at_exit
|
279
|
+
attr :dumped
|
115
280
|
|
116
281
|
attr :shutdown
|
117
282
|
attr :status
|
@@ -120,10 +285,37 @@ require 'fcntl'
|
|
120
285
|
attr :ppid
|
121
286
|
attr :uri
|
122
287
|
attr :socket
|
123
|
-
|
124
288
|
#
|
125
|
-
#
|
126
|
-
#
|
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
|
127
319
|
#
|
128
320
|
def initialize opts = {}, &block
|
129
321
|
#--{{{
|
@@ -131,24 +323,22 @@ require 'fcntl'
|
|
131
323
|
|
132
324
|
@obj = getopt['object']
|
133
325
|
@socket_creation_attempts = getopt['socket_creation_attempts'] || default('socket_creation_attempts')
|
134
|
-
@pulse_rate = getopt['pulse_rate'] || default('pulse_rate')
|
135
326
|
@debug = getopt['debug'] || default('debug')
|
136
327
|
@psname = getopt['psname']
|
137
328
|
@at_exit = getopt['at_exit']
|
138
329
|
@dumped = getopt['dumped']
|
330
|
+
@threadsafe = getopt['threadsafe'] || default('threadsafe')
|
139
331
|
|
140
|
-
raise ArgumentError, 'no slave object!' if
|
332
|
+
raise ArgumentError, 'no slave object or slave object block provided!' if
|
141
333
|
@obj.nil? and block.nil?
|
142
334
|
|
143
335
|
@shutdown = false
|
144
336
|
@waiter = @status = nil
|
145
|
-
|
146
|
-
@heartbeat = Heartbeat::new @pulse_rate, @debug
|
147
|
-
@r, @w = IO::pipe
|
148
|
-
@r2, @w2 = IO::pipe
|
337
|
+
@lifeline = LifeLine.new
|
149
338
|
|
150
339
|
# weird syntax because dot/rdoc chokes on this!?!?
|
151
340
|
init_failure = lambda do |e|
|
341
|
+
trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
|
152
342
|
o = Object.new
|
153
343
|
class << o
|
154
344
|
attr_accessor '__slave_object_failure__'
|
@@ -164,6 +354,7 @@ require 'fcntl'
|
|
164
354
|
e = nil
|
165
355
|
begin
|
166
356
|
Kernel.at_exit{ Kernel.exit! }
|
357
|
+
@lifeline.catch
|
167
358
|
|
168
359
|
if @obj
|
169
360
|
@object = @obj
|
@@ -184,14 +375,16 @@ require 'fcntl'
|
|
184
375
|
end
|
185
376
|
|
186
377
|
$0 = (@psname ||= gen_psname(@object))
|
378
|
+
|
187
379
|
unless @dumped or @object.respond_to?('__slave_object_failure__')
|
188
380
|
@object.extend DRbUndumped
|
189
381
|
end
|
190
382
|
|
191
|
-
|
383
|
+
if @threadsafe
|
384
|
+
@object = ThreadSafe.new @object
|
385
|
+
end
|
192
386
|
|
193
|
-
@
|
194
|
-
@r2.close
|
387
|
+
@ppid, @pid = Process::ppid, Process::pid
|
195
388
|
@socket = nil
|
196
389
|
@uri = nil
|
197
390
|
|
@@ -200,7 +393,7 @@ require 'fcntl'
|
|
200
393
|
@socket_creation_attempts.times do |attempt|
|
201
394
|
se = nil
|
202
395
|
begin
|
203
|
-
s = File::join(tmpdir, "#{ basename }_#{ attempt }")
|
396
|
+
s = File::join(tmpdir, "#{ basename }_#{ attempt }_#{ rand }")
|
204
397
|
u = "drbunix://#{ s }"
|
205
398
|
DRb::start_service u, @object
|
206
399
|
@socket = s
|
@@ -214,20 +407,18 @@ require 'fcntl'
|
|
214
407
|
end
|
215
408
|
|
216
409
|
if @socket and @uri
|
217
|
-
@heartbeat.start
|
218
|
-
|
219
410
|
trap('SIGUSR2') do
|
220
|
-
# @heartbeat.stop rescue nil
|
221
411
|
DBb::thread.kill rescue nil
|
222
412
|
FileUtils::rm_f @socket rescue nil
|
223
413
|
exit
|
224
414
|
end
|
225
415
|
|
226
|
-
@
|
227
|
-
@
|
228
|
-
DRb::thread.join
|
416
|
+
@lifeline.puts @socket
|
417
|
+
@lifeline.cling
|
229
418
|
else
|
230
|
-
@
|
419
|
+
@lifeline.release
|
420
|
+
warn "slave(#{ $$ }) could not create socket!"
|
421
|
+
exit
|
231
422
|
end
|
232
423
|
rescue Exception => e
|
233
424
|
trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
|
@@ -240,24 +431,16 @@ require 'fcntl'
|
|
240
431
|
#
|
241
432
|
else
|
242
433
|
detach
|
243
|
-
@
|
244
|
-
@w2.close
|
245
|
-
@socket = @r.read
|
246
|
-
@r.close
|
434
|
+
@lifeline.throw
|
247
435
|
|
436
|
+
buf = @lifeline.gets
|
437
|
+
raise "failed to find slave socket" if buf.nil? or buf.strip.empty?
|
438
|
+
@socket = buf.strip
|
248
439
|
trace{ "parent - socket <#{ @socket }>" }
|
249
440
|
|
250
441
|
if @at_exit
|
251
|
-
@at_exit_thread =
|
252
|
-
|
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
|
442
|
+
@at_exit_thread = @lifeline.on_cut{
|
443
|
+
@at_exit.respond_to?('call') ? @at_exit.call(self) : send(@at_exit.to_s, self)
|
261
444
|
}
|
262
445
|
end
|
263
446
|
|
@@ -265,7 +448,6 @@ require 'fcntl'
|
|
265
448
|
Kernel.at_exit{ FileUtils::rm_f @socket }
|
266
449
|
@uri = "drbunix://#{ socket }"
|
267
450
|
trace{ "parent - uri <#{ @uri }>" }
|
268
|
-
@heartbeat.start
|
269
451
|
#
|
270
452
|
# starting drb on localhost avoids dns lookups!
|
271
453
|
#
|
@@ -274,6 +456,7 @@ require 'fcntl'
|
|
274
456
|
if @object.respond_to? '__slave_object_failure__'
|
275
457
|
c, m, bt = Marshal.load @object.__slave_object_failure__
|
276
458
|
(e = c.new(m)).set_backtrace bt
|
459
|
+
trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
|
277
460
|
raise e
|
278
461
|
end
|
279
462
|
@psname ||= gen_psname(@object)
|
@@ -329,16 +512,16 @@ require 'fcntl'
|
|
329
512
|
end
|
330
513
|
alias :wait2 :wait
|
331
514
|
#
|
332
|
-
#
|
333
|
-
#
|
515
|
+
# cuts the lifeline and kills the child process - give the key 'quiet' to
|
516
|
+
# ignore errors shutting down, including having already shutdown
|
334
517
|
#
|
335
518
|
def shutdown opts = {}
|
336
519
|
#--{{{
|
337
520
|
quiet = getopts(opts)['quiet']
|
338
521
|
raise "already shutdown" if @shutdown unless quiet
|
339
|
-
|
340
|
-
@
|
341
|
-
|
522
|
+
begin; Process::kill 'SIGUSR2', @pid; rescue Exception => e; end
|
523
|
+
begin; @lifeline.cut; rescue Exception; end
|
524
|
+
raise e if e unless quiet
|
342
525
|
@shutdown = true
|
343
526
|
#--}}}
|
344
527
|
end
|
@@ -355,7 +538,7 @@ require 'fcntl'
|
|
355
538
|
#
|
356
539
|
def gen_psname obj
|
357
540
|
#--{{{
|
358
|
-
"#{ obj.class }_#{ obj.object_id }_#{ Process::ppid }_#{ Process::pid }".downcase.gsub(%r/\s+/,'_')
|
541
|
+
"slave_#{ obj.class }_#{ obj.object_id }_#{ Process::ppid }_#{ Process::pid }".downcase.gsub(%r/\s+/,'_')
|
359
542
|
#--}}}
|
360
543
|
end
|
361
544
|
#
|
@@ -379,155 +562,62 @@ require 'fcntl'
|
|
379
562
|
#
|
380
563
|
def trace
|
381
564
|
#--{{{
|
382
|
-
|
565
|
+
if @debug
|
566
|
+
STDERR.puts yield
|
567
|
+
STDERR.flush
|
568
|
+
end
|
383
569
|
#--}}}
|
384
570
|
end
|
571
|
+
|
385
572
|
#
|
386
|
-
#
|
387
|
-
#
|
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.
|
573
|
+
# a simple convenience method which returns an *object* from another
|
574
|
+
# process. the object returned is the result of the supplied block. eg
|
391
575
|
#
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
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
|
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
|
425
589
|
#--{{{
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
cur.raise e
|
437
|
-
ensure
|
438
|
-
@pipe.close rescue nil
|
439
|
-
end
|
440
|
-
end
|
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
|
441
600
|
#--}}}
|
442
|
-
|
443
|
-
|
601
|
+
end
|
602
|
+
def self.object opts = {}, &b
|
444
603
|
#--{{{
|
445
|
-
|
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
|
604
|
+
async = opts.delete('async') || opts.delete(:async)
|
465
605
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
child_start
|
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
|
477
616
|
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
617
|
end
|
618
|
+
|
619
|
+
async ? Thread.new{ value[slave] } : value[slave]
|
530
620
|
#--}}}
|
531
|
-
end
|
621
|
+
end
|
532
622
|
#--}}}
|
533
623
|
end # class Slave
|