concurrent-ruby 0.7.0-x86-linux → 0.7.1-x86-linux
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 +8 -8
- data/CHANGELOG.md +138 -0
- data/README.md +73 -105
- data/lib/1.9/concurrent_ruby_ext.so +0 -0
- data/lib/2.0/concurrent_ruby_ext.so +0 -0
- data/lib/concurrent/actor.rb +11 -12
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +1 -1
- data/lib/concurrent/actor/behaviour/linking.rb +4 -1
- data/lib/concurrent/actor/behaviour/pausing.rb +2 -2
- data/lib/concurrent/actor/behaviour/supervised.rb +2 -1
- data/lib/concurrent/actor/behaviour/termination.rb +1 -1
- data/lib/concurrent/actor/context.rb +2 -1
- data/lib/concurrent/actor/core.rb +7 -3
- data/lib/concurrent/actor/utils/balancer.rb +4 -2
- data/lib/concurrent/actor/utils/pool.rb +1 -1
- data/lib/concurrent/agent.rb +1 -22
- data/lib/concurrent/async.rb +1 -79
- data/lib/concurrent/atomic.rb +1 -1
- data/lib/concurrent/atomic/thread_local_var.rb +71 -24
- data/lib/concurrent/atomics.rb +0 -1
- data/lib/concurrent/configuration.rb +11 -5
- data/lib/concurrent/dataflow.rb +1 -30
- data/lib/concurrent/dereferenceable.rb +9 -2
- data/lib/concurrent/executor/indirect_immediate_executor.rb +46 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +2 -4
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +24 -22
- data/lib/concurrent/executor/thread_pool_executor.rb +2 -0
- data/lib/concurrent/executor/timer_set.rb +7 -8
- data/lib/concurrent/executors.rb +1 -0
- data/lib/concurrent/future.rb +7 -29
- data/lib/concurrent/ivar.rb +9 -0
- data/lib/concurrent/logging.rb +3 -0
- data/lib/concurrent/mvar.rb +26 -9
- data/lib/concurrent/observable.rb +33 -0
- data/lib/concurrent/promise.rb +59 -1
- data/lib/concurrent/scheduled_task.rb +1 -0
- data/lib/concurrent/timer_task.rb +18 -18
- data/lib/concurrent/tvar.rb +2 -0
- data/lib/concurrent/version.rb +1 -1
- data/lib/concurrent_ruby_ext.so +0 -0
- metadata +21 -4
@@ -5,8 +5,8 @@ module Concurrent
|
|
5
5
|
# Allows to pause actors on errors.
|
6
6
|
# When paused all arriving messages are collected and processed after the actor
|
7
7
|
# is resumed or reset. Resume will simply continue with next message.
|
8
|
-
# Reset also reinitialized context.
|
9
|
-
#
|
8
|
+
# Reset also reinitialized context.
|
9
|
+
# TODO example
|
10
10
|
class Pausing < Abstract
|
11
11
|
def initialize(core, subsequent)
|
12
12
|
super core, subsequent
|
@@ -3,7 +3,8 @@ module Concurrent
|
|
3
3
|
module Behaviour
|
4
4
|
|
5
5
|
# Sets and holds the supervisor of the actor if any. There is at most one supervisor
|
6
|
-
# for each actor. Each supervisor is automatically linked.
|
6
|
+
# for each actor. Each supervisor is automatically linked. Messages:
|
7
|
+
# `:pause!, :resume!, :reset!, :restart!` are accepted only from supervisor.
|
7
8
|
class Supervised < Abstract
|
8
9
|
attr_reader :supervisor
|
9
10
|
|
@@ -115,7 +115,8 @@ module Concurrent
|
|
115
115
|
undef_method :spawn
|
116
116
|
end
|
117
117
|
|
118
|
-
# Basic Context of an Actor.
|
118
|
+
# Basic Context of an Actor. It does not support supervision and pausing.
|
119
|
+
# It simply terminates on error.
|
119
120
|
#
|
120
121
|
# - linking
|
121
122
|
# - terminates on error
|
@@ -46,10 +46,14 @@ module Concurrent
|
|
46
46
|
synchronize do
|
47
47
|
@mailbox = Array.new
|
48
48
|
@serialized_execution = SerializedExecution.new
|
49
|
-
@executor = Type! opts.fetch(:executor, Concurrent.configuration.global_task_pool), Executor
|
50
49
|
@children = Set.new
|
51
|
-
|
50
|
+
|
51
|
+
@context_class = Child! opts.fetch(:class), AbstractContext
|
52
52
|
allocate_context
|
53
|
+
|
54
|
+
@executor = Type! opts.fetch(:executor, Concurrent.configuration.global_task_pool), Executor
|
55
|
+
raise ArgumentError, 'ImmediateExecutor is not supported' if @executor.is_a? ImmediateExecutor
|
56
|
+
|
53
57
|
@reference = (Child! opts[:reference_class] || @context.default_reference_class, Reference).new self
|
54
58
|
@name = (Type! opts.fetch(:name), String, Symbol).to_s
|
55
59
|
|
@@ -82,7 +86,7 @@ module Concurrent
|
|
82
86
|
handle_envelope Envelope.new(message, nil, parent, reference)
|
83
87
|
end
|
84
88
|
|
85
|
-
initialized.set
|
89
|
+
initialized.set reference if initialized
|
86
90
|
rescue => ex
|
87
91
|
log ERROR, ex
|
88
92
|
@first_behaviour.terminate!
|
@@ -4,6 +4,7 @@ module Concurrent
|
|
4
4
|
|
5
5
|
# Distributes messages between subscribed actors. Each actor'll get only one message then
|
6
6
|
# it's unsubscribed. The actor needs to resubscribe when it's ready to receive next message.
|
7
|
+
# It will buffer the messages if there is no worker registered.
|
7
8
|
# @see Pool
|
8
9
|
class Balancer < RestartingContext
|
9
10
|
|
@@ -24,14 +25,15 @@ module Concurrent
|
|
24
25
|
when :subscribed?
|
25
26
|
@receivers.include? envelope.sender
|
26
27
|
else
|
27
|
-
@buffer <<
|
28
|
+
@buffer << envelope
|
28
29
|
distribute
|
30
|
+
Behaviour::MESSAGE_PROCESSED
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
32
34
|
def distribute
|
33
35
|
while !@receivers.empty? && !@buffer.empty?
|
34
|
-
@receivers.shift
|
36
|
+
redirect @receivers.shift, @buffer.shift
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
data/lib/concurrent/agent.rb
CHANGED
@@ -8,28 +8,7 @@ require 'concurrent/logging'
|
|
8
8
|
|
9
9
|
module Concurrent
|
10
10
|
|
11
|
-
#
|
12
|
-
# of the agent can be requested at any time (`#deref`). Each agent has a work queue and operates on
|
13
|
-
# the global thread pool. Consumers can `#post` code blocks to the agent. The code block (function)
|
14
|
-
# will receive the current value of the agent as its sole parameter. The return value of the block
|
15
|
-
# will become the new value of the agent. Agents support two error handling modes: fail and continue.
|
16
|
-
# A good example of an agent is a shared incrementing counter, such as the score in a video game.
|
17
|
-
#
|
18
|
-
# @example Basic usage
|
19
|
-
# score = Concurrent::Agent.new(10)
|
20
|
-
# score.value #=> 10
|
21
|
-
#
|
22
|
-
# score << proc{|current| current + 100 }
|
23
|
-
# sleep(0.1)
|
24
|
-
# score.value #=> 110
|
25
|
-
#
|
26
|
-
# score << proc{|current| current * 2 }
|
27
|
-
# sleep(0.1)
|
28
|
-
# score.value #=> 220
|
29
|
-
#
|
30
|
-
# score << proc{|current| current - 50 }
|
31
|
-
# sleep(0.1)
|
32
|
-
# score.value #=> 170
|
11
|
+
# {include:file:doc/agent.md}
|
33
12
|
#
|
34
13
|
# @!attribute [r] timeout
|
35
14
|
# @return [Fixnum] the maximum number of seconds before an update is cancelled
|
data/lib/concurrent/async.rb
CHANGED
@@ -8,85 +8,7 @@ require 'concurrent/executor/serialized_execution'
|
|
8
8
|
|
9
9
|
module Concurrent
|
10
10
|
|
11
|
-
#
|
12
|
-
# class/object or object.
|
13
|
-
#
|
14
|
-
# Scenario:
|
15
|
-
# As a stateful, plain old Ruby class/object
|
16
|
-
# I want safe, asynchronous behavior
|
17
|
-
# So my long-running methods don't block the main thread
|
18
|
-
#
|
19
|
-
# Stateful, mutable objects must be managed carefully when used asynchronously.
|
20
|
-
# But Ruby is an object-oriented language so designing with objects and classes
|
21
|
-
# plays to Ruby's strengths and is often more natural to many Ruby programmers.
|
22
|
-
# The `Async` module is a way to mix simple yet powerful asynchronous capabilities
|
23
|
-
# into any plain old Ruby object or class. These capabilities provide a reasonable
|
24
|
-
# level of thread safe guarantees when used correctly.
|
25
|
-
#
|
26
|
-
# When this module is mixed into a class or object it provides to new methods:
|
27
|
-
# `async` and `await`. These methods are thread safe with respect to the enclosing
|
28
|
-
# object. The former method allows methods to be called asynchronously by posting
|
29
|
-
# to the global thread pool. The latter allows a method to be called synchronously
|
30
|
-
# on the current thread but does so safely with respect to any pending asynchronous
|
31
|
-
# method calls. Both methods return an `Obligation` which can be inspected for
|
32
|
-
# the result of the method call. Calling a method with `async` will return a
|
33
|
-
# `:pending` `Obligation` whereas `await` will return a `:complete` `Obligation`.
|
34
|
-
#
|
35
|
-
# Very loosely based on the `async` and `await` keywords in C#.
|
36
|
-
#
|
37
|
-
# @example Defining an asynchronous class
|
38
|
-
# class Echo
|
39
|
-
# include Concurrent::Async
|
40
|
-
#
|
41
|
-
# def initialize
|
42
|
-
# init_mutex # initialize the internal synchronization objects
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# def echo(msg)
|
46
|
-
# sleep(rand)
|
47
|
-
# print "#{msg}\n"
|
48
|
-
# nil
|
49
|
-
# end
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# horn = Echo.new
|
53
|
-
# horn.echo('zero') # synchronous, not thread-safe
|
54
|
-
#
|
55
|
-
# horn.async.echo('one') # asynchronous, non-blocking, thread-safe
|
56
|
-
# horn.await.echo('two') # synchronous, blocking, thread-safe
|
57
|
-
#
|
58
|
-
# @example Monkey-patching an existing object
|
59
|
-
# numbers = 1_000_000.times.collect{ rand }
|
60
|
-
# numbers.extend(Concurrent::Async)
|
61
|
-
# numbers.init_mutex # initialize the internal synchronization objects
|
62
|
-
#
|
63
|
-
# future = numbers.async.max
|
64
|
-
# future.state #=> :pending
|
65
|
-
#
|
66
|
-
# sleep(2)
|
67
|
-
#
|
68
|
-
# future.state #=> :fulfilled
|
69
|
-
# future.value #=> 0.999999138918843
|
70
|
-
#
|
71
|
-
# @note This module depends on several internal synchronization objects that
|
72
|
-
# must be initialized prior to calling any of the async/await/executor methods.
|
73
|
-
# The best practice is to call `init_mutex` from within the constructor
|
74
|
-
# of the including class. A less ideal but acceptable practice is for the
|
75
|
-
# thread creating the asynchronous object to explicitly call the `init_mutex`
|
76
|
-
# method prior to calling any of the async/await/executor methods. If
|
77
|
-
# `init_mutex` is *not* called explicitly the async/await/executor methods
|
78
|
-
# will raize a `Concurrent::InitializationError`. This is the only way
|
79
|
-
# thread-safe initialization can be guaranteed.
|
80
|
-
#
|
81
|
-
# @note Thread safe guarantees can only be made when asynchronous method calls
|
82
|
-
# are not mixed with synchronous method calls. Use only synchronous calls
|
83
|
-
# when the object is used exclusively on a single thread. Use only
|
84
|
-
# `async` and `await` when the object is shared between threads. Once you
|
85
|
-
# call a method using `async`, you should no longer call any methods
|
86
|
-
# directly on the object. Use `async` and `await` exclusively from then on.
|
87
|
-
# With careful programming it is possible to switch back and forth but it's
|
88
|
-
# also very easy to create race conditions and break your application.
|
89
|
-
# Basically, it's "async all the way down."
|
11
|
+
# {include:file:doc/async.md}
|
90
12
|
#
|
91
13
|
# @since 0.6.0
|
92
14
|
#
|
data/lib/concurrent/atomic.rb
CHANGED
@@ -25,7 +25,7 @@ begin
|
|
25
25
|
|
26
26
|
require "concurrent/atomic_reference/#{ruby_engine}"
|
27
27
|
rescue LoadError
|
28
|
-
warn 'Compiled extensions not installed, pure Ruby Atomic will be used.'
|
28
|
+
#warn 'Compiled extensions not installed, pure Ruby Atomic will be used.'
|
29
29
|
end
|
30
30
|
|
31
31
|
if defined? Concurrent::JavaAtomic
|
@@ -2,41 +2,83 @@ require 'concurrent/atomic'
|
|
2
2
|
|
3
3
|
module Concurrent
|
4
4
|
|
5
|
-
|
5
|
+
# @!macro [attach] abstract_thread_local_var
|
6
|
+
# A `ThreadLocalVar` is a variable where the value is different for each thread.
|
7
|
+
# Each variable may have a default value, but when you modify the variable only
|
8
|
+
# the current thread will ever see that change.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# v = ThreadLocalVar.new(14)
|
12
|
+
# v.value #=> 14
|
13
|
+
# v.value = 2
|
14
|
+
# v.value #=> 2
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# v = ThreadLocalVar.new(14)
|
18
|
+
#
|
19
|
+
# t1 = Thread.new do
|
20
|
+
# v.value #=> 14
|
21
|
+
# v.value = 1
|
22
|
+
# v.value #=> 1
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# t2 = Thread.new do
|
26
|
+
# v.value #=> 14
|
27
|
+
# v.value = 2
|
28
|
+
# v.value #=> 2
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# v.value #=> 14
|
32
|
+
class AbstractThreadLocalVar
|
6
33
|
|
7
|
-
|
8
|
-
@storage = Atomic.new Hash.new
|
9
|
-
end
|
34
|
+
module ThreadLocalRubyStorage
|
10
35
|
|
11
|
-
|
12
|
-
@storage.get[Thread.current]
|
13
|
-
end
|
36
|
+
protected
|
14
37
|
|
15
|
-
|
16
|
-
|
17
|
-
|
38
|
+
unless RUBY_PLATFORM == 'java'
|
39
|
+
require 'ref'
|
40
|
+
end
|
18
41
|
|
19
|
-
|
42
|
+
def allocate_storage
|
43
|
+
@storage = Ref::WeakKeyMap.new
|
44
|
+
end
|
20
45
|
|
21
|
-
|
46
|
+
def get
|
47
|
+
@storage[Thread.current]
|
48
|
+
end
|
22
49
|
|
23
|
-
|
50
|
+
def set(value, &block)
|
51
|
+
key = Thread.current
|
24
52
|
|
25
|
-
|
26
|
-
@var = java.lang.ThreadLocal.new
|
27
|
-
end
|
53
|
+
@storage[key] = value
|
28
54
|
|
29
|
-
|
30
|
-
|
55
|
+
if block_given?
|
56
|
+
begin
|
57
|
+
block.call
|
58
|
+
ensure
|
59
|
+
@storage.delete key
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
31
63
|
end
|
32
64
|
|
33
|
-
|
34
|
-
@var.set(value)
|
35
|
-
end
|
65
|
+
module ThreadLocalJavaStorage
|
36
66
|
|
37
|
-
|
67
|
+
protected
|
38
68
|
|
39
|
-
|
69
|
+
def allocate_storage
|
70
|
+
@var = java.lang.ThreadLocal.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def get
|
74
|
+
@var.get
|
75
|
+
end
|
76
|
+
|
77
|
+
def set(value)
|
78
|
+
@var.set(value)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
40
82
|
|
41
83
|
NIL_SENTINEL = Object.new
|
42
84
|
|
@@ -58,19 +100,24 @@ module Concurrent
|
|
58
100
|
end
|
59
101
|
|
60
102
|
def value=(value)
|
103
|
+
bind value
|
104
|
+
end
|
105
|
+
|
106
|
+
def bind(value, &block)
|
61
107
|
if value.nil?
|
62
108
|
stored_value = NIL_SENTINEL
|
63
109
|
else
|
64
110
|
stored_value = value
|
65
111
|
end
|
66
112
|
|
67
|
-
set stored_value
|
113
|
+
set stored_value, &block
|
68
114
|
|
69
115
|
value
|
70
116
|
end
|
71
117
|
|
72
118
|
end
|
73
119
|
|
120
|
+
# @!macro abstract_thread_local_var
|
74
121
|
class ThreadLocalVar < AbstractThreadLocalVar
|
75
122
|
if RUBY_PLATFORM == 'java'
|
76
123
|
include ThreadLocalJavaStorage
|
data/lib/concurrent/atomics.rb
CHANGED
@@ -7,5 +7,4 @@ require 'concurrent/atomic/copy_on_write_observer_set'
|
|
7
7
|
require 'concurrent/atomic/cyclic_barrier'
|
8
8
|
require 'concurrent/atomic/count_down_latch'
|
9
9
|
require 'concurrent/atomic/event'
|
10
|
-
require 'concurrent/atomic/thread_local_var'
|
11
10
|
require 'concurrent/atomic/synchronization'
|
@@ -17,6 +17,9 @@ module Concurrent
|
|
17
17
|
# lambda { |level, progname, message = nil, &block| _ }
|
18
18
|
attr_accessor :logger
|
19
19
|
|
20
|
+
# defines if executors should be auto-terminated in at_exit callback
|
21
|
+
attr_accessor :auto_terminate
|
22
|
+
|
20
23
|
# Create a new configuration object.
|
21
24
|
def initialize
|
22
25
|
immediate_executor = ImmediateExecutor.new
|
@@ -24,6 +27,7 @@ module Concurrent
|
|
24
27
|
@global_operation_pool = Delay.new(executor: immediate_executor) { new_operation_pool }
|
25
28
|
@global_timer_set = Delay.new(executor: immediate_executor) { Concurrent::TimerSet.new }
|
26
29
|
@logger = no_logger
|
30
|
+
@auto_terminate = true
|
27
31
|
end
|
28
32
|
|
29
33
|
# if assigned to {#logger}, it will log nothing.
|
@@ -129,6 +133,12 @@ module Concurrent
|
|
129
133
|
yield(configuration)
|
130
134
|
end
|
131
135
|
|
136
|
+
def self.finalize_global_executors
|
137
|
+
self.finalize_executor(self.configuration.global_timer_set)
|
138
|
+
self.finalize_executor(self.configuration.global_task_pool)
|
139
|
+
self.finalize_executor(self.configuration.global_operation_pool)
|
140
|
+
end
|
141
|
+
|
132
142
|
private
|
133
143
|
|
134
144
|
# Attempt to properly shutdown the given executor using the `shutdown` or
|
@@ -150,12 +160,8 @@ module Concurrent
|
|
150
160
|
false
|
151
161
|
end
|
152
162
|
|
153
|
-
|
154
163
|
# set exit hook to shutdown global thread pools
|
155
164
|
at_exit do
|
156
|
-
|
157
|
-
self.finalize_executor(self.configuration.global_task_pool)
|
158
|
-
self.finalize_executor(self.configuration.global_operation_pool)
|
159
|
-
# TODO may break other test suites using concurrent-ruby, terminates before test is run
|
165
|
+
finalize_global_executors if configuration.auto_terminate
|
160
166
|
end
|
161
167
|
end
|
data/lib/concurrent/dataflow.rb
CHANGED
@@ -19,36 +19,7 @@ module Concurrent
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
#
|
23
|
-
# data dependencies are available. Data dependencies are `Future` values. The
|
24
|
-
# dataflow task itself is also a `Future` value, so you can build up a graph of
|
25
|
-
# these tasks, each of which is run when all the data and other tasks it depends
|
26
|
-
# on are available or completed.
|
27
|
-
#
|
28
|
-
# Our syntax is somewhat related to that of Akka's `flow` and Habanero Java's
|
29
|
-
# `DataDrivenFuture`. However unlike Akka we don't schedule a task at all until
|
30
|
-
# it is ready to run, and unlike Habanero Java we pass the data values into the
|
31
|
-
# task instead of dereferencing them again in the task.
|
32
|
-
#
|
33
|
-
# The theory of dataflow goes back to the 80s. In the terminology of the literature,
|
34
|
-
# our implementation is coarse-grained, in that each task can be many instructions,
|
35
|
-
# and dynamic in that you can create more tasks within other tasks.
|
36
|
-
#
|
37
|
-
# @example Parallel Fibonacci calculator
|
38
|
-
# def fib(n)
|
39
|
-
# if n < 2
|
40
|
-
# Concurrent::dataflow { n }
|
41
|
-
# else
|
42
|
-
# n1 = fib(n - 1)
|
43
|
-
# n2 = fib(n - 2)
|
44
|
-
# Concurrent::dataflow(n1, n2) { |v1, v2| v1 + v2 }
|
45
|
-
# end
|
46
|
-
# end
|
47
|
-
#
|
48
|
-
# f = fib(14) #=> #<Concurrent::Future:0x000001019a26d8 ...
|
49
|
-
#
|
50
|
-
# # wait up to 1 second for the answer...
|
51
|
-
# f.value(1) #=> 377
|
22
|
+
# {include:file:doc/dataflow.md}
|
52
23
|
#
|
53
24
|
# @param [Future] inputs zero or more `Future` operations that this dataflow depends upon
|
54
25
|
#
|