buzzware-buzzcore 0.2.2

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.
@@ -0,0 +1,28 @@
1
+ # This sorts out the issues of require'ing files in Ruby
2
+ # 1) on one line, you specify all the paths you need
3
+ # 2) Relative paths will be relative to the file you are in, absolute paths also supported
4
+ # 3) Paths will be expanded
5
+ # 4) Paths will only be added if they don't already exist
6
+ #
7
+ module ::Kernel
8
+ def require_paths(*aArgs)
9
+ caller_dir = File.dirname(File.expand_path(caller.first.sub(/:[0-9]+.*/,'')))
10
+ aArgs.each do |aPath|
11
+ aPath = File.expand_path(aPath,caller_dir)
12
+ $LOAD_PATH << aPath unless $LOAD_PATH.include?(aPath)
13
+ end
14
+ end
15
+
16
+ def require_paths_first(*aArgs)
17
+ caller_dir = File.dirname(File.expand_path(caller.first.sub(/:[0-9]+.*/,'')))
18
+ paths = []
19
+ aArgs.each do |aPath|
20
+ aPath = File.expand_path(aPath,caller_dir)
21
+ paths << aPath
22
+ end
23
+ paths.each do |p|
24
+ $LOAD_PATH.insert(0,p)
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,80 @@
1
+ gem 'Platform'; require 'platform'
2
+ gem 'shairontoledo-popen4'; require 'popen4'
3
+
4
+ module POpen4
5
+
6
+ class ExecuteError < StandardError
7
+
8
+ attr_reader :result #,:stderr,:stdout,:exitcode,:pid
9
+
10
+ def initialize(aArg)
11
+ if aArg.is_a? Hash
12
+ msg = ([aArg[:stderr],aArg[:stdout],"Error #{aArg[:exitcode].to_s}"].find {|i| i && !i.empty?})
13
+ super(msg)
14
+ @result = aArg
15
+ else
16
+ super(aArg)
17
+ end
18
+ end
19
+
20
+ def inspect
21
+ "#{self.class.to_s}: #{@result.inspect}"
22
+ end
23
+
24
+ end
25
+
26
+ def self.pump_thread(aIn,aOut)
27
+ Thread.new do
28
+ loop { aOut.puts aIn.gets }
29
+ end
30
+ end
31
+
32
+ # Usage :
33
+ # result = POpen4::shell('somebinary') do |r| # block gives opportunity to adjust result, and avoid exception raised from non-zero exit codes
34
+ # if r[:exitcode]==254 # eg. say this binary returns 254 to mean something special but not an error
35
+ # r[:stdout] = 'some correct output'
36
+ # r[:stderr] = ''
37
+ # r[:exitcode] = 0
38
+ # end
39
+ # end
40
+ #
41
+ # OR
42
+ #
43
+ # result = POpen4::shell('somebinary');
44
+ # puts result[:stdout]
45
+ #
46
+ # Giving aStdOut,aStdErr causes the command output to be connected to the given stream, and that stream to not be given in the result hash
47
+ def self.shell(aCommand,aWorkingDir=nil,aTimeout=nil,aStdOut=nil,aStdErr=nil)
48
+ raise ExecuteError.new('aWorkingDir doesnt exist') unless !aWorkingDir || File.exists?(aWorkingDir)
49
+ orig_wd = Dir.getwd
50
+ result = {:command => aCommand, :dir => (aWorkingDir || orig_wd)}
51
+ status = nil
52
+ begin
53
+ Dir.chdir(aWorkingDir) if aWorkingDir
54
+ Timeout.timeout(aTimeout,ExecuteError) do # nil aTimeout will not time out
55
+ status = POpen4::popen4(aCommand) do |stdout, stderr, stdin, pid|
56
+ thrOut = aStdOut ? Thread.new { aStdOut.puts stdout.read } : nil
57
+ thrErr = aStdErr ? Thread.new { aStdErr.puts stderr.read } : nil
58
+ thrOut.join if thrOut
59
+ thrErr.join if thrErr
60
+
61
+ result[:stdout] = stdout.read unless aStdOut
62
+ result[:stderr] = stderr.read unless aStdErr
63
+ result[:pid] = pid
64
+ end
65
+ end
66
+ ensure
67
+ Dir.chdir(orig_wd)
68
+ end
69
+ result[:exitcode] = (status && status.exitstatus) || 1
70
+ yield(result) if block_given?
71
+ raise ExecuteError.new(result) if result[:exitcode] != 0
72
+ return result
73
+ end
74
+
75
+ def self.shell_out(aCommand,aWorkingDir=nil,aTimeout=nil,&block)
76
+ block_given? ? POpen4::shell(aCommand,aWorkingDir,aTimeout,STDOUT,STDERR,&block) : POpen4::shell(aCommand,aWorkingDir,aTimeout,STDOUT,STDERR)
77
+ end
78
+
79
+ end
80
+
@@ -0,0 +1,53 @@
1
+ module StringUtils
2
+ def self.crop(aString,aLength,aEllipsis=true,aConvertNil=true)
3
+ return aConvertNil ? ' '*aLength : nil if !aString
4
+
5
+ increase = aLength-aString.length
6
+ return aString+' '*increase if increase>=0
7
+ return aEllipsis ? aString[0,aLength-3]+'...' : aString[0,aLength]
8
+ end
9
+
10
+ # aTemplate is a string containing tokens like ${SOME_TOKEN}
11
+ # aValues is a hash of token names eg. 'SOME_TOKEN' and their values to substitute
12
+ def self.render_template(aTemplate,aValues)
13
+ # get positions of tokens
14
+ result = aTemplate.gsub(/\$\{(.*?)\}/) do |s|
15
+ key = s[2..-2]
16
+ rep = (aValues[key] || s)
17
+ #puts "replacing #{s} with #{rep}"
18
+ rep
19
+ end
20
+ #puts "rendered :\n#{result}"
21
+ return result
22
+ end
23
+
24
+ def self.clean_number(aString)
25
+ aString.gsub(/[^0-9.-]/,'')
26
+ end
27
+
28
+ # supply a block with 2 parameters, and it will get called for each char as an integer
29
+ def self.each_unicode_char(aString)
30
+ len = 1
31
+ index = 0
32
+ char = 0
33
+ aString.each_byte do |b|
34
+ if index==0
35
+ len = 1
36
+ len = 2 if b & 0b11000000 != 0
37
+ len = 3 if b & 0b11100000 != 0
38
+ len = 4 if b & 0b11110000 != 0
39
+ char = 0
40
+ end
41
+
42
+ char |= b << index*8
43
+
44
+ yield(char,len) if index==len-1 # last byte; char is complete
45
+
46
+ index += 1
47
+ index = 0 if index >= len
48
+ end
49
+ end
50
+
51
+
52
+ end
53
+
@@ -0,0 +1,70 @@
1
+ # represents a mono-spaced text document with a given width and expandable height.
2
+ class TextDoc
3
+
4
+ attr_reader :width, :height, :lines
5
+
6
+ def logger
7
+ RAILS_DEFAULT_LOGGER
8
+ end
9
+
10
+ def initialize(aWidth=80,aHeight=66)
11
+ @width = aWidth
12
+ @height = aHeight
13
+
14
+ @lines = Array.new(@height)
15
+ line_str = ' '*@width
16
+ @lines.collect!{|line| line_str.clone }
17
+ end
18
+
19
+ def replace_string(aString,aCol,aSubString)
20
+ return aString if aSubString==nil || aSubString==''
21
+
22
+ aSubString = aSubString.to_s
23
+ start_col = aCol < 0 ? 0 : aCol
24
+ end_col = aCol+aSubString.length-1
25
+ end_col = @width-1 if end_col >= @width
26
+ source_len = end_col-start_col+1
27
+ return aString if source_len <= 0 || end_col < 0 || start_col >= @width
28
+ aString += ' '*((end_col+1) - aString.length) if aString.length < end_col+1
29
+ aString[start_col,source_len] = aSubString[start_col-aCol,end_col-start_col+1]
30
+ return aString
31
+ end
32
+
33
+ def replace(aCol,aLine,aString)
34
+ return if (aLine < 0) || (aLine>=@lines.length)
35
+ replace_string(@lines[aLine],aCol,aString)
36
+ end
37
+
38
+ def replace_block(aCol,aLine,aLines)
39
+ aLines = aLines.split(/\n/) if aLines.is_a?(String)
40
+ aLines = aLines.lines if aLines.is_a?(TextDoc)
41
+
42
+ aLines.each_index do |iSource|
43
+ replace(aCol,aLine+iSource,aLines[iSource])
44
+ end
45
+ end
46
+
47
+ def add_block(aLines,aCol=0)
48
+ aLines = aLines.split(/\n/) if aLines.is_a?(String)
49
+ aLines = aLines.lines if aLines.is_a?(TextDoc)
50
+ aLines.each_index do |iSource|
51
+ @lines << ' '*@width
52
+ replace(aCol,@lines.length-1,aLines[iSource])
53
+ end
54
+ end
55
+
56
+ def add_line(aLine=nil,aCol=0)
57
+ @lines << ' '*@width and return if !aLine
58
+ @lines << ' '*@width
59
+ replace(aCol,@lines.length-1,aLine)
60
+ end
61
+
62
+ def centre_bar(aChar = '-', indent = 6)
63
+ (' '*indent) + aChar*(@width-(indent*2)) + (' '*indent)
64
+ end
65
+
66
+ def to_s
67
+ return @lines.join("\n")
68
+ end
69
+ end
70
+
@@ -0,0 +1,709 @@
1
+ #require 'monitor'
2
+ require 'timeout'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+ require 'thread'
6
+ require 'fastthread'
7
+
8
+ # # This class provides an object value that can be passed between threads without
9
+ # # fear of collisions. As WaitOne is used, multiple consumers could wait on one producer,
10
+ # # and only one consumer would get each produced value.
11
+ # # Reading it will block until it is written to. Writing will block until the last
12
+ # # written value is read. Therefore, it acts like a blocking queue with a fixed
13
+ # # maximum length of one. It currently doesn't support timeouts.
14
+ # # Reading or writing may raise a UnblockException when the thread is aborted
15
+ # # externally.
16
+ # class MultiThreadVariable {
17
+ #
18
+ # AutoResetEvent areWrite = new AutoResetEvent(true);
19
+ # AutoResetEvent areRead = new AutoResetEvent(false);
20
+ #
21
+ # def initialize
22
+ # @unblock = false
23
+ # @val = nil
24
+ # @mutex = Mutex.new
25
+ # end
26
+ #
27
+ # def value
28
+ # if (@unblock || !areRead.WaitOne())
29
+ # raise new UnblockException();
30
+ # @mutex.synchronize do
31
+ # object result = val;
32
+ # areWrite.Set();
33
+ # return result;
34
+ # end
35
+ # def value=
36
+ # if (@unblock || !areWrite.WaitOne())
37
+ # raise new UnblockException();
38
+ # @mutex.synchronize do
39
+ # val = value;
40
+ # areRead.Set();
41
+ # end
42
+ # end
43
+ #
44
+ # #Call this when shutting down to break any existing block and prevent any future blocks
45
+ # def unblock()
46
+ # @unblock = true;
47
+ # areWrite.Set();
48
+ # areRead.Set();
49
+ # end
50
+ # end
51
+
52
+
53
+ # class MultiThreadVariableMonitor
54
+ #
55
+ # include MonitorMixin
56
+ #
57
+ # class UnblockError < StandardError; end
58
+ # class TimeoutError < StandardError; end
59
+ # class LockFailedError < StandardError; end
60
+ #
61
+ # def initialize(aTimeout=nil)
62
+ # mon_initialize()
63
+ # @value = nil
64
+ # @unblock = false
65
+ # @timeout = aTimeout
66
+ # @readable = new_cond
67
+ # @writable = new_cond
68
+ # @is_first_write = true
69
+ # end
70
+ #
71
+ # def value
72
+ # raise UnblockError.new if @unblock
73
+ # mon_synchronize do
74
+ # raise TimeoutError.new if not @readable.wait(@timeout)
75
+ # result = @value
76
+ # @writable.signal
77
+ # end
78
+ # return result
79
+ # end
80
+ #
81
+ # def value=(v)
82
+ # raise UnblockError.new if @unblock
83
+ # mon_synchronize do
84
+ # if @is_first_write
85
+ # @is_first_write = false
86
+ # else
87
+ # raise TimeoutError.new if not @writable.wait(@timeout)
88
+ # end
89
+ # @value = v
90
+ # @readable.signal
91
+ # end
92
+ # return v
93
+ # end
94
+ #
95
+ # def unblock()
96
+ # @unblock = true;
97
+ # @readable.broadcast
98
+ # @writable.broadcast
99
+ # while @readable.count_waiters()>0 or @writable.count_waiters()>0 do
100
+ # sleep(1)
101
+ # end
102
+ # end
103
+ # end
104
+
105
+ class UnblockError < StandardError; end
106
+
107
+ SizedQueue.class_eval do
108
+ def unblock
109
+ @unblock = true
110
+ Thread.exclusive do
111
+ if @queue_wait
112
+ while t = @queue_wait.shift do
113
+ t.raise(UnblockError.new) unless t == Thread.current
114
+ end
115
+ end
116
+ if @waiting
117
+ while t = @waiting.shift do
118
+ t.raise(UnblockError.new) unless t == Thread.current
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ alias original_push push
125
+ def push(obj)
126
+ raise UnblockError.new if @unblock
127
+ original_push(obj)
128
+ end
129
+
130
+ alias original_pop pop
131
+ def pop(*args)
132
+ raise UnblockError.new if @unblock
133
+ original_pop(*args)
134
+ end
135
+
136
+ end
137
+
138
+ # BEGIN
139
+ # $Id: semaphore.rb,v 1.2 2003/03/15 20:10:10 fukumoto Exp $
140
+ class CountingSemaphore
141
+
142
+ def initialize(initvalue = 0)
143
+ @counter = initvalue
144
+ @waiting_list = []
145
+ end
146
+
147
+ def wait
148
+ Thread.critical = true
149
+ if (@counter -= 1) < 0
150
+ @waiting_list.push(Thread.current)
151
+ Thread.stop
152
+ end
153
+ self
154
+ ensure
155
+ Thread.critical = false
156
+ end
157
+
158
+ def signal
159
+ Thread.critical = true
160
+ begin
161
+ if (@counter += 1) <= 0
162
+ t = @waiting_list.shift
163
+ t.wakeup if t
164
+ end
165
+ rescue ThreadError
166
+ retry
167
+ end
168
+ self
169
+ ensure
170
+ Thread.critical = false
171
+ end
172
+
173
+ alias down wait
174
+ alias up signal
175
+ alias P wait
176
+ alias V signal
177
+
178
+ def exclusive
179
+ wait
180
+ yield
181
+ ensure
182
+ signal
183
+ end
184
+
185
+ alias synchronize exclusive
186
+
187
+ end
188
+
189
+ Semaphore = CountingSemaphore
190
+ # END
191
+
192
+
193
+ class MultiThreadVariable
194
+
195
+ attr_accessor :read_timeout, :write_timeout
196
+
197
+ def initialize(aReadTimeout=nil,aWriteTimeout=nil)
198
+ @q = SizedQueue.new(1)
199
+ @read_timeout = aReadTimeout
200
+ @write_timeout = aWriteTimeout
201
+ end
202
+
203
+ def clear
204
+ @q.clear
205
+ end
206
+
207
+ def empty?
208
+ @q.empty?
209
+ end
210
+
211
+ def value
212
+ if @read_timeout
213
+ Timeout.timeout(@read_timeout) do
214
+ @q.pop
215
+ end
216
+ else
217
+ @q.pop
218
+ end
219
+ end
220
+
221
+ def inner_value_set(aValue)
222
+ Thread.exclusive do
223
+ if @reject_next
224
+ clear
225
+ else
226
+ @q.push(aValue)
227
+ end
228
+ end
229
+ end
230
+
231
+ def value=(aValue)
232
+ if @write_timeout
233
+ Timeout.timeout(@write_timeout) { inner_value_set(aValue) }
234
+ else
235
+ inner_value_set(aValue)
236
+ end
237
+ aValue
238
+ end
239
+
240
+ def unblock()
241
+ @q.unblock()
242
+ end
243
+
244
+ def reject_value
245
+ Thread.exclusive do
246
+ if !empty?
247
+ clear
248
+ else
249
+ @reject_next = true
250
+ end
251
+ end
252
+ end
253
+
254
+
255
+ end
256
+
257
+ class MonitorVariable
258
+
259
+ def initialize(aMonitor)
260
+ @monitor = aMonitor
261
+ @cvRead = @monitor.new_cond
262
+ @cvWrite = @monitor.new_cond
263
+ @empty = true
264
+ end
265
+
266
+ def value
267
+ @monitor.synchronize do
268
+ while empty?
269
+ @cvRead.wait(@timeout)
270
+ end
271
+ result = @value
272
+ @value = nil
273
+ @empty = true
274
+ @cvWrite.signal
275
+ result
276
+ end
277
+ end
278
+
279
+ def value=(aValue)
280
+ @monitor.synchronize do
281
+ until empty?
282
+ @cvWrite.wait(@timeout)
283
+ end
284
+ if @reject_next
285
+ clear
286
+ else
287
+ @value = aValue
288
+ @empty = false
289
+ @cvRead.signal
290
+ end
291
+ aValue
292
+ end
293
+ end
294
+
295
+ def empty?
296
+ @empty
297
+ end
298
+
299
+ def clear
300
+ @monitor.synchronize do
301
+ @value = nil
302
+ @empty = true
303
+ @cvWrite.signal
304
+ end
305
+ end
306
+
307
+ def reject_value
308
+ @monitor.synchronize do
309
+ if !empty?
310
+ clear
311
+ else
312
+ @reject_next = true
313
+ end
314
+ end
315
+ end
316
+ end
317
+
318
+
319
+ # This module decouples multiple master threads from multiple slave (worker) threads
320
+ # It provides two main methods :
321
+ # + master_attempt_command to be called by multiple clients with a command, returning a result or rasing an exception
322
+ # + slave_do_command to be called by worker threads with a block which processes the command.
323
+ #
324
+ # see ProviderWorker in pay_server/app/pay_server.rb
325
+ module MasterSlaveSynchroniserMixin
326
+
327
+ def logger
328
+ @logger || (@logger = Logger.new(STDERR))
329
+ end
330
+
331
+ def ms_synchronizer_initialize(aTimeout=nil,aLogger=nil)
332
+ @logger = aLogger
333
+ @semaphore = CountingSemaphore.new(1)
334
+ timeout = aTimeout && aTimeout/2.0
335
+ @mvCommand = MultiThreadVariable.new(nil,timeout)
336
+ @mvResponse = MultiThreadVariable.new(timeout,nil)
337
+ end
338
+
339
+ def master_attempt_command(aCommand)
340
+ @semaphore.exclusive do
341
+ command_sent = false
342
+ begin
343
+ before = Time.now
344
+ logger.debug { "master sending aCommand:"+aCommand.inspect }
345
+ @mvCommand.value = aCommand
346
+ command_sent = true
347
+ logger.debug { "master waiting for result" }
348
+ result = @mvResponse.value
349
+ logger.debug { "master received result:"+result.inspect }
350
+ rescue Exception => e
351
+ # exception causes thread critical status to be lost
352
+ logger.debug { "master exception:"+e.inspect }
353
+ if command_sent
354
+ logger.debug { "rejecting" }
355
+ @mvResponse.reject_value #!!! this doesn't seem to return
356
+ end
357
+ raise e
358
+ ensure
359
+ logger.debug { "master_attempt_command: command_sent=#{command_sent.to_s} elapsed:"+(Time.now-before).to_s }
360
+ end
361
+ result
362
+ end
363
+ end
364
+
365
+ def slave_do_command(&block)
366
+ Thread.exclusive do
367
+ logger.debug { "slave waiting for command" }
368
+ command = @mvCommand.value
369
+ logger.debug { "slave received command:"+command.inspect }
370
+ result = yield(command)
371
+ logger.debug { "slave sending result:"+result.inspect }
372
+ @mvResponse.value = result
373
+ logger.debug { "slave finished" }
374
+ result
375
+ end
376
+ end
377
+
378
+ def shutdown
379
+ @mvCommand.unblock()
380
+ @mvResponse.unblock()
381
+ end
382
+ end
383
+
384
+ class MasterSlaveSynchroniser
385
+ include MasterSlaveSynchroniserMixin
386
+
387
+ def initialize(aTimeout=nil,aLogger=nil)
388
+ ms_synchronizer_initialize(aTimeout,aLogger)
389
+ end
390
+ end
391
+
392
+
393
+
394
+ module Worker
395
+
396
+ module PidFile
397
+ def self.store(aFilename, aPID)
398
+ File.open(aFilename, 'w') {|f| f << aPID}
399
+ end
400
+
401
+ def self.recall(aFilename)
402
+ IO.read(aFilename).to_i rescue nil
403
+ end
404
+ end
405
+
406
+ class Base
407
+ TempDirectory = Dir.tmpdir
408
+
409
+ def self.pid_filename
410
+ File.join(TempDirectory, "#{name}.pid")
411
+ end
412
+
413
+ def logger
414
+ if not @logger
415
+ @logger = Logger.new(STDERR)
416
+ @logger.level = Logger::DEBUG
417
+ end
418
+ @logger
419
+ end
420
+
421
+ def main_proc
422
+ begin
423
+ @is_stopped = false
424
+ @is_started = false
425
+ self.starting()
426
+ @is_started = true
427
+ @is_stopping = false
428
+ while !@is_stopping do
429
+ running();
430
+ logger.debug { "ServiceThread running loop: @is_stopping="+@is_stopping.to_s }
431
+ end
432
+ rescue SystemExit => e # smother and do nothing
433
+ rescue Exception => e
434
+ logger.warn { "Thread #{@name} #{e.inspect} exception in Starting() or Running()" }
435
+ logger.warn { e.backtrace }
436
+ ensure
437
+ @is_stopping = true
438
+ end
439
+
440
+ begin
441
+ stopping()
442
+ rescue Exception => e
443
+ logger.warn { "Thread #{@name} #{e.inspect} exception in stopping()" }
444
+ logger.warn { e.backtrace }
445
+ end
446
+ logger.info { "Thread #{@name} dropped out" }
447
+ @is_stopped = true
448
+ end
449
+
450
+ def wait_for_started(aTimeout)
451
+ before = Time.now
452
+ while !@is_started and (Time.now-before) < aTimeout
453
+ sleep(aTimeout / 10)
454
+ end
455
+ raise Timeout::Error.new("failed to start within timeout (#{aTimeout.to_s})") if !@is_started
456
+ end
457
+
458
+ def wait_for_stopped(aTimeout)
459
+ before = Time.now
460
+ while !@is_stopped and (Time.now-before) < aTimeout
461
+ sleep(aTimeout / 10)
462
+ end
463
+ raise Timeout::Error.new("failed to stop within timeout (#{aTimeout.to_s})") if !@is_stopped
464
+ end
465
+
466
+ def stop
467
+ @is_stopping = true
468
+ end
469
+
470
+ def starting
471
+ end
472
+
473
+ def running
474
+ end
475
+
476
+ def stopping
477
+ end
478
+ end
479
+
480
+
481
+ class Threader
482
+
483
+ attr_reader :worker,:thread
484
+
485
+ def self.start_new(aWorkerClass,aTimeout=0.1,&aCreateBlock)
486
+ threader = Threader.new(aWorkerClass,&aCreateBlock)
487
+ threader.start(aTimeout)
488
+ return threader
489
+ end
490
+
491
+ def initialize(aWorkerClass,&aCreateBlock)
492
+ @create_proc = aCreateBlock
493
+ @worker_class = aWorkerClass
494
+ if @create_proc
495
+ @worker = @create_proc.call(@worker_class)
496
+ else
497
+ @worker = @worker_class.new
498
+ end
499
+ end
500
+
501
+ def start(aTimeout=0.1)
502
+ @thread = Thread.new(@worker) { |aWorker| aWorker.main_proc }
503
+ @worker.wait_for_started(aTimeout)
504
+ end
505
+
506
+ def stop(aTimeout=0.1)
507
+ @worker.stop
508
+ @worker.wait_for_stopped(aTimeout)
509
+ @thread.exit unless !@thread or (@thread.join(0) and not @thread.alive?)
510
+ #@thread.join()
511
+ @worker = nil
512
+ @thread = nil
513
+ end
514
+ end
515
+
516
+ module Daemonizer
517
+
518
+ # Assists in making a daemon script from a Worker. If a block is given, it is assumed to create the worker and return it.
519
+ # Otherwise the Worker is created with no arguments from aWorkerClass
520
+ # Either way, the worker is only created when starting
521
+ def self.daemonize(aWorkerClass,aConfig={})
522
+ case !ARGV.empty? && ARGV[0]
523
+ when 'start'
524
+ worker = block_given? ? yield(aWorkerClass) : aWorkerClass.new
525
+ if aConfig['no_fork']
526
+ start_no_fork(worker)
527
+ else
528
+ start(worker)
529
+ end
530
+ when 'stop'
531
+ stop(aWorkerClass)
532
+ when 'restart'
533
+ stop(aWorkerClass)
534
+ worker = block_given? ? yield(aWorkerClass) : aWorkerClass.new
535
+ start(worker)
536
+ else
537
+ puts "Invalid command. Please specify start, stop or restart."
538
+ exit
539
+ end
540
+ end
541
+
542
+ def self.start_no_fork(aWorker)
543
+ PidFile.store(aWorker.class.pid_filename, Process.pid)
544
+ Dir.chdir(aWorker.class::TempDirectory)
545
+ trap('TERM') do
546
+ begin
547
+ puts "Daemonizer::start_no_fork TERM"
548
+ #puts aWorker.inspect
549
+ aWorker.stop
550
+ puts "after worker stop"
551
+ aWorker.wait_for_stopped()
552
+ puts "after worker stop wait"
553
+ rescue Exception => e
554
+ puts "Exception: "+e.inspect
555
+ end
556
+ exit
557
+ end
558
+ trap('HUP','IGNORE') unless is_windows? # ignore SIGHUP - required for Capistrano
559
+ aWorker.main_proc
560
+ end
561
+
562
+ def self.start(aWorker)
563
+ fork do
564
+ Process.setsid
565
+ if child_pid = fork
566
+ Process.detach(child_pid)
567
+ else
568
+ PidFile.store(aWorker.class.pid_filename, Process.pid)
569
+ Dir.chdir(aWorker.class::TempDirectory)
570
+ File.umask 0000
571
+ STDIN.reopen "/dev/null"
572
+ STDOUT.reopen "/dev/null", "a" # problems here
573
+ STDERR.reopen STDOUT
574
+ trap('TERM') do
575
+ puts "Daemonizer::start TERM"
576
+ aWorker.stop
577
+ aWorker.wait_for_stopped()
578
+ exit
579
+ end
580
+ trap('HUP','IGNORE') # ignore SIGHUP - required for Capistrano
581
+ aWorker.main_proc
582
+ end
583
+ end
584
+ end
585
+
586
+ def self.stop(aWorkerClass)
587
+ if !File.file?(aWorkerClass.pid_filename)
588
+ puts "Pid file not found. Is the aWorker started?"
589
+ exit
590
+ end
591
+ pid = PidFile.recall(aWorkerClass.pid_filename)
592
+ pid && Process.kill("TERM", pid)
593
+ FileUtils.rm(aWorkerClass.pid_filename)
594
+ end
595
+ end
596
+ end
597
+
598
+
599
+ # How to use :
600
+ #
601
+ # class Worker < ServiceThread
602
+ # def initialize
603
+ # super(:name => 'worker',:auto_start => true)
604
+ # end
605
+ #
606
+ # def starting
607
+ # # startup code
608
+ # end
609
+ #
610
+ # def running
611
+ # # repeated code, implicitly looped. set self.stopping = true to quit thread
612
+ # end
613
+ #
614
+ # def stopping
615
+ # # clean up code
616
+ # end
617
+ # end
618
+ #
619
+ # @worker = Worker.new
620
+ # logger.info "worker #{@worker.is_started ? 'has':'hasn''t'} started and {@worker.is_stopped ? 'has':'hasn''t'} stopped"
621
+ # @worker.stop
622
+ #
623
+ class ServiceThread
624
+
625
+ attr_accessor :name, :logger, :is_stopping
626
+ attr_reader :is_stopped, :is_started, :options
627
+
628
+ def random_word(min,max)
629
+ len = min + rand(max-min+1)
630
+ result = ' '*len
631
+ (len-1).downto(0) {|i| result[i] = (?a + rand(?z-?a+1)).chr}
632
+ return result
633
+ end
634
+
635
+ # if inheriting from ServiceThread and overriding initialize, remember to call super(aOptions) and that
636
+ # this method may start the thread, so any setup must be done before super, not after
637
+ def initialize(aOptions)
638
+ @options = aOptions
639
+ @thread = nil
640
+ @name = aOptions[:name] || random_word(8,8)
641
+ if not @logger = aOptions[:logger]
642
+ @logger = Logger.new(STDERR)
643
+ @logger.level = Logger::DEBUG
644
+ end
645
+ self.start() if aOptions[:auto_start]
646
+ end
647
+
648
+ def start
649
+ raise Exception.new("ServiceThread already started") if @thread
650
+ @thread = Thread.new() { main_proc() }
651
+ #Thread.pass unless @is_started or @is_stopped # no timeout !
652
+ end
653
+
654
+ def name
655
+ @name
656
+ end
657
+
658
+ def logger
659
+ @logger
660
+ end
661
+
662
+
663
+ def main_proc
664
+ begin
665
+ @is_stopped = false
666
+ @is_started = false
667
+ self.starting()
668
+ @is_started = true
669
+ @is_stopping = false
670
+ while !@is_stopping do
671
+ running();
672
+ logger.debug { "ServiceThread running loop: @is_stopping="+@is_stopping.to_s }
673
+ end
674
+ rescue Exception => e
675
+ @is_stopping = true
676
+ logger.warn { "Thread #{@name} #{e.inspect} exception in Starting() or Running()" }
677
+ logger.warn { e.backtrace }
678
+ end
679
+
680
+ begin
681
+ stopping()
682
+ rescue Exception => e
683
+ logger.warn { "Thread #{@name} #{e.inspect} exception in stopping()" }
684
+ logger.warn { e.backtrace }
685
+ end
686
+ logger.info { "Thread #{@name} dropped out" }
687
+ @is_stopped = true
688
+ end
689
+
690
+ def starting
691
+ end
692
+
693
+ def running
694
+ end
695
+
696
+ def stopping
697
+ end
698
+
699
+ def gentle_stop
700
+ @is_stopping = true
701
+ end
702
+
703
+ def stop
704
+ @is_stopping = true
705
+ @thread.exit unless !@thread or (@thread.join(0) and not @thread.alive?)
706
+ end
707
+
708
+ end
709
+