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/doc/classes/object.html
DELETED
@@ -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"> [RW] </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
|
data/doc/files/VERSION.html
DELETED
@@ -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
|