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 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