concurrent-ruby 0.1.0 → 0.1.1.pre.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.
- data/LICENSE +21 -21
- data/README.md +279 -224
- data/lib/concurrent.rb +27 -20
- data/lib/concurrent/agent.rb +106 -130
- data/lib/concurrent/cached_thread_pool.rb +130 -122
- data/lib/concurrent/defer.rb +67 -69
- data/lib/concurrent/drb_async_demux.rb +72 -0
- data/lib/concurrent/event.rb +60 -60
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
- data/lib/concurrent/executor.rb +87 -0
- data/lib/concurrent/fixed_thread_pool.rb +89 -89
- data/lib/concurrent/functions.rb +120 -0
- data/lib/concurrent/future.rb +52 -42
- data/lib/concurrent/global_thread_pool.rb +3 -3
- data/lib/concurrent/goroutine.rb +29 -25
- data/lib/concurrent/obligation.rb +67 -121
- data/lib/concurrent/promise.rb +172 -194
- data/lib/concurrent/reactor.rb +162 -0
- data/lib/concurrent/smart_mutex.rb +66 -0
- data/lib/concurrent/tcp_sync_demux.rb +96 -0
- data/lib/concurrent/thread_pool.rb +65 -61
- data/lib/concurrent/utilities.rb +34 -0
- data/lib/concurrent/version.rb +3 -3
- data/lib/concurrent_ruby.rb +1 -1
- data/md/agent.md +123 -123
- data/md/defer.md +174 -174
- data/md/event.md +32 -32
- data/md/executor.md +176 -0
- data/md/future.md +83 -83
- data/md/goroutine.md +52 -52
- data/md/obligation.md +32 -32
- data/md/promise.md +225 -225
- data/md/thread_pool.md +197 -197
- data/spec/concurrent/agent_spec.rb +376 -405
- data/spec/concurrent/cached_thread_pool_spec.rb +112 -112
- data/spec/concurrent/defer_spec.rb +209 -199
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +250 -246
- data/spec/concurrent/event_spec.rb +134 -134
- data/spec/concurrent/executor_spec.rb +146 -0
- data/spec/concurrent/fixed_thread_pool_spec.rb +84 -84
- data/spec/concurrent/functions_spec.rb +57 -0
- data/spec/concurrent/future_spec.rb +125 -115
- data/spec/concurrent/goroutine_spec.rb +67 -52
- data/spec/concurrent/obligation_shared.rb +121 -121
- data/spec/concurrent/promise_spec.rb +299 -310
- data/spec/concurrent/smart_mutex_spec.rb +234 -0
- data/spec/concurrent/thread_pool_shared.rb +209 -209
- data/spec/concurrent/utilities_spec.rb +74 -0
- data/spec/spec_helper.rb +21 -19
- metadata +38 -14
- checksums.yaml +0 -7
@@ -1,89 +1,89 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/thread_pool'
|
4
|
-
require 'concurrent/event'
|
5
|
-
|
6
|
-
module Concurrent
|
7
|
-
|
8
|
-
def self.new_fixed_thread_pool(size)
|
9
|
-
return FixedThreadPool.new(size)
|
10
|
-
end
|
11
|
-
|
12
|
-
class FixedThreadPool < ThreadPool
|
13
|
-
behavior(:thread_pool)
|
14
|
-
|
15
|
-
MIN_POOL_SIZE = 1
|
16
|
-
MAX_POOL_SIZE = 1024
|
17
|
-
|
18
|
-
def initialize(size)
|
19
|
-
super()
|
20
|
-
if size < MIN_POOL_SIZE || size > MAX_POOL_SIZE
|
21
|
-
raise ArgumentError.new("size must be between #{MIN_POOL_SIZE} and #{MAX_POOL_SIZE}")
|
22
|
-
end
|
23
|
-
|
24
|
-
@pool = size.times.collect{ create_worker_thread }
|
25
|
-
collect_garbage
|
26
|
-
end
|
27
|
-
|
28
|
-
def kill
|
29
|
-
@status = :killed
|
30
|
-
@pool.each{|t| Thread.kill(t) }
|
31
|
-
end
|
32
|
-
|
33
|
-
def size
|
34
|
-
if running?
|
35
|
-
return @pool.length
|
36
|
-
else
|
37
|
-
return 0
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def post(*args, &block)
|
42
|
-
raise ArgumentError.new('no block given') unless block_given?
|
43
|
-
if running?
|
44
|
-
@queue << [args, block]
|
45
|
-
return true
|
46
|
-
else
|
47
|
-
return false
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# @private
|
52
|
-
def status # :nodoc:
|
53
|
-
@pool.collect{|t| t.status }
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
# @private
|
59
|
-
def create_worker_thread # :nodoc:
|
60
|
-
Thread.new do
|
61
|
-
loop do
|
62
|
-
task = @queue.pop
|
63
|
-
if task == :stop
|
64
|
-
break
|
65
|
-
else
|
66
|
-
task.last.call(*task.first)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
@pool.delete(Thread.current)
|
70
|
-
if @pool.empty?
|
71
|
-
@termination.set
|
72
|
-
@status = :shutdown unless killed?
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# @private
|
78
|
-
def collect_garbage # :nodoc:
|
79
|
-
@collector = Thread.new do
|
80
|
-
sleep(1)
|
81
|
-
@pool.size.times do |i|
|
82
|
-
if @pool[i].status.nil?
|
83
|
-
@pool[i] = create_worker_thread
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/thread_pool'
|
4
|
+
require 'concurrent/event'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
def self.new_fixed_thread_pool(size)
|
9
|
+
return FixedThreadPool.new(size)
|
10
|
+
end
|
11
|
+
|
12
|
+
class FixedThreadPool < ThreadPool
|
13
|
+
behavior(:thread_pool)
|
14
|
+
|
15
|
+
MIN_POOL_SIZE = 1
|
16
|
+
MAX_POOL_SIZE = 1024
|
17
|
+
|
18
|
+
def initialize(size)
|
19
|
+
super()
|
20
|
+
if size < MIN_POOL_SIZE || size > MAX_POOL_SIZE
|
21
|
+
raise ArgumentError.new("size must be between #{MIN_POOL_SIZE} and #{MAX_POOL_SIZE}")
|
22
|
+
end
|
23
|
+
|
24
|
+
@pool = size.times.collect{ create_worker_thread }
|
25
|
+
collect_garbage
|
26
|
+
end
|
27
|
+
|
28
|
+
def kill
|
29
|
+
@status = :killed
|
30
|
+
@pool.each{|t| Thread.kill(t) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
if running?
|
35
|
+
return @pool.length
|
36
|
+
else
|
37
|
+
return 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def post(*args, &block)
|
42
|
+
raise ArgumentError.new('no block given') unless block_given?
|
43
|
+
if running?
|
44
|
+
@queue << [args, block]
|
45
|
+
return true
|
46
|
+
else
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @private
|
52
|
+
def status # :nodoc:
|
53
|
+
@pool.collect{|t| t.status }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# @private
|
59
|
+
def create_worker_thread # :nodoc:
|
60
|
+
Thread.new do
|
61
|
+
loop do
|
62
|
+
task = @queue.pop
|
63
|
+
if task == :stop
|
64
|
+
break
|
65
|
+
else
|
66
|
+
task.last.call(*task.first)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@pool.delete(Thread.current)
|
70
|
+
if @pool.empty?
|
71
|
+
@termination.set
|
72
|
+
@status = :shutdown unless killed?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @private
|
78
|
+
def collect_garbage # :nodoc:
|
79
|
+
@collector = Thread.new do
|
80
|
+
sleep(1)
|
81
|
+
@pool.size.times do |i|
|
82
|
+
if @pool[i].status.nil?
|
83
|
+
@pool[i] = create_worker_thread
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'concurrent/agent'
|
2
|
+
require 'concurrent/defer'
|
3
|
+
require 'concurrent/future'
|
4
|
+
require 'concurrent/promise'
|
5
|
+
|
6
|
+
module Kernel
|
7
|
+
|
8
|
+
## agent
|
9
|
+
|
10
|
+
def agent(initial, timeout = Concurrent::Agent::TIMEOUT)
|
11
|
+
return Concurrent::Agent.new(initial, timeout)
|
12
|
+
end
|
13
|
+
module_function :agent
|
14
|
+
|
15
|
+
def post(agent, &block)
|
16
|
+
if agent.respond_to?(:post)
|
17
|
+
return agent.post(&block)
|
18
|
+
else
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
module_function :post
|
23
|
+
|
24
|
+
## defer
|
25
|
+
|
26
|
+
def defer(*args, &block)
|
27
|
+
return Concurrent::Defer.new(*args, &block)
|
28
|
+
end
|
29
|
+
module_function :defer
|
30
|
+
|
31
|
+
## executor
|
32
|
+
|
33
|
+
def executor(*args, &block)
|
34
|
+
return Concurrent::Executor.run(*args, &block)
|
35
|
+
end
|
36
|
+
module_function :executor
|
37
|
+
|
38
|
+
## future
|
39
|
+
|
40
|
+
def future(*args, &block)
|
41
|
+
return Concurrent::Future.new(*args, &block)
|
42
|
+
end
|
43
|
+
module_function :future
|
44
|
+
|
45
|
+
## obligation
|
46
|
+
|
47
|
+
def deref(obligation, timeout = nil)
|
48
|
+
if obligation.respond_to?(:deref)
|
49
|
+
return obligation.deref(timeout)
|
50
|
+
elsif obligation.respond_to?(:value)
|
51
|
+
return obligation.deref(timeout)
|
52
|
+
else
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
module_function :deref
|
57
|
+
|
58
|
+
def pending?(obligation)
|
59
|
+
if obligation.respond_to?(:pending?)
|
60
|
+
return obligation.pending?
|
61
|
+
else
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
module_function :pending?
|
66
|
+
|
67
|
+
def fulfilled?(obligation)
|
68
|
+
if obligation.respond_to?(:fulfilled?)
|
69
|
+
return obligation.fulfilled?
|
70
|
+
elsif obligation.respond_to?(:realized?)
|
71
|
+
return obligation.realized?
|
72
|
+
else
|
73
|
+
return false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
module_function :fulfilled?
|
77
|
+
|
78
|
+
def realized?(obligation)
|
79
|
+
if obligation.respond_to?(:realized?)
|
80
|
+
return obligation.realized?
|
81
|
+
elsif obligation.respond_to?(:fulfilled?)
|
82
|
+
return obligation.fulfilled?
|
83
|
+
else
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
module_function :realized?
|
88
|
+
|
89
|
+
def rejected?(obligation)
|
90
|
+
if obligation.respond_to?(:rejected?)
|
91
|
+
return obligation.rejected?
|
92
|
+
else
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
module_function :rejected?
|
97
|
+
|
98
|
+
## promise
|
99
|
+
|
100
|
+
# Creates a new promise object. "A promise represents the eventual
|
101
|
+
# value returned from the single completion of an operation."
|
102
|
+
# Promises can be chained in a tree structure where each promise
|
103
|
+
# has zero or more children. Promises are resolved asynchronously
|
104
|
+
# in the order they are added to the tree. Parents are guaranteed
|
105
|
+
# to be resolved before their children. The result of each promise
|
106
|
+
# is passes to each of its children when the child resolves. When
|
107
|
+
# a promise is rejected all its children will be summarily rejected.
|
108
|
+
# A promise added to a rejected promise will immediately be rejected.
|
109
|
+
# A promise that is neither resolved or rejected is pending.
|
110
|
+
#
|
111
|
+
# @param args [Array] zero or more arguments for the block
|
112
|
+
# @param block [Proc] the block to call when attempting fulfillment
|
113
|
+
#
|
114
|
+
# @see Promise
|
115
|
+
# @see http://wiki.commonjs.org/wiki/Promises/A
|
116
|
+
def promise(*args, &block)
|
117
|
+
return Concurrent::Promise.new(*args, &block)
|
118
|
+
end
|
119
|
+
module_function :promise
|
120
|
+
end
|
data/lib/concurrent/future.rb
CHANGED
@@ -1,42 +1,52 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/
|
4
|
-
require 'concurrent/
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/global_thread_pool'
|
4
|
+
require 'concurrent/obligation'
|
5
|
+
require 'concurrent/utilities'
|
6
|
+
|
7
|
+
module Concurrent
|
8
|
+
|
9
|
+
class Future
|
10
|
+
include Obligation
|
11
|
+
behavior(:future)
|
12
|
+
|
13
|
+
def initialize(*args, &block)
|
14
|
+
if args.first.behaves_as?(:global_thread_pool)
|
15
|
+
thread_pool = args.first
|
16
|
+
args = args.slice(1, args.length)
|
17
|
+
else
|
18
|
+
thread_pool = $GLOBAL_THREAD_POOL
|
19
|
+
end
|
20
|
+
|
21
|
+
unless block_given?
|
22
|
+
@state = :fulfilled
|
23
|
+
else
|
24
|
+
@value = nil
|
25
|
+
@state = :pending
|
26
|
+
thread_pool.post do
|
27
|
+
Thread.pass
|
28
|
+
work(*args, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @private
|
36
|
+
def work(*args) # :nodoc:
|
37
|
+
semaphore.synchronize do
|
38
|
+
begin
|
39
|
+
atomic {
|
40
|
+
@value = yield(*args)
|
41
|
+
@state = :fulfilled
|
42
|
+
}
|
43
|
+
rescue Exception => ex
|
44
|
+
atomic {
|
45
|
+
@state = :rejected
|
46
|
+
@reason = ex
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
require 'concurrent/cached_thread_pool'
|
2
|
-
|
3
|
-
$GLOBAL_THREAD_POOL ||= Concurrent::CachedThreadPool.new
|
1
|
+
require 'concurrent/cached_thread_pool'
|
2
|
+
|
3
|
+
$GLOBAL_THREAD_POOL ||= Concurrent::CachedThreadPool.new
|
data/lib/concurrent/goroutine.rb
CHANGED
@@ -1,25 +1,29 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/global_thread_pool'
|
4
|
-
|
5
|
-
module Kernel
|
6
|
-
|
7
|
-
# Post the given agruments and block to the Global Thread Pool.
|
8
|
-
#
|
9
|
-
# @param args [Array] zero or more arguments for the block
|
10
|
-
# @param block [Proc] operation to be performed concurrently
|
11
|
-
#
|
12
|
-
# @return [true,false] success/failre of thread creation
|
13
|
-
#
|
14
|
-
# @note Althought based on Go's goroutines and Erlang's spawn/1,
|
15
|
-
# Ruby has a vastly different runtime. Threads aren't nearly as
|
16
|
-
# efficient in Ruby. Use this function appropriately.
|
17
|
-
#
|
18
|
-
# @see http://golang.org/doc/effective_go.html#goroutines
|
19
|
-
# @see https://gobyexample.com/goroutines
|
20
|
-
def go(*args, &block)
|
21
|
-
return false unless block_given?
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/global_thread_pool'
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
|
7
|
+
# Post the given agruments and block to the Global Thread Pool.
|
8
|
+
#
|
9
|
+
# @param args [Array] zero or more arguments for the block
|
10
|
+
# @param block [Proc] operation to be performed concurrently
|
11
|
+
#
|
12
|
+
# @return [true,false] success/failre of thread creation
|
13
|
+
#
|
14
|
+
# @note Althought based on Go's goroutines and Erlang's spawn/1,
|
15
|
+
# Ruby has a vastly different runtime. Threads aren't nearly as
|
16
|
+
# efficient in Ruby. Use this function appropriately.
|
17
|
+
#
|
18
|
+
# @see http://golang.org/doc/effective_go.html#goroutines
|
19
|
+
# @see https://gobyexample.com/goroutines
|
20
|
+
def go(*args, &block)
|
21
|
+
return false unless block_given?
|
22
|
+
if args.first.behaves_as?(:global_thread_pool)
|
23
|
+
args.first.post(*args.slice(1, args.length), &block)
|
24
|
+
else
|
25
|
+
$GLOBAL_THREAD_POOL.post(*args, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
module_function :go
|
29
|
+
end
|