buzzware-buzzcore 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+