concurrent-ruby-edge 0.2.0.pre2 → 0.2.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|