concurrent-ruby-edge 0.2.0.pre2 → 0.2.0.pre3
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 +7 -5
- data/lib/concurrent/channel.rb +272 -6
- data/lib/concurrent/channel/buffer.rb +9 -0
- data/lib/concurrent/channel/buffer/base.rb +197 -0
- data/lib/concurrent/channel/buffer/buffered.rb +127 -0
- data/lib/concurrent/channel/buffer/dropping.rb +53 -0
- data/lib/concurrent/channel/buffer/sliding.rb +54 -0
- data/lib/concurrent/channel/buffer/ticker.rb +80 -0
- data/lib/concurrent/channel/buffer/timer.rb +78 -0
- data/lib/concurrent/channel/buffer/unbuffered.rb +151 -0
- data/lib/concurrent/channel/selector.rb +70 -0
- data/lib/concurrent/channel/selector/after_clause.rb +27 -0
- data/lib/concurrent/channel/selector/default_clause.rb +19 -0
- data/lib/concurrent/channel/selector/error_clause.rb +21 -0
- data/lib/concurrent/channel/selector/put_clause.rb +26 -0
- data/lib/concurrent/channel/selector/take_clause.rb +24 -0
- data/lib/concurrent/channel/tick.rb +55 -0
- data/lib/concurrent/edge/future.rb +23 -41
- data/lib/concurrent/edge/lock_free_linked_set.rb +2 -1
- metadata +20 -11
- data/lib/concurrent/channel/blocking_ring_buffer.rb +0 -82
- data/lib/concurrent/channel/buffered_channel.rb +0 -89
- data/lib/concurrent/channel/channel.rb +0 -19
- data/lib/concurrent/channel/ring_buffer.rb +0 -65
- data/lib/concurrent/channel/unbuffered_channel.rb +0 -39
- data/lib/concurrent/channel/waitable_list.rb +0 -48
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'concurrent/channel/buffer/base'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
class Channel
|
5
|
+
module Buffer
|
6
|
+
|
7
|
+
# A buffer with a fixed internal capacity. Items can be put onto the
|
8
|
+
# buffer without blocking until the internal capacity is reached. Once
|
9
|
+
# the buffer is at capacity, subsequent calls to {#put} will block until
|
10
|
+
# an item is removed from the buffer, creating spare capacity.
|
11
|
+
class Buffered < Base
|
12
|
+
|
13
|
+
# @!macro channel_buffer_initialize
|
14
|
+
#
|
15
|
+
# @param [Integer] size the maximum capacity of the buffer; must be
|
16
|
+
# greater than zero.
|
17
|
+
# @raise [ArgumentError] when the size is zero (0) or less.
|
18
|
+
def initialize(size)
|
19
|
+
raise ArgumentError.new('size must be greater than 0') if size.to_i <= 0
|
20
|
+
super()
|
21
|
+
synchronize do
|
22
|
+
@size = size.to_i
|
23
|
+
@buffer = []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!macro channel_buffer_empty_question
|
28
|
+
def empty?
|
29
|
+
synchronize { ns_empty? }
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!macro channel_buffer_full_question
|
33
|
+
#
|
34
|
+
# Will return `true` once the number of items in the buffer reaches
|
35
|
+
# the {#size} value specified during initialization.
|
36
|
+
def full?
|
37
|
+
synchronize { ns_full? }
|
38
|
+
end
|
39
|
+
|
40
|
+
# @!macro channel_buffer_put
|
41
|
+
#
|
42
|
+
# New items can be put onto the buffer until the number of items in
|
43
|
+
# the buffer reaches the {#size} value specified during
|
44
|
+
# initialization.
|
45
|
+
def put(item)
|
46
|
+
loop do
|
47
|
+
synchronize do
|
48
|
+
if ns_closed?
|
49
|
+
return false
|
50
|
+
elsif !ns_full?
|
51
|
+
ns_put_onto_buffer(item)
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
Thread.pass
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @!macro channel_buffer_offer
|
60
|
+
#
|
61
|
+
# New items can be put onto the buffer until the number of items in
|
62
|
+
# the buffer reaches the {#size} value specified during
|
63
|
+
# initialization.
|
64
|
+
def offer(item)
|
65
|
+
synchronize do
|
66
|
+
if ns_closed? || ns_full?
|
67
|
+
return false
|
68
|
+
else
|
69
|
+
ns_put_onto_buffer(item)
|
70
|
+
return true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @!macro channel_buffer_take
|
76
|
+
def take
|
77
|
+
item, _ = self.next
|
78
|
+
item
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!macro channel_buffer_next
|
82
|
+
def next
|
83
|
+
loop do
|
84
|
+
synchronize do
|
85
|
+
if ns_closed? && ns_empty?
|
86
|
+
return NO_VALUE, false
|
87
|
+
elsif !ns_empty?
|
88
|
+
item = @buffer.shift
|
89
|
+
more = !ns_empty? || !ns_closed?
|
90
|
+
return item, more
|
91
|
+
end
|
92
|
+
end
|
93
|
+
Thread.pass
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# @!macro channel_buffer_poll
|
98
|
+
def poll
|
99
|
+
synchronize do
|
100
|
+
if ns_empty?
|
101
|
+
NO_VALUE
|
102
|
+
else
|
103
|
+
@buffer.shift
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# @!macro channel_buffer_empty_question
|
111
|
+
def ns_empty?
|
112
|
+
@buffer.length == 0
|
113
|
+
end
|
114
|
+
|
115
|
+
# @!macro channel_buffer_full_question
|
116
|
+
def ns_full?
|
117
|
+
@buffer.length == @size
|
118
|
+
end
|
119
|
+
|
120
|
+
# @!macro channel_buffer_put
|
121
|
+
def ns_put_onto_buffer(item)
|
122
|
+
@buffer.push(item)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'concurrent/channel/buffer/base'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
class Channel
|
5
|
+
module Buffer
|
6
|
+
|
7
|
+
# A non-blocking, buffered buffer of fixed maximum capacity. When the
|
8
|
+
# maximum capacity is reached subsequent {#put} and {#offer} operations
|
9
|
+
# will complete but the `put` item will be discarded; no transfer will
|
10
|
+
# occur.
|
11
|
+
class Dropping < Buffered
|
12
|
+
|
13
|
+
# @!method put(item)
|
14
|
+
# @!macro channel_buffer_put
|
15
|
+
#
|
16
|
+
# When the buffer is full, this method will return `true`
|
17
|
+
# immediately but the item will be discarded. The item will *not*
|
18
|
+
# be placed into the buffer (no transfer will occur).
|
19
|
+
|
20
|
+
# @!method offer(item)
|
21
|
+
# @!macro channel_buffer_offer
|
22
|
+
#
|
23
|
+
# When the buffer is full, this method will return `true`
|
24
|
+
# immediately but the item will be discarded. The item will *not*
|
25
|
+
# be placed into the buffer (no transfer will occur).
|
26
|
+
|
27
|
+
# @!method full?
|
28
|
+
# @!macro channel_buffer_full_question
|
29
|
+
#
|
30
|
+
# Always returns `false`.
|
31
|
+
|
32
|
+
# @!macro channel_buffer_blocking_question
|
33
|
+
#
|
34
|
+
# Always returns `false`.
|
35
|
+
def blocking?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @!macro channel_buffer_full_question
|
42
|
+
def ns_full?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!macro channel_buffer_put
|
47
|
+
def ns_put_onto_buffer(item)
|
48
|
+
@buffer.push(item) unless @buffer.size == size
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'concurrent/channel/buffer/base'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
class Channel
|
5
|
+
module Buffer
|
6
|
+
|
7
|
+
# A non-blocking, buffered buffer of fixed maximum capacity. When the
|
8
|
+
# maximum capacity is reached subsequent {#put} and {#offer} operations
|
9
|
+
# will complete and the item will be `put`, but the oldest elements in
|
10
|
+
# the buffer will be discarded (not transferred).
|
11
|
+
class Sliding < Buffered
|
12
|
+
|
13
|
+
# @!method put(item)
|
14
|
+
# @!macro channel_buffer_put
|
15
|
+
#
|
16
|
+
# When the buffer is full, this method will return `true`
|
17
|
+
# immediately and the item will be inserted, but the oldest
|
18
|
+
# elements in the buffer will be discarded (not transferred).
|
19
|
+
|
20
|
+
# @!method offer(item)
|
21
|
+
# @!macro channel_buffer_offer
|
22
|
+
#
|
23
|
+
# When the buffer is full, this method will return `true`
|
24
|
+
# immediately and the item will be inserted, but the oldest
|
25
|
+
# elements in the buffer will be discarded (not transferred).
|
26
|
+
|
27
|
+
# @!method full?
|
28
|
+
# @!macro channel_buffer_full_question
|
29
|
+
#
|
30
|
+
# Always returns `false`.
|
31
|
+
|
32
|
+
# @!macro channel_buffer_blocking_question
|
33
|
+
#
|
34
|
+
# Always returns `false`.
|
35
|
+
def blocking?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @!macro channel_buffer_full_question
|
42
|
+
def ns_full?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!macro channel_buffer_put
|
47
|
+
def ns_put_onto_buffer(item)
|
48
|
+
@buffer.shift if @buffer.size == size
|
49
|
+
@buffer.push(item)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'concurrent/utility/monotonic_time'
|
2
|
+
require 'concurrent/channel/tick'
|
3
|
+
require 'concurrent/channel/buffer/base'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
class Channel
|
7
|
+
module Buffer
|
8
|
+
|
9
|
+
class Ticker < Base
|
10
|
+
|
11
|
+
def initialize(interval)
|
12
|
+
super()
|
13
|
+
synchronize do
|
14
|
+
@interval = interval.to_f
|
15
|
+
@next_tick = Concurrent.monotonic_time + interval
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def size() 1; end
|
20
|
+
|
21
|
+
def empty?() false; end
|
22
|
+
|
23
|
+
def full?() true; end
|
24
|
+
|
25
|
+
def put(item)
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def offer(item)
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def take
|
34
|
+
loop do
|
35
|
+
result, _ = do_poll
|
36
|
+
if result.nil?
|
37
|
+
return NO_VALUE
|
38
|
+
elsif result != NO_VALUE
|
39
|
+
return result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def next
|
45
|
+
loop do
|
46
|
+
result, _ = do_poll
|
47
|
+
if result.nil?
|
48
|
+
return NO_VALUE, false
|
49
|
+
elsif result != NO_VALUE
|
50
|
+
return result, true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def poll
|
56
|
+
result, _ = do_poll
|
57
|
+
if result.nil? || result == NO_VALUE
|
58
|
+
NO_VALUE
|
59
|
+
else
|
60
|
+
result
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def do_poll
|
67
|
+
if ns_closed?
|
68
|
+
return nil, false
|
69
|
+
elsif (now = Concurrent.monotonic_time) > @next_tick
|
70
|
+
tick = Concurrent::Channel::Tick.new(@next_tick)
|
71
|
+
@next_tick = now + @interval
|
72
|
+
return tick, true
|
73
|
+
else
|
74
|
+
return NO_VALUE, true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'concurrent/utility/monotonic_time'
|
2
|
+
require 'concurrent/channel/tick'
|
3
|
+
require 'concurrent/channel/buffer/base'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
class Channel
|
7
|
+
module Buffer
|
8
|
+
|
9
|
+
class Timer < Base
|
10
|
+
|
11
|
+
def initialize(delay)
|
12
|
+
super()
|
13
|
+
synchronize do
|
14
|
+
@tick = Concurrent.monotonic_time + delay.to_f
|
15
|
+
@closed = false
|
16
|
+
@empty = false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def size() 1; end
|
21
|
+
|
22
|
+
def empty?
|
23
|
+
synchronized { @empty }
|
24
|
+
end
|
25
|
+
|
26
|
+
def full?
|
27
|
+
!empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def put(item)
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def offer(item)
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
def take
|
39
|
+
self.next.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def next
|
43
|
+
loop do
|
44
|
+
status, tick = do_poll
|
45
|
+
if status == :tick
|
46
|
+
return tick, false
|
47
|
+
# AFAIK a Go timer will block forever if stopped
|
48
|
+
#elsif status == :closed
|
49
|
+
#return false, false
|
50
|
+
end
|
51
|
+
Thread.pass
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def poll
|
56
|
+
status, tick = do_poll
|
57
|
+
status == :tick ? tick : NO_VALUE
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def do_poll
|
63
|
+
synchronize do
|
64
|
+
return :closed, false if ns_closed?
|
65
|
+
|
66
|
+
if Concurrent.monotonic_time > @tick
|
67
|
+
# only one listener gets notified
|
68
|
+
@closed = @empty = true
|
69
|
+
return :tick, Concurrent::Channel::Tick.new(@tick)
|
70
|
+
else
|
71
|
+
return :wait, true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'concurrent/channel/buffer/base'
|
2
|
+
require 'concurrent/atomic/atomic_reference'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
class Channel
|
6
|
+
module Buffer
|
7
|
+
|
8
|
+
# A blocking buffer with a size of zero. An item can only be put onto
|
9
|
+
# the buffer when a thread is waiting to take. Similarly, an item can
|
10
|
+
# only be put onto the buffer when a thread is waiting to put. When
|
11
|
+
# either {#put} or {#take} is called and there is no corresponding call
|
12
|
+
# in progress, the call will block indefinitely. Any other calls to the
|
13
|
+
# same method will queue behind the first call and block as well. As
|
14
|
+
# soon as a corresponding put/take call is made an exchange will occur
|
15
|
+
# and the first blocked call will return.
|
16
|
+
class Unbuffered < Base
|
17
|
+
|
18
|
+
# @!macro channel_buffer_initialize
|
19
|
+
def initialize
|
20
|
+
super
|
21
|
+
synchronize do
|
22
|
+
# one will always be empty
|
23
|
+
@putting = []
|
24
|
+
@taking = []
|
25
|
+
@closed = false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!macro channel_buffer_size_reader
|
30
|
+
#
|
31
|
+
# Always returns zero (0).
|
32
|
+
def size() 0; end
|
33
|
+
|
34
|
+
# @!macro channel_buffer_empty_question
|
35
|
+
#
|
36
|
+
# Always returns `true`.
|
37
|
+
def empty?() true; end
|
38
|
+
|
39
|
+
# @!macro channel_buffer_full_question
|
40
|
+
#
|
41
|
+
# Always returns `false`.
|
42
|
+
def full?() false; end
|
43
|
+
|
44
|
+
# @!macro channel_buffer_put
|
45
|
+
#
|
46
|
+
# Items can only be put onto the buffer when one or more threads are
|
47
|
+
# waiting to {#take} items off the buffer. When there is a thread
|
48
|
+
# waiting to take an item this method will give its item and return
|
49
|
+
# immediately. When there are no threads waiting to take, this method
|
50
|
+
# will block. As soon as a thread calls `take` the exchange will
|
51
|
+
# occur and this method will return.
|
52
|
+
def put(item)
|
53
|
+
mine = synchronize do
|
54
|
+
return false if ns_closed?
|
55
|
+
|
56
|
+
ref = Concurrent::AtomicReference.new(item)
|
57
|
+
if @taking.empty?
|
58
|
+
@putting.push(ref)
|
59
|
+
else
|
60
|
+
taking = @taking.shift
|
61
|
+
taking.value = item
|
62
|
+
ref.value = nil
|
63
|
+
end
|
64
|
+
ref
|
65
|
+
end
|
66
|
+
loop do
|
67
|
+
return true if mine.value.nil?
|
68
|
+
Thread.pass
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @!macro channel_buffer_offer
|
73
|
+
#
|
74
|
+
# Items can only be put onto the buffer when one or more threads are
|
75
|
+
# waiting to {#take} items off the buffer. When there is a thread
|
76
|
+
# waiting to take an item this method will give its item and return
|
77
|
+
# `true` immediately. When there are no threads waiting to take or the
|
78
|
+
# buffer is closed, this method will return `false` immediately.
|
79
|
+
def offer(item)
|
80
|
+
synchronize do
|
81
|
+
return false if ns_closed? || @taking.empty?
|
82
|
+
|
83
|
+
taking = @taking.shift
|
84
|
+
taking.value = item
|
85
|
+
true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @!macro channel_buffer_take
|
90
|
+
#
|
91
|
+
# Items can only be taken from the buffer when one or more threads are
|
92
|
+
# waiting to {#put} items onto the buffer. When there is a thread
|
93
|
+
# waiting to put an item this method will take that item and return it
|
94
|
+
# immediately. When there are no threads waiting to put, this method
|
95
|
+
# will block. As soon as a thread calls `pur` the exchange will occur
|
96
|
+
# and this method will return.
|
97
|
+
def take
|
98
|
+
mine = synchronize do
|
99
|
+
return NO_VALUE if ns_closed? && @putting.empty?
|
100
|
+
|
101
|
+
ref = Concurrent::AtomicReference.new(nil)
|
102
|
+
if @putting.empty?
|
103
|
+
@taking.push(ref)
|
104
|
+
else
|
105
|
+
putting = @putting.shift
|
106
|
+
ref.value = putting.value
|
107
|
+
putting.value = nil
|
108
|
+
end
|
109
|
+
ref
|
110
|
+
end
|
111
|
+
loop do
|
112
|
+
item = mine.value
|
113
|
+
return item if item
|
114
|
+
Thread.pass
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# @!macro channel_buffer_poll
|
119
|
+
#
|
120
|
+
# Items can only be taken off the buffer when one or more threads are
|
121
|
+
# waiting to {#put} items onto the buffer. When there is a thread
|
122
|
+
# waiting to put an item this method will take the item and return
|
123
|
+
# it immediately. When there are no threads waiting to put or the
|
124
|
+
# buffer is closed, this method will return `NO_VALUE` immediately.
|
125
|
+
def poll
|
126
|
+
synchronize do
|
127
|
+
return NO_VALUE if @putting.empty?
|
128
|
+
|
129
|
+
putting = @putting.shift
|
130
|
+
value = putting.value
|
131
|
+
putting.value = nil
|
132
|
+
value
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# @!macro channel_buffer_next
|
137
|
+
#
|
138
|
+
# Items can only be taken from the buffer when one or more threads are
|
139
|
+
# waiting to {#put} items onto the buffer. This method exhibits the
|
140
|
+
# same blocking behavior as {#take}.
|
141
|
+
#
|
142
|
+
# @see {#take}
|
143
|
+
def next
|
144
|
+
item = take
|
145
|
+
more = synchronize { !@putting.empty? }
|
146
|
+
return item, more
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|