proco 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,53 @@
1
+ class Proco
2
+ module MT
3
+ # @private
4
+ class Pool
5
+ include Proco::Logger
6
+
7
+ def initialize size, logger = nil
8
+ @logger = logger
9
+ @workers = size.times.map { |i|
10
+ Worker.new @logger
11
+ }
12
+ @num_workers = @workers.length
13
+ if @num_workers > 1
14
+ self.instance_eval do
15
+ alias assign assign_try
16
+ end
17
+ else
18
+ self.instance_eval do
19
+ alias assign assign_wait
20
+ end
21
+ end
22
+ end
23
+
24
+ def assign_wait &block
25
+ # Optimistic randomized assignment to avoid mutex contention
26
+ @workers.sample.assign(&block)
27
+ end
28
+
29
+ def assign_try &block
30
+ @num_workers.times do |i|
31
+ ret = @workers.sample.try_assign(&block)
32
+ return ret if ret
33
+ end
34
+ # phew. blocking assignment
35
+ # debug "Failed immediate thread allocation in the 1st round (#@num_workers)"
36
+ assign_wait(&block)
37
+ end
38
+
39
+ def exit
40
+ @workers.each(&:exit)
41
+ end
42
+
43
+ def kill
44
+ @workers.each(&:kill)
45
+ end
46
+
47
+ def counter
48
+ @workers.map(&:counter).inject(:+)
49
+ end
50
+ end#Pool
51
+ end#MT
52
+ end#Proco
53
+
@@ -0,0 +1,52 @@
1
+ class Proco
2
+ module MT
3
+ # @private
4
+ module Threaded
5
+ include Proco::Logger
6
+ include Proco::MT::Base
7
+
8
+ def initialize
9
+ super
10
+ @running = false
11
+ end
12
+
13
+ def running?
14
+ @running
15
+ end
16
+
17
+ def exit
18
+ broadcast do
19
+ @running = false
20
+ end
21
+ @thread.join
22
+ end
23
+
24
+ def kill
25
+ @running = false
26
+ Thread.kill @thread if @thread
27
+ end
28
+
29
+ def spawn &block
30
+ @thread = Thread.new do
31
+ debug "#{Thread.current} started (#{self})"
32
+ broadcast do
33
+ @running = true
34
+ end
35
+
36
+ begin
37
+ block.call
38
+ rescue Exception => e
39
+ error "[#{Thread.current}] #{e}"
40
+ raise
41
+ ensure
42
+ debug "#{Thread.current} exited (#{self})"
43
+ end
44
+ end
45
+ wait_until { running? }
46
+
47
+ @thread
48
+ end
49
+ end#Threaded
50
+ end#MT
51
+ end#Proco
52
+
@@ -0,0 +1,81 @@
1
+ class Proco
2
+ module MT
3
+ # @private
4
+ class Worker
5
+ include Proco::MT::Threaded
6
+
7
+ attr_reader :counter
8
+
9
+ def initialize logger
10
+ super()
11
+
12
+ @logger = logger
13
+ @block = nil
14
+ @counter = 0
15
+
16
+ spawn do
17
+ work while running?
18
+ end
19
+ end
20
+
21
+ def work
22
+ # Not using do_when makes the code around the task block about 10% faster
23
+ @mtx.lock
24
+ while true
25
+ break if @block
26
+ return unless running?
27
+ @cv.wait @mtx
28
+ end
29
+ block = @block
30
+ @counter += 1
31
+ @block = nil
32
+
33
+ # Release lock here, so that a new task can be assigned during the execution
34
+ # 50 -> 30
35
+ @cv.broadcast
36
+ @mtx.unlock
37
+
38
+ # Work!
39
+ block.call
40
+ end
41
+
42
+ # Blocks when working
43
+ def assign &block
44
+ #do_when(Proc.new { return unless running?; @block.nil? }) do
45
+ # @block = block
46
+ #end
47
+ @mtx.lock
48
+ while true
49
+ break unless @block
50
+ return unless running?
51
+ @cv.wait @mtx
52
+ end
53
+ @block = block
54
+ ensure
55
+ @cv.broadcast
56
+ @mtx.unlock
57
+ end
58
+
59
+ # Returns nil when working
60
+ def try_assign &block
61
+ #try_when(Proc.new { return unless running?; @block.nil? }) do
62
+ # @block = block
63
+ #end
64
+ return unless @mtx.try_lock
65
+
66
+ begin
67
+ return if !running? || @block
68
+ @block = block
69
+ ensure
70
+ @cv.broadcast
71
+ @mtx.unlock
72
+ end
73
+ end
74
+
75
+ def exit
76
+ wait_until { @block.nil? }
77
+ super
78
+ end
79
+ end#Worker
80
+ end#MT
81
+ end#Proco
@@ -0,0 +1,58 @@
1
+ require 'thread'
2
+
3
+ class Proco
4
+ module Queue
5
+ # @private
6
+ class Base
7
+ include Proco::MT::Base
8
+
9
+ class Invalidated < Exception
10
+ def to_s
11
+ "Queue invalidated"
12
+ end
13
+ end
14
+
15
+ def initialize size
16
+ super()
17
+ @size = size
18
+ @items = []
19
+ @valid = true
20
+ end
21
+
22
+ def invalidate
23
+ broadcast do
24
+ @valid = false
25
+ end
26
+ end
27
+
28
+ def push item
29
+ @mtx.lock
30
+ while true
31
+ raise Invalidated unless @valid
32
+ break if @items.length < @size
33
+ @cv.wait @mtx
34
+ end
35
+ push_impl item
36
+ ensure
37
+ @cv.broadcast
38
+ @mtx.unlock
39
+ end
40
+
41
+ def take
42
+ @mtx.lock
43
+ while true
44
+ empty = @items.empty?
45
+ break unless empty
46
+ return nil unless @valid
47
+ @cv.wait @mtx
48
+ end
49
+ take_impl
50
+ ensure
51
+ @cv.broadcast
52
+ @mtx.unlock
53
+ end
54
+ end#Base
55
+ end#Queue
56
+ end
57
+
58
+
@@ -0,0 +1,30 @@
1
+ require 'thread'
2
+
3
+ class Proco
4
+ module Queue
5
+ # @private
6
+ class BatchQueue < Proco::Queue::Base
7
+ def initialize size, batch_size
8
+ super size
9
+ @futures = []
10
+ @batch_size = batch_size
11
+ end
12
+
13
+ def push_impl item
14
+ @items << item
15
+ if @items.length % @batch_size == 1
16
+ @futures << Future.new
17
+ end
18
+ @futures.last
19
+ end
20
+
21
+ def take_impl
22
+ items = @items[0, @batch_size]
23
+ @items = @items[@batch_size..-1] || []
24
+
25
+ [@futures.shift, items]
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,30 @@
1
+ require 'thread'
2
+
3
+ class Proco
4
+ module Queue
5
+ # @private
6
+ class MultiQueue < Proco::Queue::Base
7
+ def initialize size
8
+ super
9
+ @future = Future.new
10
+ end
11
+
12
+ def push_impl item
13
+ @items << item
14
+ @future
15
+ end
16
+
17
+ def take_impl
18
+ items = @items
19
+ ret = [@future, items]
20
+
21
+ # Reset vars
22
+ @items = []
23
+ @future = Future.new
24
+
25
+ ret
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,23 @@
1
+ require 'thread'
2
+
3
+ class Proco
4
+ module Queue
5
+ # @private
6
+ class SingleQueue < Proco::Queue::Base
7
+ def initialize size
8
+ super
9
+ end
10
+
11
+ def push_impl item
12
+ future = Future.new
13
+ @items << [future, item]
14
+ future
15
+ end
16
+
17
+ def take_impl
18
+ @items.shift
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,3 @@
1
+ class Proco
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'proco/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "proco"
8
+ gem.version = Proco::VERSION
9
+ gem.authors = ["Junegunn Choi"]
10
+ gem.email = ["junegunn.c@gmail.com"]
11
+ gem.description = %q{A lightweight asynchronous task executor service designed for efficient batch processing}
12
+ gem.summary = %q{A lightweight asynchronous task executor service designed for efficient batch processing}
13
+ gem.homepage = "https://github.com/junegunn/proco"
14
+
15
+ gem.files = `git ls-files`.split($/).reject { |f| f =~ %r[^viz/] }
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency 'lps', '~> 0.1.3'
21
+ gem.add_runtime_dependency 'option_initializer', '~> 1.2.0'
22
+ gem.add_development_dependency 'minitest'
23
+ gem.add_development_dependency 'parallelize'
24
+ end
@@ -0,0 +1,40 @@
1
+ $VERBOSE = true
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) if $0 == __FILE__
3
+ require 'rubygems'
4
+ require 'minitest/autorun'
5
+ require 'proco'
6
+
7
+ class MTBaseSub
8
+ include Proco::MT::Base
9
+
10
+ def initialize
11
+ super
12
+ end
13
+
14
+ def ok dur
15
+ a = false
16
+ t = Thread.new { sleep dur; a = :ok; self.signal }
17
+ do_when(proc { a }) do
18
+ a
19
+ end.tap { t.join }
20
+ end
21
+
22
+ def not_ok
23
+ # Proc.new instead of proc (1.8 compat)
24
+ do_when(Proc.new { return :return }) do
25
+ :anything
26
+ end
27
+ end
28
+ end
29
+
30
+ class TestMTBase < MiniTest::Unit::TestCase
31
+ def test_mt_base
32
+ t = MTBaseSub.new
33
+ st = Time.now
34
+ assert_equal :ok, t.ok(0.1)
35
+ assert Time.now - st >= 0.1 # FIXME
36
+
37
+ assert_equal :return, t.not_ok
38
+ end
39
+ end
40
+
@@ -0,0 +1,32 @@
1
+ $VERBOSE = true
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) if $0 == __FILE__
3
+ require 'rubygems'
4
+ require 'minitest/autorun'
5
+ require 'proco'
6
+
7
+ class MTThreadedSub
8
+ include Proco::MT::Threaded
9
+
10
+ def initialize
11
+ super
12
+ end
13
+ end
14
+
15
+ class TestMTThreaded < MiniTest::Unit::TestCase
16
+ def test_mt_base
17
+ t = MTThreadedSub.new
18
+ assert_equal false, t.running?
19
+ status = nil
20
+ t.spawn do
21
+ while t.running?
22
+ sleep 0.01
23
+ end
24
+ status = :done
25
+ end
26
+ assert_equal true, t.running?
27
+ t.exit
28
+ assert_equal :done, status
29
+ assert_equal false, t.running?
30
+ end
31
+ end
32
+
@@ -0,0 +1,20 @@
1
+ $VERBOSE = true
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) if $0 == __FILE__
3
+ require 'rubygems'
4
+ require 'minitest/autorun'
5
+ require 'proco'
6
+
7
+ class TestPool < MiniTest::Unit::TestCase
8
+ def test_pool
9
+ pool = Proco::MT::Pool.new(8)
10
+ cnt = 0
11
+ mtx = Mutex.new
12
+ 1000.times do
13
+ pool.assign { mtx.synchronize { cnt += 3 } }
14
+ end
15
+ pool.exit
16
+ assert_equal 1000, pool.counter
17
+ assert_equal 3000, cnt
18
+ end
19
+ end
20
+