concurrent-ruby-edge 0.2.0.pre1 → 0.2.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -18
- data/lib/concurrent/actor/context.rb +1 -1
- data/lib/concurrent/actor/core.rb +1 -1
- data/lib/concurrent/channel/blocking_ring_buffer.rb +1 -1
- data/lib/concurrent/channel/buffered_channel.rb +2 -2
- data/lib/concurrent/channel/waitable_list.rb +1 -1
- data/lib/concurrent/edge/atomic_markable_reference.rb +12 -12
- data/lib/concurrent/edge/future.rb +12 -7
- data/lib/concurrent/edge/lock_free_linked_set.rb +6 -6
- data/lib/concurrent/edge/lock_free_linked_set/node.rb +17 -8
- data/lib/concurrent/edge/lock_free_linked_set/window.rb +3 -3
- data/lib/concurrent/edge/lock_free_stack.rb +17 -14
- metadata +5 -6
- data/lib/concurrent/agent.rb +0 -259
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c02c328ebe5aaf45aaf86106223e979107a4ea99
|
4
|
+
data.tar.gz: 425af0cce7f688a07e0546922c916877c2b4277a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 877ef401c5cb31943832dd1a27f592e65b261608744b1d1b00e0210f82d9ae7d246fd3d224a78cbe64aa1533cf88b5c77338985bcaf7a29a8ceb7f29174bbb32
|
7
|
+
data.tar.gz: d3aa66b036246d9af5f5ba0fff8ec7b46943a7c5d8fcbfcf52e4166c40a9b3fd1a80e335aaea539a756f21210a12ccd981243409d94a99fb9c4c874fe31d74bb
|
data/README.md
CHANGED
@@ -61,7 +61,7 @@ This library contains a variety of concurrency abstractions at high and low leve
|
|
61
61
|
|
62
62
|
#### General-purpose Concurrency Abstractions
|
63
63
|
|
64
|
-
* [Async](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Async.html): A mixin module that provides simple asynchronous behavior to
|
64
|
+
* [Async](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Async.html): A mixin module that provides simple asynchronous behavior to a class. Loosely based on Erlang's [gen_server](http://www.erlang.org/doc/man/gen_server.html).
|
65
65
|
* [Future](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Future.html): An asynchronous operation that produces a value.
|
66
66
|
* [Dataflow](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#dataflow-class_method): Built on Futures, Dataflow allows you to create a task that will be scheduled when all of its data dependencies are available.
|
67
67
|
* [Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html): Similar to Futures, with more features.
|
@@ -79,11 +79,9 @@ Collection classes that were originally part of the (deprecated) `thread_safe` g
|
|
79
79
|
|
80
80
|
Value objects inspired by other languages:
|
81
81
|
|
82
|
-
* [Atom](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Atom.html): A way to manage shared, synchronous, independent state.
|
83
82
|
* [Maybe](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Maybe.html) A thread-safe, immutable object representing an optional value, based on
|
84
83
|
[Haskell Data.Maybe](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html).
|
85
|
-
* [Delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html) Lazy evaluation of a block yielding an immutable result. Based on Clojure's
|
86
|
-
[delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html).
|
84
|
+
* [Delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html) Lazy evaluation of a block yielding an immutable result. Based on Clojure's [delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html).
|
87
85
|
|
88
86
|
Structure classes derived from Ruby's [Struct](http://ruby-doc.org/core-2.2.0/Struct.html):
|
89
87
|
|
@@ -93,10 +91,15 @@ Structure classes derived from Ruby's [Struct](http://ruby-doc.org/core-2.2.0/St
|
|
93
91
|
|
94
92
|
Thread-safe variables:
|
95
93
|
|
94
|
+
* [Agent](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Agent.html): A way to manage shared, mutable, *asynchronous*, independent, state. Based on Clojure's [Agent](http://clojure.org/agents).
|
95
|
+
* [Atom](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Atom.html): A way to manage shared, mutable, *synchronous*, independent state. Based on Clojure's [Atom](http://clojure.org/atoms).
|
96
96
|
* [AtomicBoolean](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicBoolean.html) A boolean value that can be updated atomically.
|
97
97
|
* [AtomicFixnum](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicFixnum.html) A numeric value that can be updated atomically.
|
98
98
|
* [AtomicReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MutexAtomic.html) An object reference that may be updated atomically.
|
99
|
+
* [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html) A synchronization point at which threads can pair and swap elements within pairs. Based on Java's [Exchanger](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html).
|
100
|
+
* [MVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MVar.html) A synchronized single element container. Based on Haskell's [MVar](https://hackage.haskell.org/package/base-4.8.1.0/docs/Control-Concurrent-MVar.html) and Scala's [MVar](http://docs.typelevel.org/api/scalaz/nightly/index.html#scalaz.concurrent.MVar$).
|
99
101
|
* [ThreadLocalVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html) A variable where the value is different for each thread.
|
102
|
+
* [TVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) A transactional variable implementing software transactional memory (STM). Based on Clojure's [Ref](http://clojure.org/refs).
|
100
103
|
|
101
104
|
#### Java-inspired ThreadPools and Other Executors
|
102
105
|
|
@@ -104,16 +107,13 @@ Thread-safe variables:
|
|
104
107
|
|
105
108
|
#### Thread Synchronization Classes and Algorithms
|
106
109
|
|
107
|
-
* [
|
110
|
+
* [CountDownLatch](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CountDownLatch.html) A synchronization object that allows one thread to wait on multiple other threads.
|
108
111
|
* [CyclicBarrier](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CyclicBarrier.html) A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.
|
109
112
|
* [Event](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Event.html) Old school kernel-style event.
|
110
|
-
* [
|
111
|
-
* [I-Structure](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/IVar.html) (IVar) Similar to a "future" but can be manually assigned once, after which it becomes immutable.
|
112
|
-
* [M-Structure](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MVar.html) (MVar) A synchronized single element container.
|
113
|
+
* [IVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/IVar.html) Similar to a "future" but can be manually assigned once, after which it becomes immutable.
|
113
114
|
* [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html) A lock that supports multiple readers but only one writer.
|
114
115
|
* [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReentrantReadWriteLock.html) A read/write lock with reentrant and upgrade features.
|
115
116
|
* [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html) A counting-based locking mechanism that uses permits.
|
116
|
-
* [Software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar) A transactional variable - a single-element container that is used as part of a transaction.
|
117
117
|
|
118
118
|
### Edge Features
|
119
119
|
|
@@ -125,12 +125,11 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
|
|
125
125
|
|
126
126
|
* [Actor](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor.html):
|
127
127
|
Implements the Actor Model, where concurrent actors exchange messages.
|
128
|
-
* [
|
129
|
-
|
130
|
-
`Promise`, `IVar`, `Event`, `
|
131
|
-
new synchronization layer to make all the features **non-blocking** and **lock-free
|
128
|
+
* [New Future Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/FutureShortcuts.html):
|
129
|
+
Unified implementation of futures and promises which combines features of previous `Future`,
|
130
|
+
`Promise`, `IVar`, `Event`, `dataflow`, `Delay`, and `TimerTask` into a single framework. It extensively uses the
|
131
|
+
new synchronization layer to make all the features **non-blocking** and **lock-free**, with the exception of obviously blocking
|
132
132
|
operations like `#wait`, `#value`. It also offers better performance.
|
133
|
-
* [Agent](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Agent.html): A single atomic value that represents an identity.
|
134
133
|
* [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Channel.html):
|
135
134
|
Communicating Sequential Processes (CSP).
|
136
135
|
* [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
|
@@ -142,12 +141,11 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
|
|
142
141
|
|
143
142
|
*Why are these not in core?*
|
144
143
|
|
145
|
-
- **Actor** - Partial documentation and tests; stability is good.
|
144
|
+
- **Actor** - Partial documentation and tests; depends on new future/promise framework; stability is good.
|
146
145
|
- **Future/Promise Framework** - API changes; partial documentation and tests; stability good.
|
147
|
-
- **
|
148
|
-
- **Channel** - Missing documentation; limted features; stability good.
|
146
|
+
- **Channel** - Missing documentation; limited features; stability good.
|
149
147
|
- **LazyRegister** - Missing documentation and tests.
|
150
|
-
- **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** -
|
148
|
+
- **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Need real world battle testing
|
151
149
|
|
152
150
|
## Usage
|
153
151
|
|
@@ -15,7 +15,7 @@ module Concurrent
|
|
15
15
|
# > {include:Actor::RestartingContext}
|
16
16
|
#
|
17
17
|
# Example of ac actor definition:
|
18
|
-
#
|
18
|
+
#
|
19
19
|
# {include:file:doc/actor/define.out.rb}
|
20
20
|
#
|
21
21
|
# See methods of {AbstractContext} what else can be tweaked, e.g {AbstractContext#default_reference_class}
|
@@ -10,7 +10,7 @@ module Concurrent
|
|
10
10
|
# @note Whole class should be considered private. An user should use {Context}s and {Reference}s only.
|
11
11
|
# @note devel: core should not block on anything, e.g. it cannot wait on children to terminate
|
12
12
|
# that would eat up all threads in task pool and deadlock
|
13
|
-
class Core < Synchronization::
|
13
|
+
class Core < Synchronization::LockableObject
|
14
14
|
include TypeCheck
|
15
15
|
include Concern::Logging
|
16
16
|
|
@@ -6,7 +6,7 @@ module Concurrent
|
|
6
6
|
|
7
7
|
# @api Channel
|
8
8
|
# @!macro edge_warning
|
9
|
-
class BufferedChannel < Synchronization::
|
9
|
+
class BufferedChannel < Synchronization::LockableObject
|
10
10
|
|
11
11
|
def initialize(size)
|
12
12
|
super()
|
@@ -14,7 +14,7 @@ module Concurrent
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def probe_set_size
|
17
|
-
@probe_set.size
|
17
|
+
@probe_set.size # TODO (pitr 12-Sep-2015): unsafe?
|
18
18
|
end
|
19
19
|
|
20
20
|
def buffer_queue_size
|
@@ -11,11 +11,11 @@ module Concurrent
|
|
11
11
|
# @api Edge
|
12
12
|
class AtomicMarkableReference < ::Concurrent::Synchronization::Object
|
13
13
|
|
14
|
+
private *attr_volatile_with_cas(:reference)
|
15
|
+
|
14
16
|
# @!macro [attach] atomic_markable_reference_method_initialize
|
15
17
|
def initialize(value = nil, mark = false)
|
16
|
-
super()
|
17
|
-
@Reference = AtomicReference.new ImmutableArray[value, mark]
|
18
|
-
ensure_ivar_visibility!
|
18
|
+
super(ImmutableArray[value, mark]) # ensures visibility
|
19
19
|
end
|
20
20
|
|
21
21
|
# @!macro [attach] atomic_markable_reference_method_compare_and_set
|
@@ -36,7 +36,7 @@ module Concurrent
|
|
36
36
|
def compare_and_set(expected_val, new_val, expected_mark, new_mark)
|
37
37
|
# Memoize a valid reference to the current AtomicReference for
|
38
38
|
# later comparison.
|
39
|
-
current =
|
39
|
+
current = reference
|
40
40
|
curr_val, curr_mark = current
|
41
41
|
|
42
42
|
# Ensure that that the expected marks match.
|
@@ -56,7 +56,7 @@ module Concurrent
|
|
56
56
|
|
57
57
|
prospect = ImmutableArray[new_val, new_mark]
|
58
58
|
|
59
|
-
|
59
|
+
compare_and_set_reference current, prospect
|
60
60
|
end
|
61
61
|
alias_method :compare_and_swap, :compare_and_set
|
62
62
|
|
@@ -66,7 +66,7 @@ module Concurrent
|
|
66
66
|
#
|
67
67
|
# @return [ImmutableArray] the current reference and marked values
|
68
68
|
def get
|
69
|
-
|
69
|
+
reference
|
70
70
|
end
|
71
71
|
|
72
72
|
# @!macro [attach] atomic_markable_reference_method_value
|
@@ -75,7 +75,7 @@ module Concurrent
|
|
75
75
|
#
|
76
76
|
# @return [Object] the current value of the reference
|
77
77
|
def value
|
78
|
-
|
78
|
+
reference[0]
|
79
79
|
end
|
80
80
|
|
81
81
|
# @!macro [attach] atomic_markable_reference_method_mark
|
@@ -84,7 +84,7 @@ module Concurrent
|
|
84
84
|
#
|
85
85
|
# @return [Boolean] the current marked value
|
86
86
|
def mark
|
87
|
-
|
87
|
+
reference[1]
|
88
88
|
end
|
89
89
|
alias_method :marked?, :mark
|
90
90
|
|
@@ -98,7 +98,7 @@ module Concurrent
|
|
98
98
|
#
|
99
99
|
# @return [ImmutableArray] both the new value and the new mark
|
100
100
|
def set(new_val, new_mark)
|
101
|
-
|
101
|
+
self.reference = ImmutableArray[new_val, new_mark]
|
102
102
|
end
|
103
103
|
|
104
104
|
# @!macro [attach] atomic_markable_reference_method_update
|
@@ -115,7 +115,7 @@ module Concurrent
|
|
115
115
|
# @return [ImmutableArray] the new value and new mark
|
116
116
|
def update
|
117
117
|
loop do
|
118
|
-
old_val, old_mark =
|
118
|
+
old_val, old_mark = reference
|
119
119
|
new_val, new_mark = yield old_val, old_mark
|
120
120
|
|
121
121
|
if compare_and_set old_val, new_val, old_mark, new_mark
|
@@ -139,7 +139,7 @@ module Concurrent
|
|
139
139
|
#
|
140
140
|
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
|
141
141
|
def try_update!
|
142
|
-
old_val, old_mark =
|
142
|
+
old_val, old_mark = reference
|
143
143
|
new_val, new_mark = yield old_val, old_mark
|
144
144
|
|
145
145
|
unless compare_and_set old_val, new_val, old_mark, new_mark
|
@@ -165,7 +165,7 @@ module Concurrent
|
|
165
165
|
# @return [ImmutableArray] the new value and marked state, or nil if
|
166
166
|
# the update failed
|
167
167
|
def try_update
|
168
|
-
old_val, old_mark =
|
168
|
+
old_val, old_mark = reference
|
169
169
|
new_val, new_mark = yield old_val, old_mark
|
170
170
|
|
171
171
|
return unless compare_and_set old_val, new_val, old_mark, new_mark
|
@@ -125,7 +125,8 @@ module Concurrent
|
|
125
125
|
include FutureShortcuts
|
126
126
|
|
127
127
|
# Represents an event which will happen in future (will be completed). It has to always happen.
|
128
|
-
class Event < Synchronization::
|
128
|
+
class Event < Synchronization::LockableObject
|
129
|
+
safe_initialization!
|
129
130
|
include Concern::Deprecation
|
130
131
|
|
131
132
|
# @!visibility private
|
@@ -167,14 +168,15 @@ module Concurrent
|
|
167
168
|
COMPLETED = Completed.new
|
168
169
|
|
169
170
|
def initialize(promise, default_executor)
|
171
|
+
super()
|
170
172
|
@Promise = promise
|
171
173
|
@DefaultExecutor = default_executor
|
172
174
|
@Touched = AtomicBoolean.new(false)
|
173
175
|
@Callbacks = LockFreeStack.new
|
174
|
-
|
176
|
+
# TODO (pitr 12-Sep-2015): replace with AtomicFixnum, avoid aba problem
|
177
|
+
# TODO (pitr 12-Sep-2015): look at java.util.concurrent solution
|
178
|
+
@Waiters = LockFreeStack.new
|
175
179
|
@State = AtomicReference.new PENDING
|
176
|
-
super()
|
177
|
-
ensure_ivar_visibility!
|
178
180
|
end
|
179
181
|
|
180
182
|
# @return [:pending, :completed]
|
@@ -878,9 +880,11 @@ module Concurrent
|
|
878
880
|
# @abstract
|
879
881
|
# @!visibility private
|
880
882
|
class AbstractPromise < Synchronization::Object
|
883
|
+
safe_initialization!
|
884
|
+
|
881
885
|
def initialize(future)
|
886
|
+
super()
|
882
887
|
@Future = future
|
883
|
-
ensure_ivar_visibility!
|
884
888
|
end
|
885
889
|
|
886
890
|
def future
|
@@ -1374,11 +1378,12 @@ module Concurrent
|
|
1374
1378
|
|
1375
1379
|
# @note proof of concept
|
1376
1380
|
class Channel < Synchronization::Object
|
1381
|
+
safe_initialization!
|
1382
|
+
|
1377
1383
|
# TODO make lock free
|
1378
1384
|
def initialize
|
1379
|
-
super
|
1385
|
+
super()
|
1380
1386
|
@ProbeSet = Concurrent::Channel::WaitableList.new
|
1381
|
-
ensure_ivar_visibility!
|
1382
1387
|
end
|
1383
1388
|
|
1384
1389
|
def probe_set_size
|
@@ -55,7 +55,7 @@ module Concurrent
|
|
55
55
|
|
56
56
|
node = Node.new item, curr
|
57
57
|
|
58
|
-
if pred.
|
58
|
+
if pred.successor_reference.compare_and_set curr, node, false, false
|
59
59
|
return true
|
60
60
|
end
|
61
61
|
end
|
@@ -88,7 +88,7 @@ module Concurrent
|
|
88
88
|
|
89
89
|
while curr < item
|
90
90
|
curr = curr.next_node
|
91
|
-
marked = curr.
|
91
|
+
marked = curr.successor_reference.marked?
|
92
92
|
end
|
93
93
|
|
94
94
|
curr == item && !marked
|
@@ -109,11 +109,11 @@ module Concurrent
|
|
109
109
|
return false if curr != item
|
110
110
|
|
111
111
|
succ = curr.next_node
|
112
|
-
removed = curr.
|
112
|
+
removed = curr.successor_reference.compare_and_set succ, succ, false, true
|
113
113
|
|
114
114
|
next_node unless removed
|
115
115
|
|
116
|
-
pred.
|
116
|
+
pred.successor_reference.compare_and_set curr, succ, false, false
|
117
117
|
|
118
118
|
return true
|
119
119
|
end
|
@@ -134,9 +134,9 @@ module Concurrent
|
|
134
134
|
|
135
135
|
until curr.last?
|
136
136
|
curr = curr.next_node
|
137
|
-
marked = curr.
|
137
|
+
marked = curr.successor_reference.marked?
|
138
138
|
|
139
|
-
yield curr.
|
139
|
+
yield curr.data unless marked
|
140
140
|
end
|
141
141
|
|
142
142
|
self
|
@@ -6,27 +6,36 @@ module Concurrent
|
|
6
6
|
class Node < Synchronization::Object
|
7
7
|
include Comparable
|
8
8
|
|
9
|
-
|
9
|
+
safe_initialization!
|
10
10
|
|
11
11
|
def initialize(data = nil, successor = nil)
|
12
12
|
super()
|
13
|
+
@SuccessorReference = AtomicMarkableReference.new(successor || Tail.new)
|
14
|
+
@Data = data
|
15
|
+
@Key = key_for data
|
16
|
+
end
|
17
|
+
|
18
|
+
def data
|
19
|
+
@Data
|
20
|
+
end
|
13
21
|
|
14
|
-
|
15
|
-
@
|
16
|
-
|
22
|
+
def successor_reference
|
23
|
+
@SuccessorReference
|
24
|
+
end
|
17
25
|
|
18
|
-
|
26
|
+
def key
|
27
|
+
@Key
|
19
28
|
end
|
20
29
|
|
21
30
|
# Check to see if the node is the last in the list.
|
22
31
|
def last?
|
23
|
-
@
|
32
|
+
@SuccessorReference.value.is_a? Tail
|
24
33
|
end
|
25
34
|
|
26
35
|
# Next node in the list. Note: this is not the AtomicMarkableReference
|
27
36
|
# of the next node, this is the actual Node itself.
|
28
37
|
def next_node
|
29
|
-
@
|
38
|
+
@SuccessorReference.value
|
30
39
|
end
|
31
40
|
|
32
41
|
# This method provides a unqiue key for the data which will be used for
|
@@ -49,7 +58,7 @@ module Concurrent
|
|
49
58
|
# a self-loop.
|
50
59
|
class Tail < Node
|
51
60
|
def initialize(_data = nil, _succ = nil)
|
52
|
-
@
|
61
|
+
@SuccessorReference = AtomicMarkableReference.new self
|
53
62
|
end
|
54
63
|
|
55
64
|
# Always greater than other nodes. This means that traversal will end
|
@@ -20,17 +20,17 @@ module Concurrent
|
|
20
20
|
curr = pred.next_node
|
21
21
|
|
22
22
|
loop do
|
23
|
-
succ, marked = curr.
|
23
|
+
succ, marked = curr.successor_reference.get
|
24
24
|
|
25
25
|
# Remove sequence of marked nodes
|
26
26
|
while marked
|
27
|
-
removed = pred.
|
27
|
+
removed = pred.successor_reference.compare_and_set curr, succ, false, false
|
28
28
|
|
29
29
|
# If could not remove node, try again
|
30
30
|
break_inner_loops = true && break unless removed
|
31
31
|
|
32
32
|
curr = succ
|
33
|
-
succ, marked = curr.
|
33
|
+
succ, marked = curr.successor_reference.get
|
34
34
|
end
|
35
35
|
|
36
36
|
break if break_inner_loops
|
@@ -2,6 +2,8 @@ module Concurrent
|
|
2
2
|
module Edge
|
3
3
|
class LockFreeStack < Synchronization::Object
|
4
4
|
|
5
|
+
safe_initialization!
|
6
|
+
|
5
7
|
class Node
|
6
8
|
attr_reader :value, :next_node
|
7
9
|
|
@@ -21,50 +23,51 @@ module Concurrent
|
|
21
23
|
|
22
24
|
EMPTY = Empty[nil, nil]
|
23
25
|
|
26
|
+
private *attr_volatile_with_cas(:head)
|
27
|
+
|
24
28
|
def initialize
|
25
|
-
|
26
|
-
ensure_ivar_visibility!
|
29
|
+
super(EMPTY)
|
27
30
|
end
|
28
31
|
|
29
32
|
def empty?
|
30
|
-
|
33
|
+
head.equal? EMPTY
|
31
34
|
end
|
32
35
|
|
33
36
|
def compare_and_push(head, value)
|
34
|
-
|
37
|
+
compare_and_set_head head, Node[value, head]
|
35
38
|
end
|
36
39
|
|
37
40
|
def push(value)
|
38
41
|
while true
|
39
|
-
|
40
|
-
return self if
|
42
|
+
current_head = head
|
43
|
+
return self if compare_and_set_head current_head, Node[value, current_head]
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
44
47
|
def peek
|
45
|
-
|
48
|
+
head
|
46
49
|
end
|
47
50
|
|
48
51
|
def compare_and_pop(head)
|
49
|
-
|
52
|
+
compare_and_set_head head, head.next_node
|
50
53
|
end
|
51
54
|
|
52
55
|
def pop
|
53
56
|
while true
|
54
|
-
|
55
|
-
return
|
57
|
+
current_head = head
|
58
|
+
return current_head.value if compare_and_set_head current_head, current_head.next_node
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
59
62
|
def compare_and_clear(head)
|
60
|
-
|
63
|
+
compare_and_set_head head, EMPTY
|
61
64
|
end
|
62
65
|
|
63
66
|
def clear
|
64
67
|
while true
|
65
|
-
|
66
|
-
return false if
|
67
|
-
return true if
|
68
|
+
current_head = head
|
69
|
+
return false if current_head == EMPTY
|
70
|
+
return true if compare_and_set_head current_head, EMPTY
|
68
71
|
end
|
69
72
|
end
|
70
73
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: concurrent-ruby-edge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.0.
|
4
|
+
version: 0.2.0.pre2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jerry D'Antonio
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-
|
13
|
+
date: 2015-09-19 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: concurrent-ruby
|
@@ -18,14 +18,14 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - "~>"
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.0.0.
|
21
|
+
version: 1.0.0.pre2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - "~>"
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version: 1.0.0.
|
28
|
+
version: 1.0.0.pre2
|
29
29
|
description: |
|
30
30
|
These features are under active development and may change frequently. They are expected not to
|
31
31
|
keep backward compatibility (there may also lack tests and documentation). Semantic versions will
|
@@ -71,7 +71,6 @@ files:
|
|
71
71
|
- lib/concurrent/actor/utils/balancer.rb
|
72
72
|
- lib/concurrent/actor/utils/broadcast.rb
|
73
73
|
- lib/concurrent/actor/utils/pool.rb
|
74
|
-
- lib/concurrent/agent.rb
|
75
74
|
- lib/concurrent/channel.rb
|
76
75
|
- lib/concurrent/channel/blocking_ring_buffer.rb
|
77
76
|
- lib/concurrent/channel/buffered_channel.rb
|
@@ -105,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
104
|
version: 1.3.1
|
106
105
|
requirements: []
|
107
106
|
rubyforge_project:
|
108
|
-
rubygems_version: 2.4.
|
107
|
+
rubygems_version: 2.4.5.1
|
109
108
|
signing_key:
|
110
109
|
specification_version: 4
|
111
110
|
summary: Edge features and additions to the concurrent-ruby gem.
|
data/lib/concurrent/agent.rb
DELETED
@@ -1,259 +0,0 @@
|
|
1
|
-
require 'concurrent/collection/copy_on_write_observer_set'
|
2
|
-
require 'concurrent/concern/dereferenceable'
|
3
|
-
require 'concurrent/concern/observable'
|
4
|
-
require 'concurrent/concern/logging'
|
5
|
-
require 'concurrent/executor/executor'
|
6
|
-
require 'concurrent/synchronization'
|
7
|
-
|
8
|
-
module Concurrent
|
9
|
-
|
10
|
-
# `Agent`s are inspired by [Clojure's](http://clojure.org/) [agent](http://clojure.org/agents) function. An `Agent` is a single atomic value that represents an identity. The current value of the `Agent` can be requested at any time (`deref`). Each `Agent` has a work queue and operates on the global thread pool (see below). Consumers can `post` code blocks to the `Agent`. The code block (function) will receive the current value of the `Agent` as its sole parameter. The return value of the block will become the new value of the `Agent`. `Agent`s support two error handling modes: fail and continue. A good example of an `Agent` is a shared incrementing counter, such as the score in a video game.
|
11
|
-
#
|
12
|
-
# An `Agent` must be initialize with an initial value. This value is always accessible via the `value` (or `deref`) methods. Code blocks sent to the `Agent` will be processed in the order received. As each block is processed the current value is updated with the result from the block. This update is an atomic operation so a `deref` will never block and will always return the current value.
|
13
|
-
#
|
14
|
-
# When an `Agent` is created it may be given an optional `validate` block and zero or more `rescue` blocks. When a new value is calculated the value will be checked against the validator, if present. If the validator returns `true` the new value will be accepted. If it returns `false` it will be rejected. If a block raises an exception during execution the list of `rescue` blocks will be seacrhed in order until one matching the current exception is found. That `rescue` block will then be called an passed the exception object. If no matching `rescue` block is found, or none were configured, then the exception will be suppressed.
|
15
|
-
#
|
16
|
-
# `Agent`s also implement Ruby's [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html). Code that observes an `Agent` will receive a callback with the new value any time the value is changed.
|
17
|
-
#
|
18
|
-
# @!macro copy_options
|
19
|
-
#
|
20
|
-
# @example Simple Example
|
21
|
-
#
|
22
|
-
# require 'concurrent'
|
23
|
-
#
|
24
|
-
# score = Concurrent::Agent.new(10)
|
25
|
-
# score.value #=> 10
|
26
|
-
#
|
27
|
-
# score << proc{|current| current + 100 }
|
28
|
-
# sleep(0.1)
|
29
|
-
# score.value #=> 110
|
30
|
-
#
|
31
|
-
# score << proc{|current| current * 2 }
|
32
|
-
# sleep(0.1)
|
33
|
-
# score.value #=> 220
|
34
|
-
#
|
35
|
-
# score << proc{|current| current - 50 }
|
36
|
-
# sleep(0.1)
|
37
|
-
# score.value #=> 170
|
38
|
-
#
|
39
|
-
# @example With Validation and Error Handling
|
40
|
-
#
|
41
|
-
# score = Concurrent::Agent.new(0).validate{|value| value <= 1024 }.
|
42
|
-
# rescue(NoMethodError){|ex| puts "Bam!" }.
|
43
|
-
# rescue(ArgumentError){|ex| puts "Pow!" }.
|
44
|
-
# rescue{|ex| puts "Boom!" }
|
45
|
-
# score.value #=> 0
|
46
|
-
#
|
47
|
-
# score << proc{|current| current + 2048 }
|
48
|
-
# sleep(0.1)
|
49
|
-
# score.value #=> 0
|
50
|
-
#
|
51
|
-
# score << proc{|current| raise ArgumentError }
|
52
|
-
# sleep(0.1)
|
53
|
-
# #=> puts "Pow!"
|
54
|
-
# score.value #=> 0
|
55
|
-
#
|
56
|
-
# score << proc{|current| current + 100 }
|
57
|
-
# sleep(0.1)
|
58
|
-
# score.value #=> 100
|
59
|
-
#
|
60
|
-
# @example With Observation
|
61
|
-
#
|
62
|
-
# bingo = Class.new{
|
63
|
-
# def update(time, score)
|
64
|
-
# puts "Bingo! [score: #{score}, time: #{time}]" if score >= 100
|
65
|
-
# end
|
66
|
-
# }.new
|
67
|
-
#
|
68
|
-
# score = Concurrent::Agent.new(0)
|
69
|
-
# score.add_observer(bingo)
|
70
|
-
#
|
71
|
-
# score << proc{|current| sleep(0.1); current += 30 }
|
72
|
-
# score << proc{|current| sleep(0.1); current += 30 }
|
73
|
-
# score << proc{|current| sleep(0.1); current += 30 }
|
74
|
-
# score << proc{|current| sleep(0.1); current += 30 }
|
75
|
-
#
|
76
|
-
# sleep(1)
|
77
|
-
# #=> Bingo! [score: 120, time: 2013-07-22 21:26:08 -0400]
|
78
|
-
#
|
79
|
-
# @!attribute [r] timeout
|
80
|
-
# @return [Fixnum] the maximum number of seconds before an update is cancelled
|
81
|
-
#
|
82
|
-
# @!macro edge_warning
|
83
|
-
class Agent < Synchronization::Object
|
84
|
-
include Concern::Dereferenceable
|
85
|
-
include Concern::Observable
|
86
|
-
include Concern::Logging
|
87
|
-
|
88
|
-
attr_reader :timeout, :io_executor, :fast_executor
|
89
|
-
|
90
|
-
# Initialize a new Agent with the given initial value and provided options.
|
91
|
-
#
|
92
|
-
# @param [Object] initial the initial value
|
93
|
-
#
|
94
|
-
# @!macro executor_and_deref_options
|
95
|
-
def initialize(initial, opts = {})
|
96
|
-
super()
|
97
|
-
synchronize { ns_initialize(initial, opts) }
|
98
|
-
end
|
99
|
-
|
100
|
-
# Specifies a block fast to be performed when an update fast raises
|
101
|
-
# an exception. Rescue blocks will be checked in order they were added. The first
|
102
|
-
# block for which the raised exception "is-a" subclass of the given `clazz` will
|
103
|
-
# be called. If no `clazz` is given the block will match any caught exception.
|
104
|
-
# This behavior is intended to be identical to Ruby's `begin/rescue/end` behavior.
|
105
|
-
# Any number of rescue handlers can be added. If no rescue handlers are added then
|
106
|
-
# caught exceptions will be suppressed.
|
107
|
-
#
|
108
|
-
# @param [Exception] clazz the class of exception to catch
|
109
|
-
# @yield the block to be called when a matching exception is caught
|
110
|
-
# @yieldparam [StandardError] ex the caught exception
|
111
|
-
#
|
112
|
-
# @example
|
113
|
-
# score = Concurrent::Agent.new(0).
|
114
|
-
# rescue(NoMethodError){|ex| puts "Bam!" }.
|
115
|
-
# rescue(ArgumentError){|ex| puts "Pow!" }.
|
116
|
-
# rescue{|ex| puts "Boom!" }
|
117
|
-
#
|
118
|
-
# score << proc{|current| raise ArgumentError }
|
119
|
-
# sleep(0.1)
|
120
|
-
# #=> puts "Pow!"
|
121
|
-
def rescue(clazz = StandardError, &block)
|
122
|
-
unless block.nil?
|
123
|
-
synchronize { @rescuers << Rescuer.new(clazz, block) }
|
124
|
-
end
|
125
|
-
self
|
126
|
-
end
|
127
|
-
|
128
|
-
alias_method :catch, :rescue
|
129
|
-
alias_method :on_error, :rescue
|
130
|
-
|
131
|
-
# A block task to be performed after every update to validate if the new
|
132
|
-
# value is valid. If the new value is not valid then the current value is not
|
133
|
-
# updated. If no validator is provided then all updates are considered valid.
|
134
|
-
#
|
135
|
-
# @yield the block to be called after every update fast to determine if
|
136
|
-
# the result is valid
|
137
|
-
# @yieldparam [Object] value the result of the last update fast
|
138
|
-
# @yieldreturn [Boolean] true if the value is valid else false
|
139
|
-
def validate(&block)
|
140
|
-
|
141
|
-
unless block.nil?
|
142
|
-
synchronize { @validator = block }
|
143
|
-
end
|
144
|
-
self
|
145
|
-
end
|
146
|
-
|
147
|
-
alias_method :validates, :validate
|
148
|
-
alias_method :validate_with, :validate
|
149
|
-
alias_method :validates_with, :validate
|
150
|
-
|
151
|
-
# Update the current value with the result of the given block fast,
|
152
|
-
# block should not do blocking calls, use #post_off for blocking calls
|
153
|
-
#
|
154
|
-
# @yield the fast to be performed with the current value in order to calculate
|
155
|
-
# the new value
|
156
|
-
# @yieldparam [Object] value the current value
|
157
|
-
# @yieldreturn [Object] the new value
|
158
|
-
# @return [true, nil] nil when no block is given
|
159
|
-
def post(&block)
|
160
|
-
post_on(@fast_executor, &block)
|
161
|
-
end
|
162
|
-
|
163
|
-
# Update the current value with the result of the given block fast,
|
164
|
-
# block can do blocking calls
|
165
|
-
#
|
166
|
-
# @yield the fast to be performed with the current value in order to calculate
|
167
|
-
# the new value
|
168
|
-
# @yieldparam [Object] value the current value
|
169
|
-
# @yieldreturn [Object] the new value
|
170
|
-
# @return [true, nil] nil when no block is given
|
171
|
-
def post_off(&block)
|
172
|
-
post_on(@io_executor, &block)
|
173
|
-
end
|
174
|
-
|
175
|
-
# Update the current value with the result of the given block fast,
|
176
|
-
# block should not do blocking calls, use #post_off for blocking calls
|
177
|
-
#
|
178
|
-
# @yield the fast to be performed with the current value in order to calculate
|
179
|
-
# the new value
|
180
|
-
# @yieldparam [Object] value the current value
|
181
|
-
# @yieldreturn [Object] the new value
|
182
|
-
def <<(block)
|
183
|
-
post(&block)
|
184
|
-
self
|
185
|
-
end
|
186
|
-
|
187
|
-
# Waits/blocks until all the updates sent before this call are done.
|
188
|
-
#
|
189
|
-
# @param [Numeric] timeout the maximum time in second to wait.
|
190
|
-
# @return [Boolean] false on timeout, true otherwise
|
191
|
-
def await(timeout = nil)
|
192
|
-
done = Event.new
|
193
|
-
post { |val| done.set; val }
|
194
|
-
done.wait timeout
|
195
|
-
end
|
196
|
-
|
197
|
-
protected
|
198
|
-
|
199
|
-
def ns_initialize(initial, opts)
|
200
|
-
init_mutex(self)
|
201
|
-
@value = initial
|
202
|
-
@rescuers = []
|
203
|
-
@validator = Proc.new { |result| true }
|
204
|
-
self.observers = Collection::CopyOnWriteObserverSet.new
|
205
|
-
@serialized_execution = SerializedExecution.new
|
206
|
-
@io_executor = Executor.executor_from_options(opts) || Concurrent.global_io_executor
|
207
|
-
@fast_executor = Executor.executor_from_options(opts) || Concurrent.global_fast_executor
|
208
|
-
set_deref_options(opts)
|
209
|
-
end
|
210
|
-
|
211
|
-
private
|
212
|
-
|
213
|
-
def post_on(executor, &block)
|
214
|
-
return nil if block.nil?
|
215
|
-
@serialized_execution.post(executor) { work(&block) }
|
216
|
-
true
|
217
|
-
end
|
218
|
-
|
219
|
-
# @!visibility private
|
220
|
-
Rescuer = Struct.new(:clazz, :block) # :nodoc:
|
221
|
-
|
222
|
-
# @!visibility private
|
223
|
-
def try_rescue(ex) # :nodoc:
|
224
|
-
rescuer = synchronize do
|
225
|
-
@rescuers.find { |r| ex.is_a?(r.clazz) }
|
226
|
-
end
|
227
|
-
rescuer.block.call(ex) if rescuer
|
228
|
-
rescue Exception => ex
|
229
|
-
# suppress
|
230
|
-
log DEBUG, ex
|
231
|
-
end
|
232
|
-
|
233
|
-
# @!visibility private
|
234
|
-
def work(&handler) # :nodoc:
|
235
|
-
validator, value = synchronize { [@validator, @value] }
|
236
|
-
|
237
|
-
begin
|
238
|
-
result = handler.call(value)
|
239
|
-
valid = validator.call(result)
|
240
|
-
rescue Exception => ex
|
241
|
-
exception = ex
|
242
|
-
end
|
243
|
-
|
244
|
-
should_notify = synchronize do
|
245
|
-
if !exception && valid
|
246
|
-
@value = result
|
247
|
-
true
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
if should_notify
|
252
|
-
time = Time.now
|
253
|
-
observers.notify_observers { [time, self.value] }
|
254
|
-
end
|
255
|
-
|
256
|
-
try_rescue(exception)
|
257
|
-
end
|
258
|
-
end
|
259
|
-
end
|