concurrently 1.0.1
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 +7 -0
- data/.gitignore +5 -0
- data/.rspec +4 -0
- data/.travis.yml +16 -0
- data/.yardopts +7 -0
- data/Gemfile +17 -0
- data/LICENSE +176 -0
- data/README.md +129 -0
- data/RELEASE_NOTES.md +49 -0
- data/Rakefile +28 -0
- data/concurrently.gemspec +33 -0
- data/ext/Ruby/thread.rb +28 -0
- data/ext/all/array.rb +24 -0
- data/ext/mruby/array.rb +19 -0
- data/ext/mruby/fiber.rb +5 -0
- data/ext/mruby/io.rb +54 -0
- data/guides/Installation.md +46 -0
- data/guides/Overview.md +335 -0
- data/guides/Performance.md +140 -0
- data/guides/Troubleshooting.md +262 -0
- data/lib/Ruby/concurrently.rb +12 -0
- data/lib/Ruby/concurrently/error.rb +4 -0
- data/lib/Ruby/concurrently/event_loop.rb +24 -0
- data/lib/Ruby/concurrently/event_loop/io_selector.rb +38 -0
- data/lib/all/concurrently/error.rb +10 -0
- data/lib/all/concurrently/evaluation.rb +109 -0
- data/lib/all/concurrently/evaluation/error.rb +18 -0
- data/lib/all/concurrently/event_loop.rb +101 -0
- data/lib/all/concurrently/event_loop/fiber.rb +37 -0
- data/lib/all/concurrently/event_loop/io_selector.rb +42 -0
- data/lib/all/concurrently/event_loop/proc_fiber_pool.rb +18 -0
- data/lib/all/concurrently/event_loop/run_queue.rb +111 -0
- data/lib/all/concurrently/proc.rb +233 -0
- data/lib/all/concurrently/proc/evaluation.rb +246 -0
- data/lib/all/concurrently/proc/fiber.rb +67 -0
- data/lib/all/concurrently/version.rb +8 -0
- data/lib/all/io.rb +248 -0
- data/lib/all/kernel.rb +201 -0
- data/lib/mruby/concurrently/proc.rb +21 -0
- data/lib/mruby/kernel.rb +15 -0
- data/mrbgem.rake +42 -0
- data/perf/_shared/stage.rb +33 -0
- data/perf/concurrent_proc_call.rb +13 -0
- data/perf/concurrent_proc_call_and_forget.rb +15 -0
- data/perf/concurrent_proc_call_detached.rb +15 -0
- data/perf/concurrent_proc_call_nonblock.rb +13 -0
- data/perf/concurrent_proc_calls.rb +49 -0
- data/perf/concurrent_proc_calls_awaiting.rb +48 -0
- metadata +144 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'lib/all/concurrently/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "concurrently"
|
5
|
+
spec.version = Concurrently::VERSION
|
6
|
+
spec.summary = %q{A concurrency framework based on fibers}
|
7
|
+
spec.description = <<-DESC
|
8
|
+
Concurrently is a concurrency framework for Ruby and mruby. With it, concurrent
|
9
|
+
code can be written sequentially similar to async/await.
|
10
|
+
|
11
|
+
The concurrency primitive of Concurrently is the concurrent proc. It is very
|
12
|
+
similar to a regular proc. Calling a concurrent proc creates a concurrent
|
13
|
+
evaluation which is kind of a lightweight thread: It can wait for stuff without
|
14
|
+
blocking other concurrent evaluations.
|
15
|
+
|
16
|
+
Under the hood, concurrent procs are evaluated inside fibers. They can wait for
|
17
|
+
readiness of I/O or a period of time (or the result of other concurrent
|
18
|
+
evaluations).
|
19
|
+
DESC
|
20
|
+
|
21
|
+
spec.homepage = "https://github.com/christopheraue/m-ruby-concurrently"
|
22
|
+
spec.license = "Apache-2.0"
|
23
|
+
spec.authors = ["Christopher Aue"]
|
24
|
+
spec.email = ["rubygems@christopheraue.net"]
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.require_paths = ["lib/Ruby"]
|
27
|
+
|
28
|
+
spec.required_ruby_version = ">= 2.2.7"
|
29
|
+
|
30
|
+
spec.add_dependency "nio4r", "~> 2.1"
|
31
|
+
spec.add_dependency "hitimes", "~> 1.2"
|
32
|
+
spec.add_dependency "callbacks_attachable", "~> 2.2"
|
33
|
+
end
|
data/ext/Ruby/thread.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# @api ruby_patches
|
2
|
+
# @since 1.0.0
|
3
|
+
class Thread
|
4
|
+
# Attach an event loop to every thread in Ruby.
|
5
|
+
def __concurrently_event_loop__
|
6
|
+
@__concurrently_event_loop__ ||= Concurrently::EventLoop.new
|
7
|
+
end
|
8
|
+
|
9
|
+
# Disable fiber-local variables and treat variables using the fiber-local
|
10
|
+
# interface as thread-local. Most of the code out there is not using
|
11
|
+
# fibers explicitly and really intends to attach values to the current
|
12
|
+
# thread instead to the current fiber.
|
13
|
+
#
|
14
|
+
# This also makes sure we can safely reuse fibers without worrying about
|
15
|
+
# lost or leaked fiber-local variables.
|
16
|
+
|
17
|
+
# Redirect getting fiber locals to getting thread locals
|
18
|
+
alias_method :[], :thread_variable_get
|
19
|
+
|
20
|
+
# Redirect setting fiber locals to setting thread locals
|
21
|
+
alias_method :[]=, :thread_variable_set
|
22
|
+
|
23
|
+
# Redirect checking fiber local to checking thread local
|
24
|
+
alias_method :key?, :thread_variable?
|
25
|
+
|
26
|
+
# Redirect getting names for fiber locals to getting names of thread locals
|
27
|
+
alias_method :keys, :thread_variables
|
28
|
+
end
|
data/ext/all/array.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# @private
|
2
|
+
class Array
|
3
|
+
unless method_defined? :bsearch_index
|
4
|
+
# Implements Array#bsearch_index for mruby and Ruby < 2.3.
|
5
|
+
def bsearch_index
|
6
|
+
# adapted from https://github.com/python-git/python/blob/7e145963cd67c357fcc2e0c6aca19bc6ec9e64bb/Lib/bisect.py#L67
|
7
|
+
len = length
|
8
|
+
lo = 0
|
9
|
+
hi = len
|
10
|
+
|
11
|
+
while lo < hi
|
12
|
+
mid = (lo + hi).div(2)
|
13
|
+
|
14
|
+
if yield self[mid]
|
15
|
+
hi = mid
|
16
|
+
else
|
17
|
+
lo = mid + 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
lo == len ? nil : lo
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/ext/mruby/array.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# @api mruby_patches
|
2
|
+
# @since 1.0.0
|
3
|
+
class Array
|
4
|
+
# Alias for original Array#pop
|
5
|
+
alias_method :pop_single, :pop
|
6
|
+
|
7
|
+
# Reimplements Array#pop to add support for popping multiple items at once.
|
8
|
+
#
|
9
|
+
# By default, Array#pop can only pop a single item in mruby
|
10
|
+
def pop(n = nil)
|
11
|
+
if n
|
12
|
+
res = []
|
13
|
+
n.times{ res << pop_single }
|
14
|
+
res.reverse!
|
15
|
+
else
|
16
|
+
pop_single
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/ext/mruby/fiber.rb
ADDED
data/ext/mruby/io.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# @api mruby_patches
|
2
|
+
# @since 1.0.0
|
3
|
+
#
|
4
|
+
# mruby-io does not support non-blocking io operations.
|
5
|
+
class IO
|
6
|
+
unless const_defined? :EAGAIN
|
7
|
+
# raised if {IO#read_nonblock} or {IO#write_nonblock} would block
|
8
|
+
class EAGAIN < Exception; end
|
9
|
+
end
|
10
|
+
|
11
|
+
unless const_defined? :WaitReadable
|
12
|
+
# raised if IO#read_nonblock would block
|
13
|
+
module WaitReadable; end
|
14
|
+
class EAGAIN
|
15
|
+
include WaitReadable
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
unless const_defined? :WaitWritable
|
20
|
+
# raised if IO#write_nonblock would block
|
21
|
+
module WaitWritable; end
|
22
|
+
class EAGAIN
|
23
|
+
include WaitWritable
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
unless method_defined? :read_nonblock
|
28
|
+
# Implements IO#read_nonblock for mruby
|
29
|
+
#
|
30
|
+
# @see https://ruby-doc.org/core-1.9.3/IO.html#method-i-read_nonblock
|
31
|
+
# Ruby's documentation for IO#read_nonblock
|
32
|
+
def read_nonblock(maxlen, outbuf = nil)
|
33
|
+
if IO.select [self], nil, nil, 0
|
34
|
+
sysread(maxlen, outbuf)
|
35
|
+
else
|
36
|
+
raise EAGAIN, 'Resource temporarily unavailable - read would block'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
unless method_defined? :write_nonblock
|
42
|
+
# Implements IO#write_nonblock for mruby
|
43
|
+
#
|
44
|
+
# @see https://ruby-doc.org/core-1.9.3/IO.html#method-i-write_nonblock
|
45
|
+
# Ruby's documentation for `IO#write_nonblock`
|
46
|
+
def write_nonblock(string)
|
47
|
+
if IO.select nil, [self], nil, 0
|
48
|
+
syswrite(string)
|
49
|
+
else
|
50
|
+
raise EAGAIN, 'Resource temporarily unavailable - write would block'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# How to Install Concurrently
|
2
|
+
|
3
|
+
## Ruby
|
4
|
+
|
5
|
+
Install the gem manually with
|
6
|
+
|
7
|
+
$ gem install concurrently
|
8
|
+
|
9
|
+
or manage your application's dependencies with [Bundler](https://bundler.io/):
|
10
|
+
Run
|
11
|
+
|
12
|
+
$ bundle
|
13
|
+
|
14
|
+
after you added
|
15
|
+
|
16
|
+
gem 'concurrently'
|
17
|
+
|
18
|
+
to your Gemfile.
|
19
|
+
|
20
|
+
Finally,
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'concurrently'
|
24
|
+
```
|
25
|
+
|
26
|
+
in your application.
|
27
|
+
|
28
|
+
|
29
|
+
## mruby
|
30
|
+
|
31
|
+
To build Concurrently into mruby directly add it to mruby's build config or a
|
32
|
+
gem box:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
MRuby::Build.new do |conf| # or MRuby::GemBox.new do |conf|
|
36
|
+
conf.gem mgem: 'mruby-concurrently'
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
To use it in an mruby gem add it to the gem's specification as dependency:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
MRuby::Gem::Specification.new('mruby-your-gem') do |spec|
|
44
|
+
spec.add_dependency 'mruby-concurrently'
|
45
|
+
end
|
46
|
+
```
|
data/guides/Overview.md
ADDED
@@ -0,0 +1,335 @@
|
|
1
|
+
# An Overview of Concurrently
|
2
|
+
|
3
|
+
This document is meant as a general overview of what can be done with
|
4
|
+
Concurrently and how all its parts work together. For more information and
|
5
|
+
examples about a topic follow the interspersed links to the documentation.
|
6
|
+
|
7
|
+
## Evaluations
|
8
|
+
|
9
|
+
An evaluation is an atomic thread of execution leading to a result. It is
|
10
|
+
similar to a thread or a fiber. It can be suspended and resumed independently
|
11
|
+
from other evaluations. It is also similar to a future or a promise by
|
12
|
+
providing access to its future result or offering the ability to inject a
|
13
|
+
result manually. Once the evaluation has a result it is *concluded*.
|
14
|
+
|
15
|
+
Every ruby program already has an implicit [root evaluation][Concurrently::Evaluation]
|
16
|
+
running. Calling a concurrent proc creates a [proc evaluation][Concurrently::Proc::Evaluation].
|
17
|
+
|
18
|
+
## Concurrent Procs
|
19
|
+
|
20
|
+
The [concurrent proc][Concurrently::Proc] is Concurrently's concurrency
|
21
|
+
primitive. It looks and feels just like a regular proc. In fact,
|
22
|
+
[Concurrently::Proc][] inherits from `Proc`.
|
23
|
+
|
24
|
+
Concurrent procs are created with [Kernel#concurrent_proc][]:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
concurrent_proc do
|
28
|
+
# code to run concurrently
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
Concurrent procs can be used the same way regular procs are. For example, they
|
33
|
+
can be passed around or called multiple times with different arguments.
|
34
|
+
|
35
|
+
[Kernel#concurrently] is a shortcut for [Concurrently::Proc#call_and_forget][]:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
concurrently do
|
39
|
+
# code to run concurrently
|
40
|
+
end
|
41
|
+
|
42
|
+
# is equivalent to:
|
43
|
+
|
44
|
+
concurrent_proc do
|
45
|
+
# code to run concurrently
|
46
|
+
end.call_and_forget
|
47
|
+
```
|
48
|
+
|
49
|
+
### Calling Concurrent Procs
|
50
|
+
|
51
|
+
A concurrent proc has four methods to call it.
|
52
|
+
|
53
|
+
The first two evaluate the concurrent proc immediately in the foreground:
|
54
|
+
|
55
|
+
* [Concurrently::Proc#call][] blocks the (root or proc) evaluation it has been
|
56
|
+
called from until its own evaluation is concluded. Then it returns the
|
57
|
+
result. This behaves just like `Proc#call`.
|
58
|
+
* [Concurrently::Proc#call_nonblock][] will not block the (root or proc)
|
59
|
+
evaluation it has been called from if it needs to wait. Instead, it
|
60
|
+
immediately returns its [evaluation][Concurrently::Proc::Evaluation]. If it
|
61
|
+
can be evaluated without waiting it returns the result.
|
62
|
+
|
63
|
+
The other two schedule the concurrent proc to be run in the background. The
|
64
|
+
evaluation is not started right away but is deferred until the the next
|
65
|
+
iteration of the event loop:
|
66
|
+
|
67
|
+
* [Concurrently::Proc#call_detached][] returns an [evaluation][Concurrently::Proc::Evaluation].
|
68
|
+
* [Concurrently::Proc#call_and_forget][] does not give access to the evaluation
|
69
|
+
and returns `nil`.
|
70
|
+
|
71
|
+
|
72
|
+
## Timing Code
|
73
|
+
|
74
|
+
To defer the current evaluation for a fixed time use [Kernel#wait][].
|
75
|
+
|
76
|
+
* Doing something after X seconds:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
concurrent_proc do
|
80
|
+
wait X
|
81
|
+
do_it!
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
* Doing something every X seconds. This is a timer:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
concurrent_proc do
|
89
|
+
loop do
|
90
|
+
wait X
|
91
|
+
do_it!
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
* Doing something after X seconds, every Y seconds, Z times:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
concurrent_proc do
|
100
|
+
wait X
|
101
|
+
Z.times do
|
102
|
+
do_it!
|
103
|
+
wait Y
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
|
109
|
+
## Handling I/O
|
110
|
+
|
111
|
+
Readiness of I/O is awaited with [IO#await_readable][] and [IO#await_writable][].
|
112
|
+
To read and write from an IO concurrently you can use [IO#concurrently_read][]
|
113
|
+
and [IO#concurrently_write][].
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
r,w = IO.pipe
|
117
|
+
|
118
|
+
concurrently do
|
119
|
+
wait 1
|
120
|
+
w.concurrently_write "Continue!"
|
121
|
+
end
|
122
|
+
|
123
|
+
concurrently do
|
124
|
+
# This runs while r awaits readability.
|
125
|
+
end
|
126
|
+
|
127
|
+
concurrently do
|
128
|
+
# This runs while r awaits readability.
|
129
|
+
end
|
130
|
+
|
131
|
+
# Read from r. It will take one second until there is input.
|
132
|
+
message = r.concurrently_read 1024
|
133
|
+
|
134
|
+
puts message # prints "Continue!"
|
135
|
+
|
136
|
+
r.close
|
137
|
+
w.close
|
138
|
+
```
|
139
|
+
|
140
|
+
Other operations like accepting from a server socket need to be done by using
|
141
|
+
the corresponding `#*_nonblock` methods along with [IO#await_readable][] or
|
142
|
+
[IO#await_writable][]:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
require 'socket'
|
146
|
+
|
147
|
+
server = UNIXServer.new "/tmp/sock"
|
148
|
+
|
149
|
+
begin
|
150
|
+
socket = server.accept_nonblock
|
151
|
+
rescue IO::WaitReadable
|
152
|
+
server.await_readable
|
153
|
+
retry
|
154
|
+
end
|
155
|
+
|
156
|
+
# socket is an accepted socket.
|
157
|
+
```
|
158
|
+
|
159
|
+
|
160
|
+
## Flow of Control
|
161
|
+
|
162
|
+
To understand when code is run (and when it is not) it is necessary to know
|
163
|
+
a little bit more about the way Concurrently works.
|
164
|
+
|
165
|
+
Concurrently lets every (real) thread run an [event loop][Concurrently::EventLoop].
|
166
|
+
These event loops are responsible for watching IOs and scheduling evaluations
|
167
|
+
of concurrent procs. Evaluations are scheduled by putting them into a run queue
|
168
|
+
ordered by the time they are supposed to run. The run queue is then worked off
|
169
|
+
sequentially. If two evaluations are scheduled to run at the same time the
|
170
|
+
evaluation scheduled first is run first.
|
171
|
+
|
172
|
+
Event loops *do not* run parallel to your application's code at the exact same
|
173
|
+
time (e.g. on another cpu core). Instead, your code yields to them if it
|
174
|
+
waits for something: **The event loop is (and only is) entered if your code
|
175
|
+
calls `#wait` or one of the `#await_*` methods.** Later, when your code can
|
176
|
+
be resumed the event loop schedules the corresponding evaluation to run again.
|
177
|
+
|
178
|
+
Keep in mind, that an event loop **must never be interrupted, blocked or
|
179
|
+
overloaded.** A healthy event loop is one that can respond to new events
|
180
|
+
immediately.
|
181
|
+
|
182
|
+
If you are experiencing issues when using Concurrently it is probably due to
|
183
|
+
these properties of event loops. Have a look at the [Troubleshooting][] page.
|
184
|
+
|
185
|
+
|
186
|
+
## Implementing a Server Application
|
187
|
+
|
188
|
+
This is a blueprint how to build an application listening to a server socket,
|
189
|
+
accepting connections and serving requests through them.
|
190
|
+
|
191
|
+
At first, lets implement the server. It is initialized with a socket to listen
|
192
|
+
to. Listening calls the concurrent proc stored in the `RECEIVER` constant. It
|
193
|
+
then accepts or waits for incoming connections until the server is closed.
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
class ConcurrentServer
|
197
|
+
def initialize(socket)
|
198
|
+
@socket = socket
|
199
|
+
@listening = false
|
200
|
+
end
|
201
|
+
|
202
|
+
def listening?
|
203
|
+
@listening
|
204
|
+
end
|
205
|
+
|
206
|
+
def listen
|
207
|
+
@listening = true
|
208
|
+
RECEIVER.call_nonblock self, @socket
|
209
|
+
end
|
210
|
+
|
211
|
+
def close
|
212
|
+
@listening = false
|
213
|
+
@socket.close
|
214
|
+
end
|
215
|
+
|
216
|
+
RECEIVER = concurrent_proc do |server, socket|
|
217
|
+
while server.listening?
|
218
|
+
begin
|
219
|
+
Connection.new(socket.accept_nonblock).open
|
220
|
+
rescue IO::WaitReadable
|
221
|
+
socket.await_readable
|
222
|
+
retry
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
The implementation of the connection is structurally similar to the one of the
|
230
|
+
server. But because receiving data is a little bit more complex it is done in
|
231
|
+
an additional receive buffer object. Received requests are processed in their
|
232
|
+
own concurrent proc to not block the receiver loop if `request.process` calls
|
233
|
+
one of the wait methods.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class ConcurrentServer::Connection
|
237
|
+
def initialize(socket)
|
238
|
+
@socket = socket
|
239
|
+
@receive_buffer = ReceiveBuffer.new socket
|
240
|
+
@open = false
|
241
|
+
end
|
242
|
+
|
243
|
+
def open?
|
244
|
+
@open
|
245
|
+
end
|
246
|
+
|
247
|
+
def open
|
248
|
+
@open = true
|
249
|
+
RECEIVER.call_nonblock self, @receive_buffer
|
250
|
+
end
|
251
|
+
|
252
|
+
def close
|
253
|
+
@open = false
|
254
|
+
@socket.close
|
255
|
+
end
|
256
|
+
|
257
|
+
RECEIVER = concurrent_proc do |connection, receive_buffer|
|
258
|
+
while connection.open?
|
259
|
+
receive_buffer.receive
|
260
|
+
receive_buffer.shift_complete_requests.each do |request|
|
261
|
+
REQUEST_PROC.call_nonblock request
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
REQUEST_PROC = concurrent_proc do |request|
|
267
|
+
request.process
|
268
|
+
end
|
269
|
+
end
|
270
|
+
```
|
271
|
+
|
272
|
+
The receive buffer is responsible for reading from the connection's socket and
|
273
|
+
deserializing the received data.
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
class ConcurrentServer::Connection::ReceiveBuffer
|
277
|
+
def initialize(socket)
|
278
|
+
@socket = socket
|
279
|
+
@buffer = ''
|
280
|
+
end
|
281
|
+
|
282
|
+
def receive
|
283
|
+
@buffer << @socket.read_nonblock(32768)
|
284
|
+
rescue IO::WaitReadable
|
285
|
+
@socket.await_readable
|
286
|
+
retry
|
287
|
+
end
|
288
|
+
|
289
|
+
def shift_complete_requests
|
290
|
+
# Deserializes the buffer according to the used wire protocol, removes
|
291
|
+
# the consumed bytes of all completely received requests from the buffer
|
292
|
+
# and returns the requests.
|
293
|
+
end
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
Finally, this is a script bootstrapping two concurrent servers. The script
|
298
|
+
terminates after both servers were closed.
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
#!/bin/env ruby
|
302
|
+
|
303
|
+
require 'socket'
|
304
|
+
|
305
|
+
socket1 = UNIXServer.new "/tmp/sock1"
|
306
|
+
socket2 = UNIXServer.new "/tmp/sock2"
|
307
|
+
|
308
|
+
server_evaluation1 = ConcurrentServer.new(socket1).listen
|
309
|
+
server_evaluation2 = ConcurrentServer.new(socket2).listen
|
310
|
+
|
311
|
+
server_evaluation1.await_result # blocks until server 1 is closed
|
312
|
+
server_evaluation2.await_result # returns immediately if server 2 is already
|
313
|
+
# closed or blocks until it happens
|
314
|
+
```
|
315
|
+
|
316
|
+
Keep in mind, that to focus on the use of Concurrently the example does not
|
317
|
+
take error handling for I/O, properly closing all connections and other details
|
318
|
+
into account.
|
319
|
+
|
320
|
+
[Concurrently::Evaluation]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Evaluation
|
321
|
+
[Concurrently::Proc]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc
|
322
|
+
[Concurrently::Proc#call]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc#call-instance_method
|
323
|
+
[Concurrently::Proc#call_nonblock]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc#call_nonblock-instance_method
|
324
|
+
[Concurrently::Proc#call_detached]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc#call_detached-instance_method
|
325
|
+
[Concurrently::Proc#call_and_forget]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc#call_and_forget-instance_method
|
326
|
+
[Concurrently::Proc::Evaluation]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/Proc/Evaluation
|
327
|
+
[Concurrently::EventLoop]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Concurrently/EventLoop
|
328
|
+
[Kernel#concurrent_proc]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#concurrent_proc-instance_method
|
329
|
+
[Kernel#concurrently]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#concurrently-instance_method
|
330
|
+
[Kernel#wait]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/Kernel#wait-instance_method
|
331
|
+
[IO#await_readable]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_readable-instance_method
|
332
|
+
[IO#await_writable]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#await_writable-instance_method
|
333
|
+
[IO#concurrently_read]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#concurrently_read-instance_method
|
334
|
+
[IO#concurrently_write]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/IO#concurrently_write-instance_method
|
335
|
+
[Troubleshooting]: http://www.rubydoc.info/github/christopheraue/m-ruby-concurrently/file/guides/Troubleshooting.md
|