concurrent-ruby-edge 0.2.0.pre1 → 0.2.0.pre2
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 +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
|