going 1.0.0 → 1.1.0

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: e654db8fb9029e5b32303e284551f9182960c418
4
- data.tar.gz: 62605bb00f54efe7dc250850f1341f10991cecb5
3
+ metadata.gz: 0d24f3e696f0f5be0f6cd9fb1267f32f9f5ed2df
4
+ data.tar.gz: 2504de315ea4e0db31e5772152a495a870fadb34
5
5
  SHA512:
6
- metadata.gz: ed12b04c91fd266ef44da9ef91b36f62b117d7cb869d3a8af21f831e6dfee4cc2c361aaa6e768d90f264e1cc2c97f3ae256a3445c19a3313d90966384fa16918
7
- data.tar.gz: d8a8b37ecc0f747f322ca779e08d59e8683dd62c11f873862ba80d7211de40f9667acfef1310b4206dd20ed1fda8ec5cbf096ac59e4c7ce9ea0f96cce60876c8
6
+ metadata.gz: 11e3bd89f64c0bbf2312bb1cbc80ee31145f2619b50f934e0fbcee30147dcdbd8b70d70e163bcf4e906e5a70bda53bc504795b43d86def996e54effc1cbd5c3c
7
+ data.tar.gz: 6c1f571f54a90a484be36d8d03936de988b836a4b9d18f3aca081dbb9f40a38e33784173f3e00ef9f234bd82ca4b08c561e7893ef7cddec34e49f1357567d460
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'pry-byebug'
4
+
3
5
  # Specify your gem's dependencies in going.gemspec
4
6
  gemspec
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Going [![Build Status](https://travis-ci.org/jridgewell/Going.svg)](https://travis-ci.org/jridgewell/Going)
2
2
 
3
- Go for Ruby
3
+ A Ruby implementation of Go Channels.
4
4
 
5
5
  ## Installation
6
6
 
@@ -20,7 +20,260 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- Brings Go's Channel and Goroutines to Ruby.
23
+ Wording stolen from the [Go Language
24
+ Specification](https://golang.org/ref/spec) and [Effective Go
25
+ Document](https://golang.org/doc/effective_go.html), and converted over
26
+ into the equivalent Ruby code.
27
+
28
+ ### Channels
29
+
30
+ Unbuffered channels combine communication — the exchange of a value —
31
+ with synchronization — guaranteeing that two calculations ("goroutines",
32
+ or threads) are in a known state.
33
+
34
+ There are lots of nice idioms using channels. Here's one to get us
35
+ started. A channel can allow the launching goroutine to wait for the
36
+ sort to complete.
37
+
38
+ ```ruby
39
+ list = [3, 2, 1]
40
+ c = Going::Channel.new # Allocate a channel.
41
+
42
+ # Start the sort in a goroutine; when it completes, signal on the channel.
43
+ Going.go do
44
+ list.sort!
45
+ c.push 1 # Send a signal; value does not matter.
46
+ end
47
+
48
+ # doSomethingForAWhile
49
+ c.receive # Wait for sort to finish; discard sent value.
50
+ ```
51
+
52
+ Receivers always block until there is data to receive. If the channel is
53
+ unbuffered, the sender blocks until the receiver has received the value.
54
+ If the channel has a buffer, the sender blocks only until the value has
55
+ been copied to the buffer; if the buffer is full, this means waiting
56
+ until some receiver has retrieved a value.
57
+
58
+ A buffered channel can be used like a semaphore, for instance to limit
59
+ throughput. In this example, incoming requests are passed to `handle`,
60
+ which sends a value into the channel, processes the request, and then
61
+ receives a value from the channel to ready the "semaphore" for the next
62
+ consumer. The capacity of the channel buffer limits the number of
63
+ simultaneous calls to process.
64
+
65
+ ```ruby
66
+ sem = Going::Channel.new(MaxOutstanding)
67
+
68
+ def handle(request)
69
+ sem.push 1 # Wait for active queue to drain.
70
+ process r # May take a long time.
71
+ sem.receive # Done; enable next request to run.
72
+ end
73
+
74
+ def serve(request_queue)
75
+ request_queue.each do |req|
76
+ Going.go do
77
+ handle req # Don't wait for handle to finish.
78
+ end
79
+ end
80
+ end
81
+ ```
82
+
83
+ Once `MaxOutstanding` handlers are executing `process`, any more will
84
+ block trying to send into the filled channel buffer, until one of the
85
+ existing handlers finishes and receives from the buffer.
86
+
87
+ This design has a problem, though: `serve` creates a new goroutine for
88
+ every incoming request, even though only `MaxOutstanding` of them can
89
+ run at any moment. As a result, the program can consume unlimited
90
+ resources if the requests come in too fast. We can address that
91
+ deficiency by changing `serve` to gate the creation of the goroutines.
92
+ Here's an obvious solution.
93
+
94
+ ```ruby
95
+ def serve(request_queue) {
96
+ request_queue.each do |req|
97
+ sem.push 1
98
+ Going.go do
99
+ process req
100
+ sem.receive
101
+ end
102
+ end
103
+ end
104
+ ```
105
+
106
+ Going back to the general problem of writing the server, another
107
+ approach that manages resources well is to start a fixed number of
108
+ `handle` goroutines all reading from the request channel. The number of
109
+ goroutines limits the number of simultaneous calls to process. This
110
+ `serve` function also accepts a channel on which it will be told to
111
+ exit; after launching the goroutines it blocks receiving from that
112
+ channel.
113
+
114
+ ```ruby
115
+ def handle(request_queue)
116
+ request_queue.each do |req|
117
+ process req
118
+ end
119
+ end
120
+
121
+ def serve(request_queue, quit) {
122
+ # Start handlers
123
+ MaxOutstanding.times do
124
+ Going.go do
125
+ handle request_queue
126
+ end
127
+ end
128
+ quit.receive # Wait to be told to exit.
129
+ end
130
+ ```
131
+
132
+ ### Close
133
+
134
+ For a channel `ch`, the method `ch.close` records that no more values
135
+ will be sent on the channel. Sending to a closed channel causes an
136
+ exception to be thrown. After calling `#close`, and after any previously
137
+ sent values have been received, receive operations will throw the
138
+ `:close` symbol.
139
+
140
+ ```ruby
141
+ ch = Going::Channel.new 2
142
+
143
+ # Push an initial value into the buffered channel
144
+ ch.push 1
145
+
146
+ # Close the channel, preventing any futher message
147
+ ch.close
148
+
149
+ begin
150
+ ch.push 2
151
+ rescue
152
+ # Sending to a closed channel causes an exception
153
+ end
154
+
155
+ # You may receive already sent values
156
+ ch.receive # => 1
157
+
158
+ # Closed channels throw when there are no more messages
159
+ catch :close do
160
+ ch.receive
161
+ end
162
+ ```
163
+
164
+ ### Size
165
+
166
+ For a channel `ch`, the method `ch.size` returns the number of completed
167
+ send operations on the channel. For an unbuffered channel, that number
168
+ is always 0.
169
+
170
+ ```ruby
171
+ unbuffered_channel = Going::Channel.new
172
+ unbuffered_channel.size # => 0
173
+
174
+ Going.go do
175
+ unbuffered_channel.push 'message'
176
+ end
177
+ # after the goroutine has blocked on send
178
+ unbuffered_channel.size # => 0
179
+
180
+
181
+ buffered_channel = Going::Channel.new 2
182
+ buffered_channel.size # => 0
183
+
184
+ buffered_channel.push 'message'
185
+ buffered_channel.size # => 1
186
+
187
+ buffered_channel.push 'message'
188
+ buffered_channel.size # => 2
189
+
190
+ buffered_channel.receive
191
+ buffered_channel.size # => 1
192
+ ```
193
+
194
+ ### Capacity
195
+
196
+ For a channel `ch`, the method `ch.capacity` returns the buffer size of
197
+ the channel. For an unbuffered channel, that number is 0.
198
+
199
+ ```ruby
200
+ unbuffered_channel = Going::Channel.new
201
+ unbuffered_channel.capacity # => 0
202
+
203
+
204
+ buffered_channel = Going::Channel.new 2
205
+ buffered_channel.capacity # => 2
206
+
207
+ buffered_channel.push 'message'
208
+ buffered_channel.capacity # => 2
209
+ ```
210
+
211
+ ### Select Statements
212
+
213
+ A "select" statement chooses which of a set of possible send or receive
214
+ operations will proceed. It acts similar to a "case" statement but
215
+ with the cases all referring to communication operations.
216
+
217
+ Execution of a "select" statement proceeds in several steps:
218
+
219
+ 1. For all the cases in the statement, the channel operands of receive
220
+ operations and the channel and right-hand-side expressions of send
221
+ statements are evaluated exactly once, in source order, upon entering
222
+ the "select" statement. The result is a set of channels to receive
223
+ from or send to, and the corresponding values to send. Any side
224
+ effects in that evaluation will occur irrespective of which (if any)
225
+ communication operation is selected to proceed. Expressions on the
226
+ left-hand side of a receive statement with a variable assignment are
227
+ not evaluated.
228
+
229
+ 2. If one or more of the communications can proceed, a single one that
230
+ can proceed is chosen in source order. Otherwise, if there is a
231
+ default case, that case is chosen. If there is no default case, the
232
+ "select" statement blocks until at least one of the communications
233
+ can proceed.
234
+
235
+ 3. Unless the selected case is the default case, the respective
236
+ communication operation is executed.
237
+
238
+ 4. If the selected case is a receive statement with a variable
239
+ assignment, the corresponding block is executed with the received
240
+ message as the first parameter. A second, optional, hash is
241
+ also passed, with a single key `ok`. `ok` will equal `true` if the
242
+ channel is not closed, or `false` if the channel is closed.
243
+
244
+ 5. If the selected case is a send statement, the corresponding block is
245
+ executed.
246
+
247
+ ```ruby
248
+ Going.select do
249
+ channel.receive { |msg|
250
+ # do something with `msg`.
251
+ }
252
+
253
+ channel2.push(1) {
254
+ # do something after pushing
255
+ }
256
+
257
+ channel3.receive { |msg, ok: true|
258
+ if ok
259
+ # do something with msg
260
+ else
261
+ # channel3 was closed, msg is `nil`
262
+ end
263
+ }
264
+
265
+ timeout(5) {
266
+ # 5 second passed and no channel operations succeeded.
267
+ }
268
+
269
+ default {
270
+ # An immediately executing block, if nothing has succeeded yet
271
+ }
272
+ end
273
+ ```
274
+
275
+
276
+ ## Obligatory Sieve of Eratosthenes Example
24
277
 
25
278
  ```ruby
26
279
  require 'going'
data/lib/going.rb CHANGED
@@ -27,8 +27,13 @@ module Going
27
27
  #
28
28
  def self.select(&blk)
29
29
  fail 'a block must be passed' unless block_given?
30
+
30
31
  select = SelectStatement.new_instance
31
32
  select.select(&blk)
32
33
  SelectStatement.reset
34
+
35
+ select.call_completion_block
36
+
37
+ nil
33
38
  end
34
39
  end
data/lib/going/channel.rb CHANGED
@@ -124,6 +124,22 @@ module Going
124
124
  size == 0
125
125
  end
126
126
 
127
+ #
128
+ # Calls the given block once for each message until the channel is closed,
129
+ # passing that message as a parameter.
130
+ #
131
+ # Note that this is a destructive action, since each message is `shift`ed.
132
+ #
133
+ def each
134
+ return enum_for(:each) unless block_given?
135
+
136
+ catch :close do
137
+ loop do
138
+ yield self.shift
139
+ end
140
+ end
141
+ end
142
+
127
143
  def inspect
128
144
  inspection = [:capacity, :size].map do |attr|
129
145
  "#{attr}: #{send(attr).inspect}"
@@ -21,7 +21,7 @@ module Going
21
21
  Channel.new do |ch|
22
22
  Going.go do
23
23
  sleep seconds
24
- ch.receive
24
+ ch.shift
25
25
  end
26
26
  ch.push(nil, &blk)
27
27
  end
@@ -52,7 +52,6 @@ module Going
52
52
 
53
53
  wait
54
54
  cleanup
55
- call_completion_block
56
55
  end
57
56
 
58
57
  def when_complete(*args, &callback)
@@ -88,6 +87,10 @@ module Going
88
87
  end
89
88
  end
90
89
 
90
+ def call_completion_block
91
+ on_complete.call(*args) if on_complete
92
+ end
93
+
91
94
  private
92
95
 
93
96
  attr_reader :semaphore, :once_mutex, :complete_mutex, :when_completes
@@ -113,9 +116,5 @@ module Going
113
116
  def cleanup
114
117
  when_completes.each(&:call)
115
118
  end
116
-
117
- def call_completion_block
118
- on_complete.call(*args) if on_complete
119
- end
120
119
  end
121
120
  end
data/lib/going/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Going
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -285,4 +285,26 @@ describe Going::Channel do
285
285
  end
286
286
  end
287
287
  end
288
+
289
+ describe '#each' do
290
+ it 'yields for each message until channel is closed' do
291
+ Going.go do
292
+ 10.times do |i|
293
+ channel.push i
294
+ end
295
+ channel.close
296
+ end
297
+
298
+ i = 0
299
+ channel.each do |message|
300
+ expect(message).to eq(i)
301
+ i += 1
302
+ end
303
+ expect(i).to eq(10)
304
+ end
305
+
306
+ it 'returns an enumerator if no block is given' do
307
+ expect(channel.each).to be_an Enumerator
308
+ end
309
+ end
288
310
  end
@@ -102,6 +102,14 @@ describe Going::SelectStatement do
102
102
  expect(buffered_channel.size).to eq(0)
103
103
  end
104
104
 
105
+ it 'calls succeeding block after the select_statement has been evaluated' do
106
+ Going.select do |s|
107
+ s.default do
108
+ expect(Going::SelectStatement.instance).to be_nil
109
+ end
110
+ end
111
+ end
112
+
105
113
  context 'buffered channels' do
106
114
  it 'succeeds when blocked push is now under capacity' do
107
115
  buffered_channel.push 1
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: going
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Ridgewell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-28 00:00:00.000000000 Z
11
+ date: 2014-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler