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 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