slave 0.0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|