concurrent-ruby 0.3.0.pre.1 → 0.3.0.pre.2
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.
- checksums.yaml +4 -4
- data/README.md +10 -33
- data/lib/concurrent.rb +5 -11
- data/lib/concurrent/{channel.rb → actor.rb} +14 -18
- data/lib/concurrent/agent.rb +5 -4
- data/lib/concurrent/cached_thread_pool.rb +116 -25
- data/lib/concurrent/cached_thread_pool/worker.rb +91 -0
- data/lib/concurrent/event.rb +13 -14
- data/lib/concurrent/event_machine_defer_proxy.rb +0 -1
- data/lib/concurrent/executor.rb +0 -1
- data/lib/concurrent/fixed_thread_pool.rb +111 -14
- data/lib/concurrent/fixed_thread_pool/worker.rb +54 -0
- data/lib/concurrent/future.rb +0 -2
- data/lib/concurrent/global_thread_pool.rb +21 -3
- data/lib/concurrent/goroutine.rb +1 -5
- data/lib/concurrent/obligation.rb +0 -19
- data/lib/concurrent/promise.rb +2 -5
- data/lib/concurrent/runnable.rb +2 -8
- data/lib/concurrent/supervisor.rb +9 -4
- data/lib/concurrent/utilities.rb +24 -0
- data/lib/concurrent/version.rb +1 -1
- data/md/agent.md +3 -3
- data/md/future.md +4 -4
- data/md/promise.md +15 -25
- data/md/thread_pool.md +9 -8
- data/spec/concurrent/actor_spec.rb +377 -0
- data/spec/concurrent/agent_spec.rb +2 -1
- data/spec/concurrent/cached_thread_pool_spec.rb +19 -29
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +1 -1
- data/spec/concurrent/event_spec.rb +1 -1
- data/spec/concurrent/executor_spec.rb +0 -8
- data/spec/concurrent/fixed_thread_pool_spec.rb +27 -16
- data/spec/concurrent/future_spec.rb +0 -13
- data/spec/concurrent/global_thread_pool_spec.rb +73 -0
- data/spec/concurrent/goroutine_spec.rb +0 -15
- data/spec/concurrent/obligation_shared.rb +1 -38
- data/spec/concurrent/promise_spec.rb +28 -47
- data/spec/concurrent/supervisor_spec.rb +1 -2
- data/spec/concurrent/thread_pool_shared.rb +28 -7
- data/spec/concurrent/utilities_spec.rb +50 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/functions.rb +17 -0
- metadata +12 -27
- data/lib/concurrent/functions.rb +0 -105
- data/lib/concurrent/null_thread_pool.rb +0 -25
- data/lib/concurrent/thread_pool.rb +0 -149
- data/md/reactor.md +0 -32
- data/spec/concurrent/channel_spec.rb +0 -446
- data/spec/concurrent/functions_spec.rb +0 -197
- data/spec/concurrent/null_thread_pool_spec.rb +0 -78
data/lib/concurrent/event.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require '
|
2
|
+
require 'concurrent/utilities'
|
3
3
|
|
4
4
|
module Concurrent
|
5
5
|
|
@@ -7,9 +7,8 @@ module Concurrent
|
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
@set = false
|
10
|
-
@notifier = Queue.new
|
11
10
|
@mutex = Mutex.new
|
12
|
-
@
|
11
|
+
@waiters = []
|
13
12
|
end
|
14
13
|
|
15
14
|
def set?
|
@@ -20,32 +19,32 @@ module Concurrent
|
|
20
19
|
return true if set?
|
21
20
|
@mutex.synchronize do
|
22
21
|
@set = true
|
23
|
-
@
|
24
|
-
@waiting = 0
|
22
|
+
@waiters.each {|waiter| waiter.run if waiter.status == 'sleep'}
|
25
23
|
end
|
26
24
|
return true
|
27
25
|
end
|
28
26
|
|
29
27
|
def reset
|
30
|
-
@mutex.synchronize { @set = false }
|
28
|
+
@mutex.synchronize { @set = false; @waiters.clear }
|
31
29
|
return true
|
32
30
|
end
|
33
31
|
|
34
32
|
def wait(timeout = nil)
|
35
33
|
return true if set?
|
36
34
|
|
37
|
-
@mutex.synchronize { @
|
35
|
+
@mutex.synchronize { @waiters << Thread.current }
|
36
|
+
return true if set? # if event was set while waiting for mutex
|
38
37
|
|
39
38
|
if timeout.nil?
|
40
|
-
|
39
|
+
slept = sleep
|
41
40
|
else
|
42
|
-
|
43
|
-
@notifier.pop
|
44
|
-
end
|
41
|
+
slept = sleep(timeout)
|
45
42
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
rescue
|
44
|
+
# let it fail
|
45
|
+
ensure
|
46
|
+
@mutex.synchronize { @waiters.delete(Thread.current) }
|
47
|
+
return set?
|
49
48
|
end
|
50
49
|
end
|
51
50
|
end
|
data/lib/concurrent/executor.rb
CHANGED
@@ -1,31 +1,128 @@
|
|
1
|
-
require '
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/event'
|
4
|
+
require 'concurrent/fixed_thread_pool/worker'
|
2
5
|
|
3
6
|
module Concurrent
|
4
7
|
|
5
|
-
class FixedThreadPool
|
6
|
-
|
8
|
+
class FixedThreadPool
|
9
|
+
|
10
|
+
MIN_POOL_SIZE = 1
|
11
|
+
MAX_POOL_SIZE = 256
|
12
|
+
|
13
|
+
attr_accessor :max_threads
|
7
14
|
|
8
15
|
def initialize(size, opts = {})
|
9
|
-
|
16
|
+
@max_threads = size || MAX_POOL_SIZE
|
17
|
+
if @max_threads < MIN_POOL_SIZE || @max_threads > MAX_POOL_SIZE
|
18
|
+
raise ArgumentError.new("size must be from #{MIN_POOL_SIZE} to #{MAX_POOL_SIZE}")
|
19
|
+
end
|
20
|
+
|
21
|
+
@state = :running
|
22
|
+
@pool = []
|
23
|
+
@terminator = Event.new
|
24
|
+
@queue = Queue.new
|
25
|
+
@mutex = Mutex.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def running?
|
29
|
+
return @state == :running
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait_for_termination(timeout = nil)
|
33
|
+
return @terminator.wait(timeout)
|
10
34
|
end
|
11
35
|
|
12
36
|
def post(*args, &block)
|
13
|
-
raise ArgumentError.new('no block given')
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
37
|
+
raise ArgumentError.new('no block given') if block.nil?
|
38
|
+
@mutex.synchronize do
|
39
|
+
break false unless @state == :running
|
40
|
+
@queue << [args, block]
|
41
|
+
clean_pool
|
42
|
+
fill_pool
|
43
|
+
true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def <<(block)
|
48
|
+
self.post(&block)
|
49
|
+
return self
|
50
|
+
end
|
51
|
+
|
52
|
+
def shutdown
|
53
|
+
@mutex.synchronize do
|
54
|
+
break unless @state == :running
|
55
|
+
if @pool.empty?
|
56
|
+
@state = :shutdown
|
57
|
+
@terminator.set
|
19
58
|
else
|
20
|
-
|
59
|
+
@state = :shuttingdown
|
60
|
+
@pool.length.times{ @queue << :stop }
|
21
61
|
end
|
22
62
|
end
|
23
63
|
end
|
24
64
|
|
25
|
-
|
65
|
+
def kill
|
66
|
+
@mutex.synchronize do
|
67
|
+
break if @state == :shutdown
|
68
|
+
@state = :shutdown
|
69
|
+
@queue.clear
|
70
|
+
drain_pool
|
71
|
+
@terminator.set
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def length
|
76
|
+
@mutex.synchronize do
|
77
|
+
@state == :running ? @pool.length : 0
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_worker_thread
|
82
|
+
wrkr = Worker.new(@queue, self)
|
83
|
+
Thread.new(wrkr, self) do |worker, parent|
|
84
|
+
Thread.current.abort_on_exception = false
|
85
|
+
worker.run
|
86
|
+
parent.on_worker_exit(worker)
|
87
|
+
end
|
88
|
+
return wrkr
|
89
|
+
end
|
90
|
+
|
91
|
+
def fill_pool
|
92
|
+
return unless @state == :running
|
93
|
+
while @pool.length < @max_threads
|
94
|
+
@pool << create_worker_thread
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def clean_pool
|
99
|
+
@pool.reject! {|worker| worker.dead? }
|
100
|
+
end
|
101
|
+
|
102
|
+
def drain_pool
|
103
|
+
@pool.each {|worker| worker.kill }
|
104
|
+
@pool.clear
|
105
|
+
end
|
26
106
|
|
27
|
-
def
|
28
|
-
|
107
|
+
def on_start_task(worker)
|
108
|
+
end
|
109
|
+
|
110
|
+
def on_end_task(worker)
|
111
|
+
@mutex.synchronize do
|
112
|
+
break unless @state == :running
|
113
|
+
clean_pool
|
114
|
+
fill_pool
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def on_worker_exit(worker)
|
119
|
+
@mutex.synchronize do
|
120
|
+
@pool.delete(worker)
|
121
|
+
if @pool.empty? && @state != :running
|
122
|
+
@state = :shutdown
|
123
|
+
@terminator.set
|
124
|
+
end
|
125
|
+
end
|
29
126
|
end
|
30
127
|
end
|
31
128
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
class FixedThreadPool
|
6
|
+
|
7
|
+
class Worker
|
8
|
+
|
9
|
+
def initialize(queue, parent)
|
10
|
+
@queue = queue
|
11
|
+
@parent = parent
|
12
|
+
@mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def dead?
|
16
|
+
return @mutex.synchronize do
|
17
|
+
@thread.nil? ? false : ! @thread.alive?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def kill
|
22
|
+
@mutex.synchronize do
|
23
|
+
Thread.kill(@thread) unless @thread.nil?
|
24
|
+
@thread = nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(thread = Thread.current)
|
29
|
+
@mutex.synchronize do
|
30
|
+
raise StandardError.new('already running') unless @thread.nil?
|
31
|
+
@thread = thread
|
32
|
+
end
|
33
|
+
|
34
|
+
loop do
|
35
|
+
task = @queue.pop
|
36
|
+
if task == :stop
|
37
|
+
@thread = nil
|
38
|
+
@parent.on_worker_exit(self)
|
39
|
+
break
|
40
|
+
end
|
41
|
+
|
42
|
+
@parent.on_start_task(self)
|
43
|
+
begin
|
44
|
+
task.last.call(*task.first)
|
45
|
+
rescue
|
46
|
+
# let it fail
|
47
|
+
ensure
|
48
|
+
@parent.on_end_task(self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/concurrent/future.rb
CHANGED
@@ -1,8 +1,24 @@
|
|
1
|
-
|
1
|
+
module Concurrent
|
2
2
|
|
3
|
-
|
3
|
+
class NullThreadPool
|
4
4
|
|
5
|
-
|
5
|
+
def self.post(*args)
|
6
|
+
Thread.new(*args) do
|
7
|
+
Thread.current.abort_on_exception = false
|
8
|
+
yield(*args)
|
9
|
+
end
|
10
|
+
return true
|
11
|
+
end
|
12
|
+
|
13
|
+
def post(*args, &block)
|
14
|
+
return NullThreadPool.post(*args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(block)
|
18
|
+
NullThreadPool.post(&block)
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
end
|
6
22
|
|
7
23
|
module UsesGlobalThreadPool
|
8
24
|
|
@@ -22,3 +38,5 @@ module Concurrent
|
|
22
38
|
end
|
23
39
|
end
|
24
40
|
end
|
41
|
+
|
42
|
+
$GLOBAL_THREAD_POOL ||= Concurrent::NullThreadPool.new
|
data/lib/concurrent/goroutine.rb
CHANGED
@@ -19,11 +19,7 @@ module Kernel
|
|
19
19
|
# @see https://gobyexample.com/goroutines
|
20
20
|
def go(*args, &block)
|
21
21
|
return false unless block_given?
|
22
|
-
|
23
|
-
args.first.post(*args.slice(1, args.length), &block)
|
24
|
-
else
|
25
|
-
$GLOBAL_THREAD_POOL.post(*args, &block)
|
26
|
-
end
|
22
|
+
$GLOBAL_THREAD_POOL.post(*args, &block)
|
27
23
|
end
|
28
24
|
module_function :go
|
29
25
|
end
|
@@ -1,27 +1,8 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'timeout'
|
3
|
-
require 'functional'
|
4
3
|
|
5
4
|
require 'concurrent/event'
|
6
5
|
|
7
|
-
behavior_info(:future,
|
8
|
-
state: 0,
|
9
|
-
value: -1,
|
10
|
-
reason: 0,
|
11
|
-
pending?: 0,
|
12
|
-
fulfilled?: 0,
|
13
|
-
rejected?: 0)
|
14
|
-
|
15
|
-
behavior_info(:promise,
|
16
|
-
state: 0,
|
17
|
-
value: -1,
|
18
|
-
reason: 0,
|
19
|
-
pending?: 0,
|
20
|
-
fulfilled?: 0,
|
21
|
-
rejected?: 0,
|
22
|
-
then: 0,
|
23
|
-
rescue: -1)
|
24
|
-
|
25
6
|
module Concurrent
|
26
7
|
|
27
8
|
module Obligation
|
data/lib/concurrent/promise.rb
CHANGED
@@ -9,9 +9,6 @@ module Concurrent
|
|
9
9
|
include Obligation
|
10
10
|
include UsesGlobalThreadPool
|
11
11
|
|
12
|
-
behavior(:future)
|
13
|
-
behavior(:promise)
|
14
|
-
|
15
12
|
# Creates a new promise object. "A promise represents the eventual
|
16
13
|
# value returned from the single completion of an operation."
|
17
14
|
# Promises can be chained in a tree structure where each promise
|
@@ -60,7 +57,7 @@ module Concurrent
|
|
60
57
|
# @return [Promise] the new promise
|
61
58
|
def then(&block)
|
62
59
|
child = @lock.synchronize do
|
63
|
-
block = Proc.new{|result| result }
|
60
|
+
block = Proc.new{|result| result } if block.nil?
|
64
61
|
@children << Promise.new(self, &block)
|
65
62
|
@children.last.on_reject(@reason) if rejected?
|
66
63
|
push(@children.last)
|
@@ -80,7 +77,7 @@ module Concurrent
|
|
80
77
|
#
|
81
78
|
# @return [self] so that additional chaining can occur
|
82
79
|
def rescue(clazz = nil, &block)
|
83
|
-
return self if fulfilled? || rescued? ||
|
80
|
+
return self if fulfilled? || rescued? || block.nil?
|
84
81
|
@lock.synchronize do
|
85
82
|
rescuer = Rescuer.new(clazz, block)
|
86
83
|
if pending?
|
data/lib/concurrent/runnable.rb
CHANGED
@@ -1,10 +1,4 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require 'functional'
|
3
|
-
|
4
|
-
behavior_info(:runnable,
|
5
|
-
run: 0,
|
6
|
-
stop: 0,
|
7
|
-
running?: 0)
|
8
2
|
|
9
3
|
module Concurrent
|
10
4
|
|
@@ -18,6 +12,8 @@ module Concurrent
|
|
18
12
|
Thread.abort_on_exception = false
|
19
13
|
runner.run
|
20
14
|
end
|
15
|
+
#HACK: more consistent on JRuby and Rbx
|
16
|
+
sleep(0.1)
|
21
17
|
end
|
22
18
|
end
|
23
19
|
|
@@ -52,8 +48,6 @@ module Concurrent
|
|
52
48
|
|
53
49
|
module Runnable
|
54
50
|
|
55
|
-
behavior(:runnable)
|
56
|
-
|
57
51
|
LifecycleError = Class.new(StandardError)
|
58
52
|
|
59
53
|
def self.included(base)
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require 'functional'
|
3
2
|
|
4
3
|
require 'concurrent/runnable'
|
5
4
|
|
@@ -7,12 +6,11 @@ module Concurrent
|
|
7
6
|
|
8
7
|
class Supervisor
|
9
8
|
|
10
|
-
behavior(:runnable)
|
11
|
-
|
12
9
|
DEFAULT_MONITOR_INTERVAL = 1
|
13
10
|
RESTART_STRATEGIES = [:one_for_one, :one_for_all, :rest_for_one]
|
14
11
|
DEFAULT_MAX_RESTART = 5
|
15
12
|
DEFAULT_MAX_TIME = 60
|
13
|
+
WORKER_API = {run: 0, stop: 0, running?: 0}
|
16
14
|
|
17
15
|
CHILD_TYPES = [:worker, :supervisor]
|
18
16
|
CHILD_RESTART_OPTIONS = [:permanent, :transient, :temporary]
|
@@ -148,7 +146,7 @@ module Concurrent
|
|
148
146
|
end
|
149
147
|
|
150
148
|
def add_worker(worker, opts = {})
|
151
|
-
return nil if worker.nil? || !
|
149
|
+
return nil if worker.nil? || ! behaves_as_worker?(worker)
|
152
150
|
return @mutex.synchronize {
|
153
151
|
restart = opts[:restart] || :permanent
|
154
152
|
type = opts[:type] || (worker.is_a?(Supervisor) ? :supervisor : nil) || :worker
|
@@ -222,6 +220,13 @@ module Concurrent
|
|
222
220
|
|
223
221
|
private
|
224
222
|
|
223
|
+
def behaves_as_worker?(obj)
|
224
|
+
return WORKER_API.each do |method, arity|
|
225
|
+
break(false) unless obj.respond_to?(method) && obj.method(method).arity == arity
|
226
|
+
true
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
225
230
|
def monitor
|
226
231
|
@workers.each{|context| run_worker(context)}
|
227
232
|
loop do
|