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 CHANGED
@@ -1,42 +1,79 @@
1
- the Slave class forks a process and starts a drb server in the child using any
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
- typical usage:
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
- obj = AnyClass::new
11
+ typical usage:
12
12
 
13
- slave = Slave::new obj
13
+ obj = AnyClass::new
14
14
 
15
- p slave.object # handle on drb object
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
- other usage:
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
- set the pulse_rate used for the Heartbeat
22
+ other usage:
23
23
 
24
- slave = Slave::new MyClass::new, 'pulse_rate' => 10
24
+ set the pulse_rate used for the Heartbeat
25
25
 
26
- same
26
+ slave = Slave::new MyClass::new, 'pulse_rate' => 10
27
27
 
28
- Slave::pulse_rate = 10
29
- slave = Slave::new MyClass::new
28
+ same
30
29
 
31
- same
30
+ Slave::pulse_rate = 10
31
+ slave = Slave::new MyClass::new
32
32
 
33
- ENV['SLAVE_PULSE_RATE'] = 10
34
- slave = Slave::new MyClass::new
33
+ same
35
34
 
36
- slaves may be configured via the environment, the Slave class, or via the ctor
37
- for object itself. attributes which may be configured include
35
+ ENV['SLAVE_PULSE_RATE'] = 10
36
+ slave = Slave::new MyClass::new
38
37
 
39
- * socket_creation_attempts
40
- * pulse_rate
41
- * psname
42
- * debug
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.1'
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
- # 'obj' can be any object and 'opts' may contain the keys
103
- # 'socket_creation_attempts', 'pulse_rate', 'psname', or 'debug'
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
- # @heartbeat = Heartbeat::new @pulse_rate, @debug
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, obj
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
- # @heartbeat.start
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
- exit!
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
- # @heartbeat.start
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
- # stops the heartbeat thread and kills the child process
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
- # @heartbeat.stop rescue nil
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
- # generate a default name to appear in ps/top
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
- # see docs for Slave.getval
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
- # debugging output
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 a
240
- # ping on the channel indicating process health. if either end of the channel
241
- # is detached the ping will fail and an error will be raised. in this was it
242
- # is ensured that Slave object cannot continue to live without their parent
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.1'
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, obj
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.1
7
- date: 2006-06-08 00:00:00.000000 -06:00
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