slave 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/slave.rb ADDED
@@ -0,0 +1,292 @@
1
+ require 'drb/drb'
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+ require 'tempfile'
5
+
6
+ #
7
+ # the Slave class encapsulates the work of setting up a drb server in another
8
+ # process.
9
+ #
10
+ class Slave
11
+ #--{{{
12
+ DEFAULT_SOCKET_CREATION_ATTEMPTS =
13
+ Integer(ENV['SLAVE_SOCKET_CREATION_ATTEMPTS'] || 42)
14
+
15
+ DEFAULT_PULSE_RATE =
16
+ Float(ENV['SLAVE_PULSE_RATE'] || 8)
17
+
18
+ DEFAULT_DEBUG =
19
+ (ENV['SLAVE_DEBUG'] ? true : false)
20
+
21
+ @socket_creation_attempts = DEFAULT_SOCKET_CREATION_ATTEMPTS
22
+ @pulse_rate = DEFAULT_PULSE_RATE
23
+ @debug = DEFAULT_DEBUG
24
+
25
+ class << self
26
+ #--{{{
27
+ # defineds how many attempts will be made to create a temporary unix domain
28
+ # socket
29
+ attr :socket_creation_attempts, true
30
+
31
+ # defined the rate of pinging in the Heartbeat object
32
+ attr :pulse_rate, true
33
+
34
+ # if this is true and you are running from a terminal information is printed
35
+ # on STDERR
36
+ attr :debug, true
37
+
38
+ # look up a value in an option hash failing back to class defaults
39
+ def getval key, opts = {}
40
+ #--{{{
41
+ keys = [key, key.to_s, key.to_s.intern]
42
+ keys.each{|k| return opts[k] if opts.has_key?(k)}
43
+ send key rescue nil
44
+ #--}}}
45
+ end
46
+ # just fork with out silly warnings
47
+ def fork &block
48
+ #--{{{
49
+ v = $VERBOSE
50
+ begin
51
+ $VERBOSE = nil
52
+ Process::fork &block
53
+ ensure
54
+ $VERBOSE = v
55
+ end
56
+ #--}}}
57
+ end
58
+ #--}}}
59
+ end
60
+
61
+ attr :object
62
+ attr :obj
63
+ attr :psname
64
+ attr :pid
65
+ attr :ppid
66
+ attr :uri
67
+ attr :pulse_rate
68
+ attr :socket
69
+ attr :debug
70
+
71
+ # 'obj' can be any object and 'opts' may contain the keys
72
+ # 'socket_creation_attempts', 'pulse_rate', 'psname', or 'debug'
73
+ def initialize obj, opts = {}
74
+ #--{{{
75
+ @obj = obj
76
+
77
+ @socket_creation_attempts = getval('socket_creation_attempts', opts)
78
+ @pulse_rate = getval('pulse_rate', opts)
79
+ @debug = getval('debug', opts)
80
+ @psname = getval('psname', opts) || gen_psname(@obj)
81
+
82
+ trace{ "socket_creation_attempts <#{ @socket_creation_attempts }>" }
83
+ trace{ "pulse_rate <#{ @pulse_rate }>" }
84
+ trace{ "psname <#{ @psname }>" }
85
+
86
+ @shutdown = false
87
+
88
+ @heartbeat = Heartbeat::new @pulse_rate, @debug
89
+ @r, @w = IO::pipe
90
+ #
91
+ # child
92
+ #
93
+ unless((@pid = Slave::fork))
94
+ begin
95
+ $0 = @psname
96
+ @pid = Process::pid
97
+ @ppid = Process::ppid
98
+
99
+ @r.close
100
+ @socket = nil
101
+ @uri = nil
102
+
103
+ tmpdir = Dir::tmpdir
104
+ basename = File::basename @psname
105
+
106
+ @socket_creation_attempts.times do |attempt|
107
+ begin
108
+ s = File::join(tmpdir, "#{ basename }_#{ attempt }")
109
+ u = "drbunix://#{ s }"
110
+ DRb::start_service u, obj
111
+ @socket = s
112
+ @uri = u
113
+ trace{ "child - socket <#{ @socket }>" }
114
+ trace{ "child - uri <#{ @uri }>" }
115
+ break
116
+ rescue Errno::EADDRINUSE
117
+ nil
118
+ end
119
+ end
120
+
121
+ if @socket and @uri
122
+ @heartbeat.start
123
+ @w.write @socket
124
+ @w.close
125
+ trap('SIGUSR2') do
126
+ @heartbeat.stop rescue nil
127
+ DBb::thread.kill rescue nil
128
+ FileUtils::rm_f @socket rescue nil
129
+ exit!
130
+ end
131
+ DRb::thread.join
132
+ else
133
+ @w.close
134
+ end
135
+ rescue => e
136
+ trace{ %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }] }
137
+ ensure
138
+ exit!
139
+ end
140
+ #
141
+ # parent
142
+ #
143
+ else
144
+ Process::detach @pid
145
+ @w.close
146
+ @socket = @r.read
147
+ @r.close
148
+
149
+ trace{ "parent - socket <#{ @socket }>" }
150
+
151
+ if @socket and File::exist? @socket
152
+ at_exit{ FileUtils::rm_f @socket }
153
+ @uri = "drbunix://#{ socket }"
154
+ trace{ "parent - uri <#{ @uri }>" }
155
+ @heartbeat.start
156
+ #
157
+ # starting drb on localhost avoids dns lookups!
158
+ #
159
+ DRb::start_service('druby://localhost:0', nil) unless DRb::thread
160
+ @object = DRbObject::new nil, @uri
161
+ else
162
+ raise "failed to find slave socket <#{ @socket }>"
163
+ end
164
+ end
165
+ #--}}}
166
+ end
167
+ # stops the heartbeat thread and kills the child process
168
+ def shutdown
169
+ #--{{{
170
+ raise "already shutdown" if @shutdown
171
+ @heartbeat.stop rescue nil
172
+ Process::kill('SIGUSR2', @pid) rescue nil
173
+ Process::kill('SIGTERM', @pid) rescue nil
174
+ FileUtils::rm_f @socket
175
+ @shutdown = true
176
+ #--}}}
177
+ end
178
+ # generate a default name to appear in ps/top
179
+ def gen_psname obj
180
+ #--{{{
181
+ "#{ obj.class }_slave_of_#{ Process::pid }".downcase
182
+ #--}}}
183
+ end
184
+ # see docs for class.getval
185
+ def getval key, opts = {}
186
+ #--{{{
187
+ self.class.getval key
188
+ #--}}}
189
+ end
190
+ # debugging output
191
+ def trace
192
+ #--{{{
193
+ STDERR.puts(yield) if @debug and STDERR.tty?
194
+ #--}}}
195
+ end
196
+
197
+ #
198
+ # the Heartbeat class is essentially wrapper over an IPC channel that sends a
199
+ # ping on the channel indicating process health. if either end of the channel
200
+ # is detached the ping will fail and an error will be raised. in this was it
201
+ # is ensured that Slave object cannot continue to live without their parent
202
+ # being alive.
203
+ #
204
+ class Heartbeat
205
+ #--{{{
206
+ def initialize pulse_rate = 4.2, debug = false
207
+ #--{{{
208
+ @pulse_rate = Float pulse_rate
209
+ @debug = debug
210
+ @r, @w = IO::pipe
211
+ @pid = Process::pid
212
+ @ppid = Process::ppid
213
+ @cid = nil
214
+ @thread = nil
215
+ @ppid = nil
216
+ @whoami = nil
217
+ @beating = nil
218
+ @pipe = nil
219
+ #--}}}
220
+ end
221
+ def start
222
+ #--{{{
223
+ if Process::pid == @pid
224
+ @w.close
225
+ @pipe = @r
226
+ parent_start
227
+ else
228
+ @r.close
229
+ @pipe = @w
230
+ child_start
231
+ end
232
+ @beating = true
233
+ #--}}}
234
+ end
235
+ def parent_start
236
+ #--{{{
237
+ @whoami = 'parent'
238
+ @thread =
239
+ Thread::new(Thread::current) do |cur|
240
+ begin
241
+ loop do
242
+ buf = @pipe.gets
243
+ trace{ "<#{ @whoami }> <#{ @pid }> gets <#{ buf.inspect }>" }
244
+ @cid = Integer buf.strip if @cid.nil? and buf =~ %r/^\s*\d+\s*$/
245
+ end
246
+ rescue => e
247
+ cur.raise e
248
+ ensure
249
+ @pipe.close rescue nil
250
+ end
251
+ end
252
+ #--}}}
253
+ end
254
+ def child_start
255
+ #--{{{
256
+ @whoami = 'child'
257
+ @pid = Process::pid
258
+ @ppid = Process::ppid
259
+ @thread =
260
+ Thread::new(Thread::current) do |cur|
261
+ begin
262
+ loop do
263
+ trace{ "<#{ @whoami }> <#{ @pid }> puts <#{ @pid }>" }
264
+ @pipe.puts @pid
265
+ Process::kill 0, @ppid
266
+ sleep @pulse_rate
267
+ end
268
+ rescue => e
269
+ cur.raise e
270
+ ensure
271
+ @pipe.close rescue nil
272
+ end
273
+ end
274
+ #--}}}
275
+ end
276
+ def stop
277
+ #--{{{
278
+ raise "not beating" unless @beating
279
+ @thread.kill
280
+ @pipe.close rescue nil
281
+ @beating = false
282
+ #--}}}
283
+ end
284
+ def trace
285
+ #--{{{
286
+ STDERR.puts(yield) if @debug and STDERR.tty?
287
+ #--}}}
288
+ end
289
+ #--}}}
290
+ end # class Heartbeat
291
+ #--}}}
292
+ end # class Slave
data/rdoc.cmd ADDED
@@ -0,0 +1 @@
1
+ rdoc -a -d -F -S -m README -I jpg -N README VERSION lib/slave.rb
data/sample/a.rb ADDED
@@ -0,0 +1,102 @@
1
+ $:.unshift 'lib'
2
+ $:.unshift '../lib'
3
+
4
+ require 'slave'
5
+
6
+ class Incrementer
7
+ attr :decrementer, true
8
+ def increment n
9
+ n + 1
10
+ end
11
+ def decrement n
12
+ @decrementer.decrement n
13
+ end
14
+ end
15
+
16
+ class Decrementer
17
+ attr :incrementer, true
18
+ def increment n
19
+ @incrementer.increment n
20
+ end
21
+ def decrement n
22
+ n - 1
23
+ end
24
+ end
25
+
26
+
27
+ #
28
+ #
29
+ # here we set up a triangle of communicating processes
30
+ #
31
+ # incrementer------decrementer
32
+ # \ /
33
+ # \ /
34
+ # \ /
35
+ # \ /
36
+ # \ /
37
+ # parent
38
+ #
39
+ #
40
+
41
+ incrementer_slave = Slave::new Incrementer::new
42
+ incrementer = incrementer_slave.object
43
+
44
+ puts '---'
45
+ puts 'incrementer :'
46
+ puts " sockect : #{ incrementer_slave.socket.inspect }"
47
+ puts " uri : #{ incrementer_slave.uri.inspect }"
48
+ puts " pid : #{ incrementer_slave.pid.inspect }"
49
+ puts
50
+
51
+ decrementer_slave = Slave::new Decrementer::new
52
+ decrementer = decrementer_slave.object
53
+
54
+ puts '---'
55
+ puts 'decrementer :'
56
+ puts " sockect : #{ decrementer_slave.socket.inspect }"
57
+ puts " uri : #{ decrementer_slave.uri.inspect }"
58
+ puts " pid : #{ decrementer_slave.pid.inspect }"
59
+ puts
60
+
61
+ #
62
+ # connect incrementer and decrementer
63
+ #
64
+
65
+ incrementer.decrementer = decrementer
66
+ decrementer.incrementer = incrementer
67
+
68
+ #
69
+ # now we can call methods on each drb object, and they can also call methods on
70
+ # each other
71
+ #
72
+
73
+ n = 0
74
+
75
+ n = incrementer.increment n
76
+ p n #=> 1
77
+
78
+ n = decrementer.decrement n
79
+ p n #=> 0
80
+
81
+ n = decrementer.increment n
82
+ p n #=> 1
83
+
84
+ n = incrementer.decrement n
85
+ p n #=> 0
86
+
87
+ #
88
+ # we can explicitly shutdown certain slaves
89
+ #
90
+
91
+ incrementer_slave.shutdown
92
+ n = decrementer.decrement 43
93
+ p n #=> 42
94
+
95
+ #
96
+ # Slaves cannot live beyond their parent so we simply exit and all living slaves
97
+ # will eventually die. how long it takes to die is determined by the
98
+ # pulse_rate - the next time the Heartbeat trys to ping the parent the Slave
99
+ # will die.
100
+ #
101
+
102
+ exit
data/slave-0.0.0.gem ADDED
File without changes
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: slave
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.0
7
+ date: 2006-05-27 00:00:00.000000 -06:00
8
+ summary: slave
9
+ require_paths:
10
+ - lib
11
+ email: ara.t.howard@noaa.gov
12
+ homepage: http://codeforpeople.com/lib/ruby/slave/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: slave
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Ara T. Howard
31
+ files:
32
+ - install.rb
33
+ - lib
34
+ - sample
35
+ - rdoc.cmd
36
+ - doc
37
+ - VERSION
38
+ - README
39
+ - gemspec.rb
40
+ - slave-0.0.0.gem
41
+ - lib/slave-0.0.0.rb
42
+ - lib/slave.rb
43
+ - sample/a.rb
44
+ - doc/created.rid
45
+ - doc/dot
46
+ - doc/rdoc-style.css
47
+ - doc/files
48
+ - doc/classes
49
+ - doc/fr_file_index.html
50
+ - doc/fr_class_index.html
51
+ - doc/fr_method_index.html
52
+ - doc/index.html
53
+ - doc/dot/f_0.dot
54
+ - doc/dot/f_0.jpg
55
+ - doc/dot/f_1.dot
56
+ - doc/dot/f_1.jpg
57
+ - doc/dot/f_2.dot
58
+ - doc/dot/f_2.jpg
59
+ - doc/files/VERSION.html
60
+ - doc/files/lib
61
+ - doc/files/README.html
62
+ - doc/files/lib/slave_rb.html
63
+ - doc/classes/Slave.html
64
+ - doc/classes/Slave
65
+ - doc/classes/Slave/Heartbeat.html
66
+ test_files: []
67
+ rdoc_options: []
68
+ extra_rdoc_files: []
69
+ executables: []
70
+ extensions: []
71
+ requirements: []
72
+ dependencies: []