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