concurrently 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|