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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c02c328ebe5aaf45aaf86106223e979107a4ea99
4
- data.tar.gz: 425af0cce7f688a07e0546922c916877c2b4277a
3
+ metadata.gz: a4d13aad3839f1bfa159af7cc02464800dd062fb
4
+ data.tar.gz: 936d530a0a9368d1947339a2d83a6d9f36c979fd
5
5
  SHA512:
6
- metadata.gz: 877ef401c5cb31943832dd1a27f592e65b261608744b1d1b00e0210f82d9ae7d246fd3d224a78cbe64aa1533cf88b5c77338985bcaf7a29a8ceb7f29174bbb32
7
- data.tar.gz: d3aa66b036246d9af5f5ba0fff8ec7b46943a7c5d8fcbfcf52e4166c40a9b3fd1a80e335aaea539a756f21210a12ccd981243409d94a99fb9c4c874fe31d74bb
6
+ metadata.gz: f0fd846fc8ab69642ac313f12e298547ec1958d98c50466ceae1df09df7d047eccea65426fb3c04cb5d4433c933cc73f2ffb60ad81bd4f36a97418c11e58b37d
7
+ data.tar.gz: d3a92fbc3ebfb09256ba159b4016c0b0caaa371c762ccfb47d01789a09cd8d906f7052f6fa37f2b5c6d7057aff1561f384fe5e032cf25347663a3e93885b0c41
data/README.md CHANGED
@@ -130,8 +130,10 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
130
130
  `Promise`, `IVar`, `Event`, `dataflow`, `Delay`, and `TimerTask` into a single framework. It extensively uses the
131
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
- * [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Channel.html):
134
- Communicating Sequential Processes (CSP).
133
+ * [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/Channel.html):
134
+ Communicating Sequential Processes ([CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)).
135
+ Functionally equivalent to Go [channels](https://tour.golang.org/concurrency/2) with additional
136
+ inspiration from Clojure [core.async](https://clojure.github.io/core.async/).
135
137
  * [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
136
138
  * [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/AtomicMarkableReference.html)
137
139
  * [LockFreeLinkedSet](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeLinkedSet.html)
@@ -142,10 +144,10 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
142
144
  *Why are these not in core?*
143
145
 
144
146
  - **Actor** - Partial documentation and tests; depends on new future/promise framework; stability is good.
145
- - **Future/Promise Framework** - API changes; partial documentation and tests; stability good.
146
- - **Channel** - Missing documentation; limited features; stability good.
147
+ - **Channel** - Brand new implementation; partial documentation and tests; stability is good.
148
+ - **Future/Promise Framework** - API changes; partial documentation and tests; stability is good.
147
149
  - **LazyRegister** - Missing documentation and tests.
148
- - **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Need real world battle testing
150
+ - **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Need real world battle testing.
149
151
 
150
152
  ## Usage
151
153
 
@@ -1,6 +1,272 @@
1
- require 'concurrent/channel/blocking_ring_buffer'
2
- require 'concurrent/channel/buffered_channel'
3
- require 'concurrent/channel/channel'
4
- require 'concurrent/channel/ring_buffer'
5
- require 'concurrent/channel/unbuffered_channel'
6
- require 'concurrent/channel/waitable_list'
1
+ require 'concurrent/channel/buffer'
2
+ require 'concurrent/channel/selector'
3
+
4
+ require 'concurrent/maybe'
5
+ require 'concurrent/executor/cached_thread_pool'
6
+
7
+ module Concurrent
8
+
9
+ # {include:file:doc/channel.md}
10
+ class Channel
11
+ include Enumerable
12
+
13
+ GOROUTINES = Concurrent::CachedThreadPool.new
14
+ private_constant :GOROUTINES
15
+
16
+ BUFFER_TYPES = {
17
+ unbuffered: Buffer::Unbuffered,
18
+ buffered: Buffer::Buffered,
19
+ dropping: Buffer::Dropping,
20
+ sliding: Buffer::Sliding
21
+ }.freeze
22
+ private_constant :BUFFER_TYPES
23
+
24
+ DEFAULT_VALIDATOR = ->(value){ true }
25
+ private_constant :DEFAULT_VALIDATOR
26
+
27
+ Error = Class.new(StandardError)
28
+
29
+ class ValidationError < Error
30
+ def initialize(message = nil)
31
+ message ||= 'invalid value'
32
+ end
33
+ end
34
+
35
+ def initialize(opts = {})
36
+ # undocumented -- for internal use only
37
+ if opts.is_a? Buffer::Base
38
+ @buffer = opts
39
+ return
40
+ end
41
+
42
+ size = opts[:size]
43
+ buffer = opts[:buffer]
44
+
45
+ if size && buffer == :unbuffered
46
+ raise ArgumentError.new('unbuffered channels cannot have a size')
47
+ elsif size.nil? && buffer.nil?
48
+ @buffer = BUFFER_TYPES[:unbuffered].new
49
+ elsif size == 0 && buffer == :buffered
50
+ @buffer = BUFFER_TYPES[:unbuffered].new
51
+ elsif buffer == :unbuffered
52
+ @buffer = BUFFER_TYPES[:unbuffered].new
53
+ elsif size.nil? || size < 1
54
+ raise ArgumentError.new('size must be at least 1 for this buffer type')
55
+ else
56
+ buffer ||= :buffered
57
+ @buffer = BUFFER_TYPES[buffer].new(size)
58
+ end
59
+
60
+ @validator = opts.fetch(:validator, DEFAULT_VALIDATOR)
61
+ end
62
+
63
+ def size
64
+ @buffer.size
65
+ end
66
+ alias_method :capacity, :size
67
+
68
+ def put(item)
69
+ return false unless validate(item, false, false)
70
+ do_put(item)
71
+ end
72
+ alias_method :send, :put
73
+ alias_method :<<, :put
74
+
75
+ def put!(item)
76
+ validate(item, false, true)
77
+ ok = do_put(item)
78
+ raise Error if !ok
79
+ ok
80
+ end
81
+
82
+ def put?(item)
83
+ if !validate(item, true, false)
84
+ Concurrent::Maybe.nothing('invalid value')
85
+ elsif do_put(item)
86
+ Concurrent::Maybe.just(true)
87
+ else
88
+ Concurrent::Maybe.nothing
89
+ end
90
+ end
91
+
92
+ def offer(item)
93
+ return false unless validate(item, false, false)
94
+ do_offer(item)
95
+ end
96
+
97
+ def offer!(item)
98
+ validate(item, false, true)
99
+ ok = do_offer(item)
100
+ raise Error if !ok
101
+ ok
102
+ end
103
+
104
+ def offer?(item)
105
+ if !validate(item, true, false)
106
+ Concurrent::Maybe.nothing('invalid value')
107
+ elsif do_offer(item)
108
+ Concurrent::Maybe.just(true)
109
+ else
110
+ Concurrent::Maybe.nothing
111
+ end
112
+ end
113
+
114
+ def take
115
+ item, _ = self.next
116
+ item
117
+ end
118
+ alias_method :receive, :take
119
+ alias_method :~, :take
120
+
121
+ def take!
122
+ item, _ = do_next
123
+ raise Error if item == Buffer::NO_VALUE
124
+ item
125
+ end
126
+
127
+ def take?
128
+ item, _ = self.next?
129
+ item
130
+ end
131
+
132
+ #
133
+ # @example
134
+ #
135
+ # jobs = Channel.new
136
+ #
137
+ # Channel.go do
138
+ # loop do
139
+ # j, more = jobs.next
140
+ # if more
141
+ # print "received job #{j}\n"
142
+ # else
143
+ # print "received all jobs\n"
144
+ # break
145
+ # end
146
+ # end
147
+ # end
148
+ def next
149
+ item, more = do_next
150
+ item = nil if item == Buffer::NO_VALUE
151
+ return item, more
152
+ end
153
+
154
+ def next?
155
+ item, more = do_next
156
+ item = if item == Buffer::NO_VALUE
157
+ Concurrent::Maybe.nothing
158
+ else
159
+ Concurrent::Maybe.just(item)
160
+ end
161
+ return item, more
162
+ end
163
+
164
+ def poll
165
+ (item = do_poll) == Buffer::NO_VALUE ? nil : item
166
+ end
167
+
168
+ def poll!
169
+ item = do_poll
170
+ raise Error if item == Buffer::NO_VALUE
171
+ item
172
+ end
173
+
174
+ def poll?
175
+ if (item = do_poll) == Buffer::NO_VALUE
176
+ Concurrent::Maybe.nothing
177
+ else
178
+ Concurrent::Maybe.just(item)
179
+ end
180
+ end
181
+
182
+ def each
183
+ raise ArgumentError.new('no block given') unless block_given?
184
+ loop do
185
+ item, more = do_next
186
+ if item != Buffer::NO_VALUE
187
+ yield(item)
188
+ elsif !more
189
+ break
190
+ end
191
+ end
192
+ end
193
+
194
+ def close
195
+ @buffer.close
196
+ end
197
+ alias_method :stop, :close
198
+
199
+ class << self
200
+ def timer(seconds)
201
+ Channel.new(Buffer::Timer.new(seconds))
202
+ end
203
+ alias_method :after, :timer
204
+
205
+ def ticker(interval)
206
+ Channel.new(Buffer::Ticker.new(interval))
207
+ end
208
+ alias_method :tick, :ticker
209
+
210
+ def select(*args)
211
+ raise ArgumentError.new('no block given') unless block_given?
212
+ selector = Selector.new
213
+ yield(selector, *args)
214
+ selector.execute
215
+ end
216
+ alias_method :alt, :select
217
+
218
+ def go(*args, &block)
219
+ go_via(GOROUTINES, *args, &block)
220
+ end
221
+
222
+ def go_via(executor, *args, &block)
223
+ raise ArgumentError.new('no block given') unless block_given?
224
+ executor.post(*args, &block)
225
+ end
226
+
227
+ def go_loop(*args, &block)
228
+ go_loop_via(GOROUTINES, *args, &block)
229
+ end
230
+
231
+ def go_loop_via(executor, *args, &block)
232
+ raise ArgumentError.new('no block given') unless block_given?
233
+ executor.post(block, *args) do
234
+ loop do
235
+ break unless block.call(*args)
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ private
242
+
243
+ def validate(value, allow_nil, raise_error)
244
+ if !allow_nil && value.nil?
245
+ raise_error ? raise(ValidationError.new('nil is not a valid value')) : false
246
+ elsif !@validator.call(value)
247
+ raise_error ? raise(ValidationError) : false
248
+ else
249
+ true
250
+ end
251
+ rescue => ex
252
+ # the validator raised an exception
253
+ return raise_error ? raise(ex) : false
254
+ end
255
+
256
+ def do_put(item)
257
+ @buffer.put(item)
258
+ end
259
+
260
+ def do_offer(item)
261
+ @buffer.offer(item)
262
+ end
263
+
264
+ def do_next
265
+ @buffer.next
266
+ end
267
+
268
+ def do_poll
269
+ @buffer.poll
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,9 @@
1
+ require 'concurrent/channel/buffer/base'
2
+
3
+ require 'concurrent/channel/buffer/buffered'
4
+ require 'concurrent/channel/buffer/dropping'
5
+ require 'concurrent/channel/buffer/sliding'
6
+ require 'concurrent/channel/buffer/unbuffered'
7
+
8
+ require 'concurrent/channel/buffer/ticker'
9
+ require 'concurrent/channel/buffer/timer'
@@ -0,0 +1,197 @@
1
+ require 'concurrent/synchronization/lockable_object'
2
+
3
+ module Concurrent
4
+ class Channel
5
+ module Buffer
6
+
7
+ # Placeholder for when a buffer slot contains no value.
8
+ NO_VALUE = Object.new
9
+
10
+ # Abstract base class for all Channel buffers.
11
+ #
12
+ # {Concurrent::Channel} objects maintain an internal, queue-like
13
+ # object called a buffer. It's the storage bin for values put onto or
14
+ # taken from the channel. Different buffer types have different
15
+ # characteristics. Subsequently, the behavior of any given channel is
16
+ # highly dependent uping the type of its buffer. This is the base class
17
+ # which defines the common buffer interface. Any class intended to be
18
+ # used as a channel buffer should extend this class.
19
+ class Base < Synchronization::LockableObject
20
+
21
+ # @!macro [attach] channel_buffer_size_reader
22
+ #
23
+ # The maximum number of values which can be {#put} onto the buffer
24
+ # it becomes full.
25
+ attr_reader :size
26
+ alias_method :capacity, :size
27
+
28
+ # @!macro [attach] channel_buffer_initialize
29
+ #
30
+ # Creates a new buffer.
31
+ def initialize
32
+ super()
33
+ synchronize do
34
+ @closed = false
35
+ @size = 0
36
+ end
37
+ end
38
+
39
+ # @!macro [attach] channel_buffer_blocking_question
40
+ #
41
+ # Predicate indicating if this buffer will block {#put} operations
42
+ # once it reaches its maximum capacity.
43
+ #
44
+ # @return [Boolean] true if this buffer blocks else false
45
+ def blocking?
46
+ true
47
+ end
48
+
49
+ # @!macro [attach] channel_buffer_empty_question
50
+ #
51
+ # Predicate indicating if the buffer is empty.
52
+ #
53
+ # @return [Boolean] true if this buffer is empty else false
54
+ #
55
+ # @raise [NotImplementedError] until overridden in a subclass.
56
+ def empty?
57
+ raise NotImplementedError
58
+ end
59
+
60
+ # @!macro [attach] channel_buffer_full_question
61
+ #
62
+ # Predicate indicating if the buffer is full.
63
+ #
64
+ # @return [Boolean] true if this buffer is full else false
65
+ #
66
+ # @raise [NotImplementedError] until overridden in a subclass.
67
+ def full?
68
+ raise NotImplementedError
69
+ end
70
+
71
+ # @!macro [attach] channel_buffer_put
72
+ #
73
+ # Put an item onto the buffer if possible. If the buffer is open
74
+ # but not able to accept the item the calling thread will block
75
+ # until the item can be put onto the buffer.
76
+ #
77
+ # @param [Object] item the item/value to put onto the buffer.
78
+ # @return [Boolean] true if the item was added to the buffer else
79
+ # false (always false when closed).
80
+ #
81
+ # @raise [NotImplementedError] until overridden in a subclass.
82
+ def put(item)
83
+ raise NotImplementedError
84
+ end
85
+
86
+ # @!macro [attach] channel_buffer_offer
87
+ #
88
+ # Put an item onto the buffer is possible. If the buffer is open but
89
+ # unable to add an item, probably due to being full, the method will
90
+ # return immediately. Similarly, the method will return immediately
91
+ # when the buffer is closed. A return value of `false` does not
92
+ # necessarily indicate that the buffer is closed, just that the item
93
+ # could not be added.
94
+ #
95
+ # @param [Object] item the item/value to put onto the buffer.
96
+ # @return [Boolean] true if the item was added to the buffer else
97
+ # false (always false when closed).
98
+ #
99
+ # @raise [NotImplementedError] until overridden in a subclass.
100
+ def offer(item)
101
+ raise NotImplementedError
102
+ end
103
+
104
+ # @!macro [attach] channel_buffer_take
105
+ #
106
+ # Take an item from the buffer if one is available. If the buffer
107
+ # is open and no item is available the calling thread will block
108
+ # until an item is available. If the buffer is closed but items
109
+ # are available the remaining items can still be taken. Once the
110
+ # buffer closes, no remaining items can be taken.
111
+ #
112
+ # @return [Object] the item removed from the buffer; `NO_VALUE` once
113
+ # the buffer has closed.
114
+ #
115
+ # @raise [NotImplementedError] until overridden in a subclass.
116
+ def take
117
+ raise NotImplementedError
118
+ end
119
+
120
+ # @!macro [attach] channel_buffer_next
121
+ #
122
+ # Take the next item from the buffer and also return a boolean
123
+ # indicating if subsequent items can be taken. Used for iterating
124
+ # over a buffer until it is closed and empty.
125
+ #
126
+ # If the buffer is open but no items remain the calling thread will
127
+ # block until an item is available. The second of the two return
128
+ # values, a boolean, will always be `true` when the buffer is open.
129
+ # When the buffer is closed but more items remain the second return
130
+ # value will also be `true`. When the buffer is closed and the last
131
+ # item is taken the second return value will be `false`. When the
132
+ # buffer is both closed and empty the first return value will be
133
+ # `NO_VALUE` and the second return value will be `false`.
134
+ # be `false` when the buffer is both closed and empty.
135
+ #
136
+ # Note that when multiple threads access the same channel a race
137
+ # condition can occur when using this method. A call to `next` from
138
+ # one thread may return `true` for the second return value, but
139
+ # another thread may `take` the last value before the original
140
+ # thread makes another call. Code which iterates over a channel
141
+ # must be programmed to properly handle these race conditions.
142
+ #
143
+ # @return [Object, Boolean] the first return value will be the item
144
+ # taken from the buffer and the second return value will be a
145
+ # boolean indicating whether or not more items remain.
146
+ #
147
+ # @raise [NotImplementedError] until overridden in a subclass.
148
+ def next
149
+ raise NotImplementedError
150
+ end
151
+
152
+ # @!macro [attach] channel_buffer_poll
153
+ #
154
+ # Take the next item from the buffer if one is available else return
155
+ # immediately. Failing to return a value does not necessarily
156
+ # indicate that the buffer is closed, just that it is empty.
157
+ #
158
+ # @return [Object] the next item from the buffer or `NO_VALUE` if
159
+ # the buffer is empty.
160
+ #
161
+ # @raise [NotImplementedError] until overridden in a subclass.
162
+ def poll
163
+ raise NotImplementedError
164
+ end
165
+
166
+ # @!macro [attach] channel_buffer_close
167
+ #
168
+ # Close the buffer, preventing new items from being added. Once a
169
+ # buffer is closed it cannot be opened again.
170
+ #
171
+ # @return [Boolean] true if the buffer was open and successfully
172
+ # closed else false.
173
+ def close
174
+ synchronize do
175
+ @closed ? false : @closed = true
176
+ end
177
+ end
178
+
179
+ # @!macro [attach] channel_buffer_closed_question
180
+ #
181
+ # Predicate indicating is this buffer closed.
182
+ #
183
+ # @return [Boolea] true when closed else false.
184
+ def closed?
185
+ synchronize { ns_closed? }
186
+ end
187
+
188
+ private
189
+
190
+ # @!macro channel_buffer_closed_question
191
+ def ns_closed?
192
+ @closed
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end