buzzcore 0.2.5

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