async 1.25.2 → 1.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/async/barrier.rb +1 -1
- data/lib/async/clock.rb +33 -1
- data/lib/async/logger.rb +1 -6
- data/lib/async/node.rb +20 -2
- data/lib/async/queue.rb +5 -1
- data/lib/async/reactor.rb +73 -12
- data/lib/async/scheduler.rb +112 -0
- data/lib/async/task.rb +11 -3
- data/lib/async/version.rb +1 -1
- metadata +46 -104
- data/.editorconfig +0 -6
- data/.github/workflows/development.yml +0 -55
- data/.gitignore +0 -14
- data/.rspec +0 -3
- data/.yardopts +0 -1
- data/Gemfile +0 -20
- data/Guardfile +0 -14
- data/README.md +0 -385
- data/Rakefile +0 -40
- data/async.gemspec +0 -34
- data/bake.rb +0 -33
- data/benchmark/async_vs_lightio.rb +0 -84
- data/benchmark/fiber_count.rb +0 -10
- data/benchmark/rubies/README.md +0 -51
- data/benchmark/rubies/benchmark.rb +0 -220
- data/benchmark/thread_count.rb +0 -9
- data/benchmark/thread_vs_fiber.rb +0 -45
- data/examples/async_method.rb +0 -60
- data/examples/callback/loop.rb +0 -44
- data/examples/capture/README.md +0 -59
- data/examples/capture/capture.rb +0 -116
- data/examples/fibers.rb +0 -178
- data/examples/queue/producer.rb +0 -28
- data/examples/sleep_sort.rb +0 -40
- data/examples/stop/condition.rb +0 -31
- data/examples/stop/sleep.rb +0 -42
- data/gems/event.gemfile +0 -4
- data/logo.png +0 -0
- data/logo.svg +0 -64
- data/papers/1982 Grossman.pdf +0 -0
- data/papers/1987 ODell.pdf +0 -0
- data/spec/async/barrier_spec.rb +0 -116
- data/spec/async/chainable_async_examples.rb +0 -13
- data/spec/async/clock_spec.rb +0 -37
- data/spec/async/condition_examples.rb +0 -105
- data/spec/async/condition_spec.rb +0 -72
- data/spec/async/logger_spec.rb +0 -65
- data/spec/async/node_spec.rb +0 -193
- data/spec/async/notification_spec.rb +0 -66
- data/spec/async/performance_spec.rb +0 -72
- data/spec/async/queue_spec.rb +0 -129
- data/spec/async/reactor/nested_spec.rb +0 -52
- data/spec/async/reactor_spec.rb +0 -253
- data/spec/async/semaphore_spec.rb +0 -169
- data/spec/async/task_spec.rb +0 -476
- data/spec/async/wrapper_spec.rb +0 -203
- data/spec/async_spec.rb +0 -33
- data/spec/enumerator_spec.rb +0 -83
- data/spec/kernel/async_spec.rb +0 -33
- data/spec/kernel/sync_spec.rb +0 -54
- data/spec/spec_helper.rb +0 -18
data/lib/async/version.rb
CHANGED
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.28.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: console
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '1.10'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '1.10'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: nio4r
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.3'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: timers
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1
|
47
|
+
version: '4.1'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1
|
54
|
+
version: '4.1'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: async-rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,19 +67,33 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.1'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: bake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0
|
75
|
+
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: benchmark-ips
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: bundler
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,68 +109,39 @@ dependencies:
|
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
112
|
+
name: covered
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
115
|
- - "~>"
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
117
|
+
version: '0.10'
|
104
118
|
type: :development
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
124
|
+
version: '0.10'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
126
|
+
name: rspec
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
|
-
- - "
|
129
|
+
- - "~>"
|
116
130
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
131
|
+
version: '3.6'
|
118
132
|
type: :development
|
119
133
|
prerelease: false
|
120
134
|
version_requirements: !ruby/object:Gem::Requirement
|
121
135
|
requirements:
|
122
|
-
- - "
|
136
|
+
- - "~>"
|
123
137
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
125
|
-
description:
|
126
|
-
the\n\t\treactor pattern, providing both non-blocking I/O and timer events.\n"
|
138
|
+
version: '3.6'
|
139
|
+
description:
|
127
140
|
email:
|
128
|
-
- samuel.williams@oriontransfer.co.nz
|
129
141
|
executables: []
|
130
142
|
extensions: []
|
131
143
|
extra_rdoc_files: []
|
132
144
|
files:
|
133
|
-
- ".editorconfig"
|
134
|
-
- ".github/workflows/development.yml"
|
135
|
-
- ".gitignore"
|
136
|
-
- ".rspec"
|
137
|
-
- ".yardopts"
|
138
|
-
- Gemfile
|
139
|
-
- Guardfile
|
140
|
-
- README.md
|
141
|
-
- Rakefile
|
142
|
-
- async.gemspec
|
143
|
-
- bake.rb
|
144
|
-
- benchmark/async_vs_lightio.rb
|
145
|
-
- benchmark/fiber_count.rb
|
146
|
-
- benchmark/rubies/README.md
|
147
|
-
- benchmark/rubies/benchmark.rb
|
148
|
-
- benchmark/thread_count.rb
|
149
|
-
- benchmark/thread_vs_fiber.rb
|
150
|
-
- examples/async_method.rb
|
151
|
-
- examples/callback/loop.rb
|
152
|
-
- examples/capture/README.md
|
153
|
-
- examples/capture/capture.rb
|
154
|
-
- examples/fibers.rb
|
155
|
-
- examples/queue/producer.rb
|
156
|
-
- examples/sleep_sort.rb
|
157
|
-
- examples/stop/condition.rb
|
158
|
-
- examples/stop/sleep.rb
|
159
|
-
- gems/event.gemfile
|
160
145
|
- lib/async.rb
|
161
146
|
- lib/async/barrier.rb
|
162
147
|
- lib/async/clock.rb
|
@@ -168,41 +153,18 @@ files:
|
|
168
153
|
- lib/async/notification.rb
|
169
154
|
- lib/async/queue.rb
|
170
155
|
- lib/async/reactor.rb
|
156
|
+
- lib/async/scheduler.rb
|
171
157
|
- lib/async/semaphore.rb
|
172
158
|
- lib/async/task.rb
|
173
159
|
- lib/async/version.rb
|
174
160
|
- lib/async/wrapper.rb
|
175
161
|
- lib/kernel/async.rb
|
176
162
|
- lib/kernel/sync.rb
|
177
|
-
- logo.png
|
178
|
-
- logo.svg
|
179
|
-
- papers/1982 Grossman.pdf
|
180
|
-
- papers/1987 ODell.pdf
|
181
|
-
- spec/async/barrier_spec.rb
|
182
|
-
- spec/async/chainable_async_examples.rb
|
183
|
-
- spec/async/clock_spec.rb
|
184
|
-
- spec/async/condition_examples.rb
|
185
|
-
- spec/async/condition_spec.rb
|
186
|
-
- spec/async/logger_spec.rb
|
187
|
-
- spec/async/node_spec.rb
|
188
|
-
- spec/async/notification_spec.rb
|
189
|
-
- spec/async/performance_spec.rb
|
190
|
-
- spec/async/queue_spec.rb
|
191
|
-
- spec/async/reactor/nested_spec.rb
|
192
|
-
- spec/async/reactor_spec.rb
|
193
|
-
- spec/async/semaphore_spec.rb
|
194
|
-
- spec/async/task_spec.rb
|
195
|
-
- spec/async/wrapper_spec.rb
|
196
|
-
- spec/async_spec.rb
|
197
|
-
- spec/enumerator_spec.rb
|
198
|
-
- spec/kernel/async_spec.rb
|
199
|
-
- spec/kernel/sync_spec.rb
|
200
|
-
- spec/spec_helper.rb
|
201
163
|
homepage: https://github.com/socketry/async
|
202
164
|
licenses:
|
203
165
|
- MIT
|
204
166
|
metadata: {}
|
205
|
-
post_install_message:
|
167
|
+
post_install_message:
|
206
168
|
rdoc_options: []
|
207
169
|
require_paths:
|
208
170
|
- lib
|
@@ -217,28 +179,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
217
179
|
- !ruby/object:Gem::Version
|
218
180
|
version: '0'
|
219
181
|
requirements: []
|
220
|
-
rubygems_version: 3.
|
221
|
-
signing_key:
|
182
|
+
rubygems_version: 3.2.3
|
183
|
+
signing_key:
|
222
184
|
specification_version: 4
|
223
|
-
summary:
|
224
|
-
test_files:
|
225
|
-
- spec/async/barrier_spec.rb
|
226
|
-
- spec/async/chainable_async_examples.rb
|
227
|
-
- spec/async/clock_spec.rb
|
228
|
-
- spec/async/condition_examples.rb
|
229
|
-
- spec/async/condition_spec.rb
|
230
|
-
- spec/async/logger_spec.rb
|
231
|
-
- spec/async/node_spec.rb
|
232
|
-
- spec/async/notification_spec.rb
|
233
|
-
- spec/async/performance_spec.rb
|
234
|
-
- spec/async/queue_spec.rb
|
235
|
-
- spec/async/reactor/nested_spec.rb
|
236
|
-
- spec/async/reactor_spec.rb
|
237
|
-
- spec/async/semaphore_spec.rb
|
238
|
-
- spec/async/task_spec.rb
|
239
|
-
- spec/async/wrapper_spec.rb
|
240
|
-
- spec/async_spec.rb
|
241
|
-
- spec/enumerator_spec.rb
|
242
|
-
- spec/kernel/async_spec.rb
|
243
|
-
- spec/kernel/sync_spec.rb
|
244
|
-
- spec/spec_helper.rb
|
185
|
+
summary: A concurrency framework for Ruby.
|
186
|
+
test_files: []
|
data/.editorconfig
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
name: Development
|
2
|
-
|
3
|
-
on: [push, pull_request]
|
4
|
-
|
5
|
-
jobs:
|
6
|
-
test:
|
7
|
-
runs-on: ${{matrix.os}}-latest
|
8
|
-
continue-on-error: ${{matrix.experimental}}
|
9
|
-
|
10
|
-
strategy:
|
11
|
-
matrix:
|
12
|
-
experimental: [false]
|
13
|
-
|
14
|
-
os:
|
15
|
-
- ubuntu
|
16
|
-
- macos
|
17
|
-
|
18
|
-
ruby:
|
19
|
-
- 2.5
|
20
|
-
- 2.6
|
21
|
-
- 2.7
|
22
|
-
|
23
|
-
include:
|
24
|
-
- experimental: true
|
25
|
-
os: ubuntu
|
26
|
-
ruby: truffleruby
|
27
|
-
env: JRUBY_OPTS="--debug -X+O"
|
28
|
-
- experimental: true
|
29
|
-
os: ubuntu
|
30
|
-
ruby: jruby
|
31
|
-
- experimental: true
|
32
|
-
os: ubuntu
|
33
|
-
ruby: head
|
34
|
-
- experimental: true
|
35
|
-
os: ubuntu
|
36
|
-
ruby: 2.6
|
37
|
-
env: COVERAGE=PartialSummary,Coveralls
|
38
|
-
|
39
|
-
steps:
|
40
|
-
- uses: actions/checkout@v1
|
41
|
-
- uses: ruby/setup-ruby@v1
|
42
|
-
with:
|
43
|
-
ruby-version: ${{matrix.ruby}}
|
44
|
-
|
45
|
-
- name: Install dependencies
|
46
|
-
run: ${{matrix.env}} bundle install
|
47
|
-
|
48
|
-
- name: Run tests
|
49
|
-
timeout-minutes: 5
|
50
|
-
run: ${{matrix.env}} bundle exec rspec
|
51
|
-
|
52
|
-
- name: Run external tests
|
53
|
-
timeout-minutes: 5
|
54
|
-
if: matrix.experimental == false && matrix.os == 'ubuntu'
|
55
|
-
run: ${{matrix.env}} bundle exec bake external
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.yardopts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--markup markdown
|
data/Gemfile
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
source 'https://rubygems.org'
|
4
|
-
|
5
|
-
gemspec
|
6
|
-
|
7
|
-
group :development do
|
8
|
-
gem 'pry'
|
9
|
-
gem 'guard-rspec'
|
10
|
-
gem 'guard-yard'
|
11
|
-
|
12
|
-
gem 'yard'
|
13
|
-
end
|
14
|
-
|
15
|
-
group :test do
|
16
|
-
gem 'benchmark-ips'
|
17
|
-
gem 'ruby-prof', platforms: :mri
|
18
|
-
|
19
|
-
gem 'covered', require: 'covered/rspec'
|
20
|
-
end
|
data/Guardfile
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
directories %w(lib spec)
|
4
|
-
clearing :on
|
5
|
-
|
6
|
-
guard :rspec, cmd: "bundle exec rspec" do
|
7
|
-
watch(%r{^spec/.+_spec\.rb$})
|
8
|
-
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
9
|
-
watch("spec/spec_helper.rb") { "spec" }
|
10
|
-
end
|
11
|
-
|
12
|
-
guard 'yard', :port => '8808' do
|
13
|
-
watch(%r{^lib/(.+)\.rb$})
|
14
|
-
end
|
data/README.md
DELETED
@@ -1,385 +0,0 @@
|
|
1
|
-
# ![Async](logo.svg)
|
2
|
-
|
3
|
-
Async is a composable asynchronous I/O framework for Ruby based on [nio4r] and [timers].
|
4
|
-
|
5
|
-
[timers]: https://github.com/socketry/timers
|
6
|
-
[nio4r]: https://github.com/socketry/nio4r
|
7
|
-
|
8
|
-
[![Actions Status](https://github.com/socketry/async/workflows/Development/badge.svg)](https://github.com/socketry/async/actions?workflow=Development)
|
9
|
-
[![Code Climate](https://codeclimate.com/github/socketry/async.svg)](https://codeclimate.com/github/socketry/async)
|
10
|
-
[![Coverage Status](https://coveralls.io/repos/socketry/async/badge.svg)](https://coveralls.io/r/socketry/async)
|
11
|
-
[![Gitter](https://badges.gitter.im/join.svg)](https://gitter.im/socketry/async)
|
12
|
-
|
13
|
-
> "Lately I've been looking into `async`, as one of my projects – [tus-ruby-server](https://github.com/janko/tus-ruby-server) – would really benefit from non-blocking I/O. It's really beautifully designed." *– [janko](https://github.com/janko)*
|
14
|
-
|
15
|
-
## Motivation
|
16
|
-
|
17
|
-
Several years ago, I was hosting websites on a server in my garage. Back then, my ADSL modem was very basic, and I wanted to have a DNS server which would resolve to an internal IP address when the domain itself resolved to my public IP. Thus was born [RubyDNS]. This project [was originally built on](https://github.com/ioquatix/rubydns/tree/v0.8.5) top of [EventMachine], but a lack of support for [IPv6 at the time](https://github.com/ioquatix/rubydns/issues/45) and [other problems](https://github.com/ioquatix/rubydns/issues/14), meant that I started looking for other options. Around that time [Celluloid] was picking up steam. I had not encountered actors before and I wanted to learn more about it. So, [I reimplemented RubyDNS on top of Celluloid](https://github.com/ioquatix/rubydns/tree/v0.9.0) and this eventually became the first stable release.
|
18
|
-
|
19
|
-
Moving forward, I refactored the internals of RubyDNS into [Celluloid::DNS]. This rewrite helped solidify the design of RubyDNS and to a certain extent it works. However, [unfixed bugs and design problems](https://github.com/celluloid/celluloid/pull/710) in Celluloid meant that RubyDNS 2.0 was delayed by almost 2 years. I wasn't happy releasing it with known bugs and problems. After working on the issues for a while, and thinking about possible solutions, I decided to build a small event reactor using [nio4r] and [timers], the core parts of [Celluloid::IO] which made it work so well. The result is this project.
|
20
|
-
|
21
|
-
One observation I made when looking at existing gems for asynchronous IO was a tendency to try and do everything within a single code-base. The design of this core library is deliberately simple. Additional libraries provide asynchronous networking, process management, etc. It's likely you will prefer to depend on [async-io] for actual wrappers around `IO` and `Socket`. This helps to ensure a clean separation of concerns.
|
22
|
-
|
23
|
-
In designing this library, I also built a [similarly designed C++ library of the same name](https://github.com/kurocha/async). These two libraries share similar design principles.
|
24
|
-
|
25
|
-
[Celluloid]: https://github.com/celluloid/celluloid
|
26
|
-
[Celluloid::IO]: https://github.com/celluloid/celluloid-io
|
27
|
-
[Celluloid::DNS]: https://github.com/celluloid/celluloid-dns
|
28
|
-
[EventMachine]: https://github.com/eventmachine/eventmachine
|
29
|
-
[RubyDNS]: https://github.com/ioquatix/rubydns
|
30
|
-
[async-io]: https://github.com/socketry/async-io
|
31
|
-
|
32
|
-
## Installation
|
33
|
-
|
34
|
-
Add this line to your application's Gemfile:
|
35
|
-
|
36
|
-
```ruby
|
37
|
-
gem "async"
|
38
|
-
```
|
39
|
-
|
40
|
-
And then execute:
|
41
|
-
|
42
|
-
$ bundle
|
43
|
-
|
44
|
-
Or install it yourself as:
|
45
|
-
|
46
|
-
$ gem install async
|
47
|
-
|
48
|
-
## Usage
|
49
|
-
|
50
|
-
Please [try the interactive online tutorial](https://katacoda.com/ioquatix/scenarios/async-introduction).
|
51
|
-
|
52
|
-
### Tasks
|
53
|
-
|
54
|
-
An `Async::Task` runs using a `Fiber` and blocking operations e.g. `sleep`, `read`, `write` yield control until the operation can complete. There are two main methods to create tasks.
|
55
|
-
|
56
|
-
#### `Async{...}`
|
57
|
-
|
58
|
-
The highest level entry point is `Async{...}`. It's useful if you are building a library and you want well defined asynchronous semantics. This internally invokes `Async::Reactor.run{...}`.
|
59
|
-
|
60
|
-
```ruby
|
61
|
-
def run_server
|
62
|
-
Async do |task|
|
63
|
-
# ... acccept connections
|
64
|
-
end
|
65
|
-
end
|
66
|
-
```
|
67
|
-
|
68
|
-
If `Async(&block)` happens within an existing reactor, it will schedule an asynchronous task and return. If `Async(&block)` happens outside of an existing reactor, it will create a reactor, schedule the asynchronous task, and block until it completes. The task is scheduled by calling `Async::Reactor#async(&block)`.
|
69
|
-
|
70
|
-
This allows the caller to have either blocking or non-blocking behaviour.
|
71
|
-
|
72
|
-
```ruby
|
73
|
-
require 'async'
|
74
|
-
|
75
|
-
def sleepy(duration = 1)
|
76
|
-
Async do |task|
|
77
|
-
task.sleep duration
|
78
|
-
puts "I'm done sleeping, time for action!"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Synchronous operation:
|
83
|
-
sleepy
|
84
|
-
|
85
|
-
# Asynchronous operation:
|
86
|
-
Async do
|
87
|
-
# These two functions will sleep simultaneously.
|
88
|
-
sleepy
|
89
|
-
sleepy
|
90
|
-
end
|
91
|
-
```
|
92
|
-
|
93
|
-
The cost of using `Async{...}` is minimal for initialization/server setup, but is not ideal for per-connection tasks.
|
94
|
-
|
95
|
-
#### `Async::Task#async`
|
96
|
-
|
97
|
-
If you can guarantee you are running within a task, and have access to it (e.g. via an argument), you can efficiently schedule new tasks using the `Async::Task#async(&block)` method.
|
98
|
-
|
99
|
-
```ruby
|
100
|
-
require 'async'
|
101
|
-
|
102
|
-
def nested_sleepy(task: Async::Task.current)
|
103
|
-
# Block caller
|
104
|
-
task.sleep 0.1
|
105
|
-
|
106
|
-
# Schedule nested task:
|
107
|
-
subtask = task.async do |subtask|
|
108
|
-
puts "I'm going to sleep..."
|
109
|
-
subtask.sleep 1.0
|
110
|
-
ensure
|
111
|
-
puts "I'm waking up!"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
Async do |task|
|
116
|
-
subtask = nested_sleepy(task: task)
|
117
|
-
end
|
118
|
-
```
|
119
|
-
|
120
|
-
This example creates a child `subtask` from the given parent `task`. It's the most efficient way to schedule a task. The task is executed until the first blocking operation, at which point it will yield control and `#async` will return. The result of this method is the task itself.
|
121
|
-
|
122
|
-
### Waiting for Results
|
123
|
-
|
124
|
-
Like promises, `Async::Task` produces results. In order to wait for these results, you must invoke `Async::Task#wait`:
|
125
|
-
|
126
|
-
```ruby
|
127
|
-
require 'async'
|
128
|
-
|
129
|
-
task = Async do
|
130
|
-
rand
|
131
|
-
end
|
132
|
-
|
133
|
-
puts task.wait
|
134
|
-
```
|
135
|
-
|
136
|
-
### Stopping Tasks
|
137
|
-
|
138
|
-
Use `Async::Task#stop` to stop tasks. This function raises `Async::Stop` on the target task and all descendent tasks.
|
139
|
-
|
140
|
-
```ruby
|
141
|
-
require 'async'
|
142
|
-
|
143
|
-
Async do
|
144
|
-
sleepy = Async do |task|
|
145
|
-
task.sleep 1000
|
146
|
-
end
|
147
|
-
|
148
|
-
sleepy.stop
|
149
|
-
end
|
150
|
-
```
|
151
|
-
|
152
|
-
When you design a server, you should return the task back to the caller. They can use this task to stop the server if needed, independently of any other unrelated tasks within the reactor, and it will correctly clean up all related tasks.
|
153
|
-
|
154
|
-
### Reactors
|
155
|
-
|
156
|
-
`Async::Reactor` is the top level IO reactor, and runs multiple tasks asynchronously. The reactor itself is not thread-safe, so you'd typically have [one reactor per thread or process](https://github.com/socketry/async-container).
|
157
|
-
|
158
|
-
#### Hierarchy
|
159
|
-
|
160
|
-
`Async::Reactor` and `Async::Task` form nodes in a tree. Reactors and tasks can spawn children tasks. When you invoke `Async::Reactor#async`, the parent task is determined by calling `Async::Task.current?` which uses fiber local storage. A slightly more efficient method is to use `Async::Task#async`, which uses `self` as the parent task.
|
161
|
-
|
162
|
-
|
163
|
-
```ruby
|
164
|
-
require 'async'
|
165
|
-
|
166
|
-
def sleepy(duration, task: Async::Task.current)
|
167
|
-
task.async do |subtask|
|
168
|
-
subtask.annotate "I'm going to sleep #{duration}s..."
|
169
|
-
subtask.sleep duration
|
170
|
-
puts "I'm done sleeping!"
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def nested_sleepy(task: Async::Task.current)
|
175
|
-
task.async do |subtask|
|
176
|
-
subtask.annotate "Invoking sleepy 5 times..."
|
177
|
-
5.times do |index|
|
178
|
-
sleepy(index, task: subtask)
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
Async do |task|
|
184
|
-
task.annotate "Invoking nested_sleepy..."
|
185
|
-
subtask = nested_sleepy
|
186
|
-
|
187
|
-
# Print out all running tasks in a tree:
|
188
|
-
task.print_hierarchy($stderr)
|
189
|
-
|
190
|
-
# Kill the subtask
|
191
|
-
subtask.stop
|
192
|
-
end
|
193
|
-
```
|
194
|
-
|
195
|
-
#### Embedding Reactors
|
196
|
-
|
197
|
-
`Async::Reactor#run` will run until the reactor runs out of work to do. To run a single iteration of the reactor, use `Async::Reactor#run_once`
|
198
|
-
|
199
|
-
```ruby
|
200
|
-
require 'async'
|
201
|
-
|
202
|
-
Async.logger.debug!
|
203
|
-
reactor = Async::Reactor.new
|
204
|
-
|
205
|
-
# Run the reactor for 1 second:
|
206
|
-
reactor.async do |task|
|
207
|
-
task.sleep 1
|
208
|
-
puts "Finished!"
|
209
|
-
end
|
210
|
-
|
211
|
-
while reactor.run_once
|
212
|
-
# Round and round we go!
|
213
|
-
end
|
214
|
-
```
|
215
|
-
|
216
|
-
You can use this approach to embed the reactor in another event loop.
|
217
|
-
|
218
|
-
#### Stopping Reactors
|
219
|
-
|
220
|
-
`Async::Reactor#stop` will stop the current reactor and all children tasks.
|
221
|
-
|
222
|
-
#### Interrupting Reactors
|
223
|
-
|
224
|
-
`Async::Reactor#interrupt` can be called safely from a different thread (or signal handler) and will cause the reactor to invoke `#stop`.
|
225
|
-
|
226
|
-
### Resource Management
|
227
|
-
|
228
|
-
In order to ensure your resources are cleaned up correctly, make sure you wrap resources appropriately, e.g.:
|
229
|
-
|
230
|
-
```ruby
|
231
|
-
Async::Reactor.run do
|
232
|
-
socket = connect(remote_address) # May raise Async::Stop
|
233
|
-
|
234
|
-
begin
|
235
|
-
socket.write(...) # May raise Async::Stop
|
236
|
-
socket.read(...) # May raise Async::Stop
|
237
|
-
ensure
|
238
|
-
socket.close
|
239
|
-
end
|
240
|
-
end
|
241
|
-
```
|
242
|
-
|
243
|
-
As tasks run synchronously until they yield back to the reactor, you can guarantee this model works correctly. While in theory `IO#autoclose` allows you to automatically close file descriptors when they go out of scope via the GC, it may produce unpredictable behavour (exhaustion of file descriptors, flushing data at odd times), so it's not recommended.
|
244
|
-
|
245
|
-
### Exception Handling
|
246
|
-
|
247
|
-
`Async::Task` captures and logs exceptions. All unhandled exceptions will cause the enclosing task to enter the `:failed` state. Non-`StandardError` exceptions are re-raised immediately and will generally cause the reactor to fail. This ensures that exceptions will always be visible and cause the program to fail appropriately.
|
248
|
-
|
249
|
-
```ruby
|
250
|
-
require 'async'
|
251
|
-
|
252
|
-
task = Async do
|
253
|
-
# Exception will be logged and task will be failed.
|
254
|
-
raise "Boom"
|
255
|
-
end
|
256
|
-
|
257
|
-
puts task.status # failed
|
258
|
-
puts task.result # raises RuntimeError: Boom
|
259
|
-
```
|
260
|
-
|
261
|
-
#### Propagating Exceptions
|
262
|
-
|
263
|
-
If a task has finished due to an exception, calling `Task#wait` will re-raise the exception.
|
264
|
-
|
265
|
-
```ruby
|
266
|
-
require 'async'
|
267
|
-
|
268
|
-
Async do
|
269
|
-
task = Async do
|
270
|
-
raise "Boom"
|
271
|
-
end
|
272
|
-
|
273
|
-
begin
|
274
|
-
task.wait # Re-raises above exception.
|
275
|
-
rescue
|
276
|
-
puts "It went #{$!}!"
|
277
|
-
end
|
278
|
-
end
|
279
|
-
```
|
280
|
-
|
281
|
-
#### Timeouts
|
282
|
-
|
283
|
-
You can wrap asynchronous operations in a timeout. This ensures that malicious services don't cause your code to block indefinitely.
|
284
|
-
|
285
|
-
```ruby
|
286
|
-
require 'async'
|
287
|
-
|
288
|
-
Async do |task|
|
289
|
-
task.with_timeout(1) do
|
290
|
-
task.sleep 100
|
291
|
-
rescue Async::TimeoutError
|
292
|
-
puts "I timed out!"
|
293
|
-
end
|
294
|
-
end
|
295
|
-
```
|
296
|
-
|
297
|
-
### Reoccurring Timers
|
298
|
-
|
299
|
-
Sometimes you need to do some periodic work in a loop.
|
300
|
-
|
301
|
-
```ruby
|
302
|
-
require 'async'
|
303
|
-
|
304
|
-
Async do |task|
|
305
|
-
while true
|
306
|
-
puts Time.now
|
307
|
-
task.sleep 1
|
308
|
-
end
|
309
|
-
end
|
310
|
-
```
|
311
|
-
|
312
|
-
## Caveats
|
313
|
-
|
314
|
-
### Enumerators
|
315
|
-
|
316
|
-
Due to limitations within Ruby and the nature of this library, it is not possible to use `to_enum` on methods which invoke asynchronous behavior. We hope to [fix this issue in the future](https://github.com/socketry/async/issues/23).
|
317
|
-
|
318
|
-
### Blocking Methods in Standard Library
|
319
|
-
|
320
|
-
Blocking Ruby methods such as `pop` in the `Queue` class require access to their own threads and will not yield control back to the reactor which can result in a deadlock. As a substitute for the standard library `Queue`, the `Async::Queue` class can be used.
|
321
|
-
|
322
|
-
## Conventions
|
323
|
-
|
324
|
-
### Nesting Tasks
|
325
|
-
|
326
|
-
`Async::Barrier` and `Async::Semaphore` are designed to be compatible with each other, and with other tasks that nest `#async` invocations. There are other similar situations where you may want to pass in a parent task, e.g. `Async::IO::Endpoint#bind`.
|
327
|
-
|
328
|
-
```ruby
|
329
|
-
barrier = Async::Barrier.new
|
330
|
-
semaphore = Async::Semaphore.new(2)
|
331
|
-
|
332
|
-
semaphore.async(parent: barrier) do
|
333
|
-
# ...
|
334
|
-
end
|
335
|
-
```
|
336
|
-
|
337
|
-
A `parent:` in this context is anything that responds to `#async` in the same way that `Async::Task` responds to `#async`. In situations where you strictly depend on the interface of `Async::Task`, use the `task: Task.current` pattern.
|
338
|
-
|
339
|
-
## Contributing
|
340
|
-
|
341
|
-
1. Fork it
|
342
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
343
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
344
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
345
|
-
5. Create new Pull Request
|
346
|
-
|
347
|
-
## See Also
|
348
|
-
|
349
|
-
- [async-io](https://github.com/socketry/async-io) — Asynchronous networking and sockets.
|
350
|
-
- [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client/server.
|
351
|
-
- [async-process](https://github.com/socketry/async-process) — Asynchronous process spawning/waiting.
|
352
|
-
- [async-websocket](https://github.com/socketry/async-websocket) — Asynchronous client and server websockets.
|
353
|
-
- [async-dns](https://github.com/socketry/async-dns) — Asynchronous DNS resolver and server.
|
354
|
-
- [async-rspec](https://github.com/socketry/async-rspec) — Shared contexts for running async specs.
|
355
|
-
|
356
|
-
### Projects Using Async
|
357
|
-
|
358
|
-
- [ciri](https://github.com/ciri-ethereum/ciri) — An Ethereum implementation written in Ruby.
|
359
|
-
- [falcon](https://github.com/socketry/falcon) — A rack compatible server built on top of `async-http`.
|
360
|
-
- [rubydns](https://github.com/ioquatix/rubydns) — A easy to use Ruby DNS server.
|
361
|
-
- [slack-ruby-bot](https://github.com/slack-ruby/slack-ruby-bot) — A client for making slack bots.
|
362
|
-
|
363
|
-
## License
|
364
|
-
|
365
|
-
Released under the MIT license.
|
366
|
-
|
367
|
-
Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
368
|
-
|
369
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
370
|
-
of this software and associated documentation files (the "Software"), to deal
|
371
|
-
in the Software without restriction, including without limitation the rights
|
372
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
373
|
-
copies of the Software, and to permit persons to whom the Software is
|
374
|
-
furnished to do so, subject to the following conditions:
|
375
|
-
|
376
|
-
The above copyright notice and this permission notice shall be included in
|
377
|
-
all copies or substantial portions of the Software.
|
378
|
-
|
379
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
380
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
381
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
382
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
383
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
384
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
385
|
-
THE SOFTWARE.
|