slave 0.0.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/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: []