proco 0.0.1

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,150 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $VERBOSE = true
4
+ require 'rubygems'
5
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
6
+ require 'proco'
7
+ require 'benchmark'
8
+ require 'parallelize'
9
+ require 'thread'
10
+ require 'logger'
11
+
12
+ logger = Logger.new($stdout)
13
+ times = 1_000_000
14
+
15
+ if celluloid = false
16
+ require 'celluloid'
17
+ class Cell
18
+ include Celluloid
19
+
20
+ def push
21
+ Proco::Future.new
22
+ end
23
+ end
24
+ end
25
+
26
+ result = Benchmark.bm(40) do |x|
27
+ x.report('celluloid') do
28
+ c = Cell.new
29
+ f = nil
30
+ times.times do |i|
31
+ print '.' if i % 1000 == 0
32
+ c.push!
33
+ end
34
+ c.future.push.value
35
+ end if celluloid
36
+
37
+ x.report('simple loop') do
38
+ times.times do |i|
39
+ Proco::Future.new
40
+ end
41
+ end
42
+
43
+ x.report('Default Proco') do
44
+ proco = Proco.new
45
+ proco.start do |item|
46
+ nil
47
+ end
48
+
49
+ times.times do |i|
50
+ print '.' if i % 1000 == 0
51
+ proco.submit! i
52
+ end
53
+ proco.exit
54
+ end
55
+
56
+ x.report('SizedQueue push and pop') do
57
+ q = SizedQueue.new 1000
58
+ times.times do |i|
59
+ q.push Proco::Future.new
60
+ q.pop
61
+ end
62
+ end
63
+
64
+ x.report('SizedQueue push and pop (threads)') do
65
+ q = SizedQueue.new 1000
66
+ t1 = Thread.new do
67
+ times.times do |i|
68
+ q.push Proco::Future.new
69
+ end
70
+ q.push nil
71
+ end
72
+ t2 = Thread.new do
73
+ while q.pop
74
+ end
75
+ end
76
+
77
+ t1.join
78
+ t2.join
79
+ end
80
+
81
+ x.report('Mutex synchronization') do
82
+ m = Mutex.new
83
+ a = []
84
+ times.times do |i|
85
+ m.synchronize do
86
+ a << Proco::Future.new
87
+ end
88
+ m.synchronize do
89
+ a.shift
90
+ end
91
+ end
92
+ end
93
+
94
+ x.report('Proco queue') do
95
+ q = Proco::Queue::SingleQueue.new 1000
96
+ times.times do |i|
97
+ q.push i
98
+ q.take
99
+ end
100
+ end
101
+
102
+ x.report('Proco queue (thread)') do
103
+ q = Proco::Queue::SingleQueue.new 1000
104
+ t1 = Thread.new do
105
+ times.times do |i|
106
+ print '.' if i % 1000 == 0
107
+ q.push i
108
+ end
109
+ q.invalidate
110
+ end
111
+
112
+ t2 = Thread.new do
113
+ while true
114
+ f, i = q.take
115
+ break unless f
116
+ end
117
+ end
118
+
119
+ t1.join
120
+ t2.join
121
+ end
122
+
123
+ [1, 4, 16].each do |queues|
124
+ [1, 2, 4].each do |threads|
125
+ x.report("q: #{queues}, t: #{threads}") do
126
+ proco = Proco.queues(queues).logger(logger).threads(threads).new
127
+ proco.start do |items|
128
+ nil
129
+ end
130
+ threads = 1
131
+ parallelize(threads) do
132
+ (times / threads).times do |i|
133
+ print '.' if i % 1000 == 0
134
+ proco.submit! i
135
+ end
136
+ end
137
+ proco.kill
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ data = Hash[ result.map { |r| [r.label, r.real] } ]
144
+ mlen = data.keys.map(&:length).max
145
+ mval = data.values.max
146
+ width = 40
147
+ data.each do |k, v|
148
+ puts k.ljust(mlen) + ' : ' + '*' * (width * (v / mval)).to_i
149
+ end
150
+
@@ -0,0 +1,112 @@
1
+ require 'proco/version'
2
+ require 'proco/logger'
3
+ require 'proco/mt/base'
4
+ require 'proco/mt/threaded'
5
+ require 'proco/mt/worker'
6
+ require 'proco/mt/pool'
7
+ require 'proco/dispatcher'
8
+ require 'proco/future'
9
+ require 'proco/queue/base'
10
+ require 'proco/queue/single_queue'
11
+ require 'proco/queue/batch_queue'
12
+ require 'proco/queue/multi_queue'
13
+ require 'option_initializer'
14
+
15
+ class Array
16
+ def sample
17
+ self[Kernel.rand(size)]
18
+ end unless method_defined? :sample
19
+ end
20
+
21
+ class Proco
22
+ include Proco::Logger
23
+ include OptionInitializer
24
+
25
+ option_initializer :interval, :threads, :queues, :queue_size, :batch, :batch_size, :logger
26
+ option_validator do |opt, val|
27
+ case opt
28
+ when :interval
29
+ raise ArgumentError, "interval must be a number" unless val.is_a?(Numeric)
30
+ when :threads, :queues, :queue_size, :batch_size
31
+ raise ArgumentError, "#{opt} must be a positive non-zero integer" unless val.is_a?(Fixnum) && val > 0
32
+ when :batch
33
+ raise ArgumentError, "batch must be a boolean value" unless [true, false].include?(val)
34
+ end
35
+ end
36
+
37
+ attr_reader :options
38
+
39
+ DEFAULT_OPTIONS = {
40
+ :interval => 0,
41
+ :threads => 1,
42
+ :queues => 1,
43
+ :queue_size => 1000,
44
+ :batch => false
45
+ }
46
+
47
+ def initialize options = {}, &processor
48
+ validate_options options
49
+ @options = DEFAULT_OPTIONS.merge(options)
50
+ @logger = @options[:logger]
51
+
52
+ @pool = nil
53
+ @running = false
54
+ end
55
+
56
+ # @return [Proco]
57
+ def start &block
58
+ raise ArgumentError, "Block not given" if block.nil?
59
+ @running = true
60
+ @pool = Proco::MT::Pool.new(options[:threads], @logger)
61
+ @dispatchers = options[:queues].times.map { |i|
62
+ Dispatcher.new(self, @pool, block)
63
+ }
64
+
65
+ self
66
+ end
67
+
68
+ # Synchronous submission
69
+ # @param [Object] items
70
+ # @return [Hash]
71
+ def submit item
72
+ check_running
73
+ submit!(item).get
74
+ end
75
+
76
+ # Asynchronous submission
77
+ # @param [Object] items
78
+ # @return [Proco::Future]
79
+ def submit! item
80
+ check_running
81
+ @dispatchers.sample.push(item)
82
+ end
83
+
84
+ # Stops Proco, returns results from remaining submissions in the queue.
85
+ # @return [nil]
86
+ def exit
87
+ check_running
88
+ @running = false
89
+ @dispatchers.each(&:exit)
90
+ @pool.exit
91
+ end
92
+
93
+ # @return [nil]
94
+ def kill
95
+ check_running
96
+ @running = false
97
+ @dispatchers.each(&:kill)
98
+ @pool.kill
99
+ nil
100
+ end
101
+
102
+ # @return [Boolean]
103
+ def running?
104
+ @running
105
+ end
106
+
107
+ private
108
+ def check_running
109
+ raise RuntimeError, "Not running" unless running?
110
+ end
111
+ end
112
+
@@ -0,0 +1,59 @@
1
+ require 'lps'
2
+
3
+ class Proco
4
+ # @private
5
+ class Dispatcher
6
+ include Proco::MT::Threaded
7
+
8
+ def initialize proco, thread_pool, block
9
+ super()
10
+
11
+ @logger, interval, qs, batch, batch_size =
12
+ proco.options.values_at :logger, :interval, :queue_size, :batch, :batch_size
13
+ @queue = if batch && batch_size
14
+ Proco::Queue::BatchQueue.new(qs, batch_size)
15
+ elsif batch
16
+ Proco::Queue::MultiQueue.new(qs)
17
+ else
18
+ Proco::Queue::SingleQueue.new(qs)
19
+ end
20
+ @pool = thread_pool
21
+ @block = block
22
+
23
+ spawn do
24
+ future = items = nil
25
+ LPS.interval(interval).while {
26
+ future, items = @queue.take
27
+ future # JRuby bug
28
+ }.loop do
29
+ inner_loop future, items
30
+ end
31
+ end
32
+ end
33
+
34
+ def push *items
35
+ @queue.push(*items)
36
+ end
37
+
38
+ def exit
39
+ @queue.invalidate
40
+ super
41
+ end
42
+
43
+ private
44
+ def inner_loop future, items
45
+ @pool.assign do
46
+ future.update do
47
+ begin
48
+ @block.call items
49
+ rescue Exception => e
50
+ error e.to_s # TODO
51
+ raise
52
+ end
53
+ end
54
+ end
55
+
56
+ future
57
+ end
58
+ end#Dispatcher
59
+ end#Proco
@@ -0,0 +1,40 @@
1
+ class Proco
2
+ class Future
3
+ include Proco::MT::Base
4
+
5
+ def get
6
+ do_when(proc { @state != :wait }) do
7
+ if @state == :ok
8
+ return @return
9
+ else
10
+ raise @return
11
+ end
12
+ end
13
+ end
14
+
15
+ def inspect
16
+ "Future=#{@state}"
17
+ end
18
+
19
+ # @private
20
+ def initialize
21
+ super()
22
+ @state = :wait
23
+ @return = nil
24
+ end
25
+
26
+ # @private
27
+ def update
28
+ begin
29
+ @return = yield
30
+ @state = :ok
31
+ rescue Exception => e
32
+ @return = e
33
+ @state = :fail
34
+ end
35
+
36
+ broadcast
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,21 @@
1
+ require 'forwardable'
2
+
3
+ class Proco
4
+ module Logger
5
+ extend Forwardable
6
+
7
+ class DummyLogger
8
+ [:info, :debug, :warn, :error].each do |m|
9
+ define_method m do |msg|
10
+ end
11
+ end
12
+ end
13
+
14
+ def logger
15
+ @logger ||= DummyLogger.new
16
+ end
17
+
18
+ def_delegators :logger, :info, :debug, :warn, :error
19
+ end#Logger
20
+ end#Proco
21
+
@@ -0,0 +1,65 @@
1
+ class Proco
2
+ module MT
3
+ # @private
4
+ module Base
5
+ def initialize
6
+ @mtx = Mutex.new
7
+ @cv = ConditionVariable.new
8
+ end
9
+
10
+ def try_when cond, &block
11
+ return unless @mtx.try_lock
12
+
13
+ begin
14
+ block.call if cond.call
15
+ ensure
16
+ @cv.broadcast
17
+ @mtx.unlock
18
+ end
19
+ end
20
+
21
+ def do_when cond, &block
22
+ @mtx.lock
23
+ while !cond.call
24
+ @cv.wait @mtx
25
+ end
26
+ block.call
27
+ ensure
28
+ # A good discussion on the use of broadcast instead of signal
29
+ # http://stackoverflow.com/questions/37026/java-notify-vs-notifyall-all-over-again
30
+ @cv.broadcast
31
+ @mtx.unlock
32
+ end
33
+
34
+ def synchronize
35
+ @mtx.synchronize do
36
+ yield
37
+ end
38
+ end
39
+
40
+ def wait_until &cond
41
+ do_when(cond) {}
42
+ end
43
+
44
+ def signal &block
45
+ @mtx.synchronize do
46
+ begin
47
+ block.call if block
48
+ ensure
49
+ @cv.signal
50
+ end
51
+ end
52
+ end
53
+
54
+ def broadcast &block
55
+ @mtx.synchronize do
56
+ begin
57
+ block.call if block
58
+ ensure
59
+ @cv.broadcast
60
+ end
61
+ end
62
+ end
63
+ end#Base
64
+ end#MT
65
+ end