slave 0.0.1 → 0.2.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 +66 -29
- data/install.rb +5 -0
- data/lib/{slave-0.0.1.rb → slave-0.2.0.rb} +84 -62
- data/lib/slave.rb +9 -4
- metadata +3 -5
- data/HISTORY +0 -16
- data/slave-0.0.1.gem +0 -0
data/README
CHANGED
@@ -1,42 +1,79 @@
|
|
1
|
-
|
2
|
-
object as the server. the process is detached so it is not required (nor
|
3
|
-
possible) to wait on the child pid. a Heartbeat is set up between the parent
|
4
|
-
and child processes so that the child will exit of the parent exits for any
|
5
|
-
reason - preventing orphaned slaves from running indefinitely. the purpose of
|
6
|
-
Slaves is to be able to easily set up a collection of objects communicating
|
7
|
-
via drb protocols instead of having to use IPC.
|
1
|
+
SYNOPSIS
|
8
2
|
|
9
|
-
|
3
|
+
the Slave class forks a process and starts a drb server in the child using
|
4
|
+
any object as the server. the process is detached so it is not required
|
5
|
+
(nor possible) to wait on the child pid. a Heartbeat is set up between the
|
6
|
+
parent and child processes so that the child will exit of the parent exits
|
7
|
+
for any reason - preventing orphaned slaves from running indefinitely. the
|
8
|
+
purpose of Slaves is to be able to easily set up a collection of objects
|
9
|
+
communicating via drb protocols instead of having to use IPC.
|
10
10
|
|
11
|
-
|
11
|
+
typical usage:
|
12
12
|
|
13
|
-
|
13
|
+
obj = AnyClass::new
|
14
14
|
|
15
|
-
|
16
|
-
p slave.uri # uri of the drb object
|
17
|
-
p slave.socket # unix domain socket path for drb object
|
18
|
-
p slave.psname # title shown in ps/top
|
15
|
+
slave = Slave::new obj
|
19
16
|
|
20
|
-
|
17
|
+
p slave.object # handle on drb object
|
18
|
+
p slave.uri # uri of the drb object
|
19
|
+
p slave.socket # unix domain socket path for drb object
|
20
|
+
p slave.psname # title shown in ps/top
|
21
21
|
|
22
|
-
|
22
|
+
other usage:
|
23
23
|
|
24
|
-
|
24
|
+
set the pulse_rate used for the Heartbeat
|
25
25
|
|
26
|
-
|
26
|
+
slave = Slave::new MyClass::new, 'pulse_rate' => 10
|
27
27
|
|
28
|
-
|
29
|
-
slave = Slave::new MyClass::new
|
28
|
+
same
|
30
29
|
|
31
|
-
|
30
|
+
Slave::pulse_rate = 10
|
31
|
+
slave = Slave::new MyClass::new
|
32
32
|
|
33
|
-
|
34
|
-
slave = Slave::new MyClass::new
|
33
|
+
same
|
35
34
|
|
36
|
-
|
37
|
-
|
35
|
+
ENV['SLAVE_PULSE_RATE'] = 10
|
36
|
+
slave = Slave::new MyClass::new
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
to avoid having a copy of the object in both the parent and child use the
|
39
|
+
block form
|
40
|
+
|
41
|
+
slave = Slave::new{ Server::new } # copy only in child!
|
42
|
+
server = slave.object
|
43
|
+
|
44
|
+
if both an object AND a block are passed the object is passed to the block
|
45
|
+
in the child process
|
46
|
+
|
47
|
+
slave = Slave::new(Server::new){|server| p 'in child!' }
|
48
|
+
|
49
|
+
slaves may be configured via the environment, the Slave class, or via the
|
50
|
+
ctor for object itself. attributes which may be configured include
|
51
|
+
|
52
|
+
* socket_creation_attempts
|
53
|
+
* pulse_rate
|
54
|
+
* psname
|
55
|
+
* debug
|
56
|
+
|
57
|
+
HISTORY
|
58
|
+
|
59
|
+
0.2.0:
|
60
|
+
incorporated joel vanderWerf's patch such that, if no object is passed the
|
61
|
+
block is used to create one ONLY in the child. this avoids having a copy
|
62
|
+
in both parent and child is that needs to be avoided due to, for instance,
|
63
|
+
resource consumption.
|
64
|
+
|
65
|
+
|
66
|
+
0.0.1:
|
67
|
+
- patch from Logan Capaldo adds block form to slave new, block is run in the
|
68
|
+
child
|
69
|
+
|
70
|
+
- added a few more samples/*
|
71
|
+
|
72
|
+
- added Slave#wait
|
73
|
+
|
74
|
+
- added status information to slaves
|
75
|
+
|
76
|
+
- added close-on-exec flag to pipes in parent process
|
77
|
+
|
78
|
+
0.0.0:
|
79
|
+
- initial version
|
data/install.rb
CHANGED
@@ -156,6 +156,7 @@ usage = <<-usage
|
|
156
156
|
-b, --bindir <bindir>
|
157
157
|
-r, --ruby <ruby>
|
158
158
|
-n, --no_linkify
|
159
|
+
-s, --sudo
|
159
160
|
-h, --help
|
160
161
|
usage
|
161
162
|
|
@@ -172,6 +173,8 @@ begin
|
|
172
173
|
$ruby = ARGV.req_arg
|
173
174
|
when '-n', '--no_linkify'
|
174
175
|
no_linkify = true
|
176
|
+
when '-s', '--sudo'
|
177
|
+
sudo = 'sudo'
|
175
178
|
when '-h', '--help'
|
176
179
|
help = true
|
177
180
|
else
|
@@ -193,6 +196,8 @@ unless no_linkify
|
|
193
196
|
linked = linkify('lib') + linkify('bin')
|
194
197
|
end
|
195
198
|
|
199
|
+
system "#{ $ruby } extconf.rb && make && #{ sudo } make install" if test(?s, 'extconf.rb')
|
200
|
+
|
196
201
|
install_rb(LIBDIR, libdir, LIBDIR_MODE)
|
197
202
|
install_rb(BINDIR, bindir, BINDIR_MODE, bin=true)
|
198
203
|
|
@@ -4,41 +4,33 @@ require 'tmpdir'
|
|
4
4
|
require 'tempfile'
|
5
5
|
require 'fcntl'
|
6
6
|
|
7
|
-
class Pipe
|
8
|
-
attr 'r'
|
9
|
-
attr 'w'
|
10
|
-
def initialize
|
11
|
-
@r, @w = IO.pipe
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class Lifeline
|
16
|
-
class Error < ::StandardError
|
17
|
-
def initialize
|
18
|
-
end
|
19
|
-
end
|
20
|
-
def initialize
|
21
|
-
@pid = Process.pid
|
22
|
-
@pipe = Pipe.new
|
23
|
-
end
|
24
|
-
def throw
|
25
|
-
end
|
26
|
-
def cling
|
27
|
-
@w.close
|
28
|
-
Thread.new(@r, Thread.current) do |r, t|
|
29
|
-
r.read rescue t.raise($!)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
7
|
#
|
35
8
|
# the Slave class encapsulates the work of setting up a drb server in another
|
36
|
-
# process.
|
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 Server.new
|
24
|
+
# server = slave.object
|
25
|
+
#
|
26
|
+
# p server.add_two(40) #=> 42
|
37
27
|
#
|
38
28
|
class Slave
|
39
29
|
#--{{{
|
40
|
-
VERSION = '0.0
|
41
|
-
|
30
|
+
VERSION = '0.2.0'
|
31
|
+
#
|
32
|
+
# config
|
33
|
+
#
|
42
34
|
DEFAULT_SOCKET_CREATION_ATTEMPTS =
|
43
35
|
Integer(ENV['SLAVE_SOCKET_CREATION_ATTEMPTS'] || 42)
|
44
36
|
|
@@ -51,7 +43,9 @@ require 'fcntl'
|
|
51
43
|
@socket_creation_attempts = DEFAULT_SOCKET_CREATION_ATTEMPTS
|
52
44
|
@pulse_rate = DEFAULT_PULSE_RATE
|
53
45
|
@debug = DEFAULT_DEBUG
|
54
|
-
|
46
|
+
#
|
47
|
+
# class methods
|
48
|
+
#
|
55
49
|
class << self
|
56
50
|
#--{{{
|
57
51
|
# defineds how many attempts will be made to create a temporary unix domain
|
@@ -97,13 +91,17 @@ require 'fcntl'
|
|
97
91
|
attr :pulse_rate
|
98
92
|
attr :socket
|
99
93
|
attr :debug
|
94
|
+
attr :status
|
100
95
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
96
|
+
#
|
97
|
+
# 'obj' can be any object and 'opts' may contain the keys
|
98
|
+
# 'socket_creation_attempts', 'pulse_rate', 'psname', or 'debug'
|
99
|
+
#
|
105
100
|
def initialize obj = nil, opts = {}, &block
|
106
101
|
#--{{{
|
102
|
+
raise ArgumentError, "no slave object!" if
|
103
|
+
obj.nil? and block.nil?
|
104
|
+
|
107
105
|
@obj = obj
|
108
106
|
|
109
107
|
@socket_creation_attempts = getval('socket_creation_attempts', opts)
|
@@ -116,13 +114,15 @@ require 'fcntl'
|
|
116
114
|
trace{ "psname <#{ @psname }>" }
|
117
115
|
|
118
116
|
@shutdown = false
|
117
|
+
@waiter = @status = nil
|
119
118
|
|
120
|
-
|
119
|
+
@heartbeat = Heartbeat::new @pulse_rate, @debug
|
121
120
|
@r, @w = IO::pipe
|
122
121
|
#
|
123
122
|
# child
|
124
123
|
#
|
125
124
|
unless((@pid = Slave::fork))
|
125
|
+
e = nil
|
126
126
|
begin
|
127
127
|
$0 = @psname
|
128
128
|
@pid = Process::pid
|
@@ -134,12 +134,14 @@ require 'fcntl'
|
|
134
134
|
|
135
135
|
tmpdir = Dir::tmpdir
|
136
136
|
basename = File::basename @psname
|
137
|
+
|
138
|
+
server = @obj || block.call
|
137
139
|
|
138
140
|
@socket_creation_attempts.times do |attempt|
|
139
141
|
begin
|
140
142
|
s = File::join(tmpdir, "#{ basename }_#{ attempt }")
|
141
143
|
u = "drbunix://#{ s }"
|
142
|
-
DRb::start_service u,
|
144
|
+
DRb::start_service u, server
|
143
145
|
@socket = s
|
144
146
|
@uri = u
|
145
147
|
trace{ "child - socket <#{ @socket }>" }
|
@@ -151,7 +153,7 @@ require 'fcntl'
|
|
151
153
|
end
|
152
154
|
|
153
155
|
if @socket and @uri
|
154
|
-
|
156
|
+
@heartbeat.start
|
155
157
|
@w.write @socket
|
156
158
|
@w.close
|
157
159
|
trap('SIGUSR2') do
|
@@ -160,21 +162,23 @@ require 'fcntl'
|
|
160
162
|
FileUtils::rm_f @socket rescue nil
|
161
163
|
exit!
|
162
164
|
end
|
163
|
-
block[obj] if block
|
165
|
+
block[obj] if block and obj
|
164
166
|
DRb::thread.join
|
165
167
|
else
|
166
168
|
@w.close
|
167
169
|
end
|
168
|
-
rescue => e
|
170
|
+
rescue Exception => e
|
169
171
|
trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
|
170
172
|
ensure
|
171
|
-
|
173
|
+
status = e.respond_to?('status') ? e.status : 1
|
174
|
+
exit!(status)
|
172
175
|
end
|
173
176
|
#
|
174
177
|
# parent
|
175
178
|
#
|
176
179
|
else
|
177
|
-
Process::detach @pid
|
180
|
+
#Process::detach @pid
|
181
|
+
detach
|
178
182
|
@w.close
|
179
183
|
@socket = @r.read
|
180
184
|
@r.close
|
@@ -185,7 +189,7 @@ require 'fcntl'
|
|
185
189
|
at_exit{ FileUtils::rm_f @socket }
|
186
190
|
@uri = "drbunix://#{ socket }"
|
187
191
|
trace{ "parent - uri <#{ @uri }>" }
|
188
|
-
|
192
|
+
@heartbeat.start
|
189
193
|
#
|
190
194
|
# starting drb on localhost avoids dns lookups!
|
191
195
|
#
|
@@ -197,38 +201,56 @@ require 'fcntl'
|
|
197
201
|
end
|
198
202
|
#--}}}
|
199
203
|
end
|
200
|
-
|
201
|
-
|
202
|
-
|
204
|
+
#
|
205
|
+
# starts a thread to attempt collecting the child status
|
206
|
+
#
|
207
|
+
def detach
|
208
|
+
#--{{{
|
209
|
+
@waiter =
|
210
|
+
Thread.new{ @status = Process::waitpid2(@pid).last }
|
211
|
+
#--}}}
|
212
|
+
end
|
213
|
+
#
|
214
|
+
# wait for slave to finish
|
215
|
+
#
|
216
|
+
def wait
|
217
|
+
#--{{{
|
218
|
+
@waiter.value
|
219
|
+
#--}}}
|
220
|
+
end
|
221
|
+
alias :wait2 :wait
|
222
|
+
#
|
223
|
+
# stops the heartbeat thread and kills the child process
|
224
|
+
#
|
203
225
|
def shutdown
|
204
226
|
#--{{{
|
205
227
|
raise "already shutdown" if @shutdown
|
206
|
-
|
228
|
+
@heartbeat.stop rescue nil
|
207
229
|
Process::kill('SIGUSR2', @pid) rescue nil
|
208
230
|
Process::kill('SIGTERM', @pid) rescue nil
|
209
231
|
FileUtils::rm_f @socket
|
210
232
|
@shutdown = true
|
211
233
|
#--}}}
|
212
234
|
end
|
213
|
-
|
214
|
-
|
215
|
-
|
235
|
+
#
|
236
|
+
# generate a default name to appear in ps/top
|
237
|
+
#
|
216
238
|
def gen_psname obj
|
217
239
|
#--{{{
|
218
|
-
"#{ obj.class }_slave_of_#{ Process::pid }".downcase
|
240
|
+
"#{ obj.class }_slave_of_#{ Process::pid }".downcase.gsub(%r/\s+/,'_')
|
219
241
|
#--}}}
|
220
242
|
end
|
221
|
-
|
222
|
-
|
223
|
-
|
243
|
+
#
|
244
|
+
# see docs for Slave.getval
|
245
|
+
#
|
224
246
|
def getval key, opts = {}
|
225
247
|
#--{{{
|
226
248
|
self.class.getval key
|
227
249
|
#--}}}
|
228
250
|
end
|
229
|
-
|
230
|
-
|
231
|
-
|
251
|
+
#
|
252
|
+
# debugging output - ENV['SLAVE_DEBUG']=1 to enable
|
253
|
+
#
|
232
254
|
def trace
|
233
255
|
#--{{{
|
234
256
|
STDERR.puts(yield) if @debug and STDERR.tty?
|
@@ -236,11 +258,11 @@ require 'fcntl'
|
|
236
258
|
end
|
237
259
|
|
238
260
|
#
|
239
|
-
# the Heartbeat class is essentially wrapper over an IPC channel that sends
|
240
|
-
# ping on the channel indicating process health. if either end of the
|
241
|
-
# is detached the ping will fail and an error will be raised. in
|
242
|
-
# is ensured that Slave
|
243
|
-
# being alive.
|
261
|
+
# the Heartbeat class is essentially wrapper over an IPC channel that sends
|
262
|
+
# a ping on the channel indicating process health. if either end of the
|
263
|
+
# channel is detached the ping will fail and an error will be raised. in
|
264
|
+
# this way it is ensured that Slave objects cannot continue to live without
|
265
|
+
# their parent being alive.
|
244
266
|
#
|
245
267
|
class Heartbeat
|
246
268
|
#--{{{
|
data/lib/slave.rb
CHANGED
@@ -27,7 +27,7 @@ require 'fcntl'
|
|
27
27
|
#
|
28
28
|
class Slave
|
29
29
|
#--{{{
|
30
|
-
VERSION = '0.0
|
30
|
+
VERSION = '0.2.0'
|
31
31
|
#
|
32
32
|
# config
|
33
33
|
#
|
@@ -99,6 +99,9 @@ require 'fcntl'
|
|
99
99
|
#
|
100
100
|
def initialize obj = nil, opts = {}, &block
|
101
101
|
#--{{{
|
102
|
+
raise ArgumentError, "no slave object!" if
|
103
|
+
obj.nil? and block.nil?
|
104
|
+
|
102
105
|
@obj = obj
|
103
106
|
|
104
107
|
@socket_creation_attempts = getval('socket_creation_attempts', opts)
|
@@ -131,12 +134,14 @@ require 'fcntl'
|
|
131
134
|
|
132
135
|
tmpdir = Dir::tmpdir
|
133
136
|
basename = File::basename @psname
|
137
|
+
|
138
|
+
server = @obj || block.call
|
134
139
|
|
135
140
|
@socket_creation_attempts.times do |attempt|
|
136
141
|
begin
|
137
142
|
s = File::join(tmpdir, "#{ basename }_#{ attempt }")
|
138
143
|
u = "drbunix://#{ s }"
|
139
|
-
DRb::start_service u,
|
144
|
+
DRb::start_service u, server
|
140
145
|
@socket = s
|
141
146
|
@uri = u
|
142
147
|
trace{ "child - socket <#{ @socket }>" }
|
@@ -157,7 +162,7 @@ require 'fcntl'
|
|
157
162
|
FileUtils::rm_f @socket rescue nil
|
158
163
|
exit!
|
159
164
|
end
|
160
|
-
block[obj] if block
|
165
|
+
block[obj] if block and obj
|
161
166
|
DRb::thread.join
|
162
167
|
else
|
163
168
|
@w.close
|
@@ -232,7 +237,7 @@ require 'fcntl'
|
|
232
237
|
#
|
233
238
|
def gen_psname obj
|
234
239
|
#--{{{
|
235
|
-
"#{ obj.class }_slave_of_#{ Process::pid }".downcase.gsub(%r/\s
|
240
|
+
"#{ obj.class }_slave_of_#{ Process::pid }".downcase.gsub(%r/\s+/,'_')
|
236
241
|
#--}}}
|
237
242
|
end
|
238
243
|
#
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: slave
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0
|
7
|
-
date: 2006-
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2006-09-23 00:00:00.000000 -06:00
|
8
8
|
summary: slave
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,20 +29,18 @@ cert_chain:
|
|
29
29
|
authors:
|
30
30
|
- Ara T. Howard
|
31
31
|
files:
|
32
|
-
- HISTORY
|
33
32
|
- install.rb
|
34
33
|
- sample
|
35
34
|
- lib
|
36
35
|
- README
|
37
36
|
- rdoc.cmd
|
38
|
-
- slave-0.0.1.gem
|
39
37
|
- doc
|
40
38
|
- gemspec.rb
|
41
39
|
- sample/a.rb
|
42
40
|
- sample/b.rb
|
43
41
|
- sample/c.rb
|
44
|
-
- lib/slave-0.0.1.rb
|
45
42
|
- lib/slave.rb
|
43
|
+
- lib/slave-0.2.0.rb
|
46
44
|
- doc/index.html
|
47
45
|
- doc/files
|
48
46
|
- doc/fr_class_index.html
|
data/HISTORY
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
---
|
2
|
-
|
3
|
-
0.0.1:
|
4
|
-
- patch from Logan Capaldo adds block form to slave new, block is run in the
|
5
|
-
child
|
6
|
-
|
7
|
-
- added a few more samples/*
|
8
|
-
|
9
|
-
- added Slave#wait
|
10
|
-
|
11
|
-
- added status information to slaves
|
12
|
-
|
13
|
-
- added close-on-exec flag to pipes in parent process
|
14
|
-
|
15
|
-
0.0.0:
|
16
|
-
- initial version
|
data/slave-0.0.1.gem
DELETED
File without changes
|