async-io 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +18 -0
- data/Gemfile +20 -0
- data/README.md +111 -0
- data/Rakefile +6 -0
- data/async-io.gemspec +25 -0
- data/lib/async/io.rb +34 -0
- data/lib/async/io/address.rb +144 -0
- data/lib/async/io/generic.rb +127 -0
- data/lib/async/io/socket.rb +159 -0
- data/lib/async/io/tcp_socket.rb +37 -0
- data/lib/async/io/udp_socket.rb +36 -0
- data/lib/async/io/unix_socket.rb +52 -0
- data/lib/async/io/version.rb +25 -0
- data/lib/async/io/wrap/tcp.rb +62 -0
- data/spec/async/io/address_spec.rb +45 -0
- data/spec/async/io/echo_spec.rb +72 -0
- data/spec/async/io/generic_spec.rb +47 -0
- data/spec/async/io/tcp_socket_spec.rb +115 -0
- data/spec/async/io/udp_socket_spec.rb +73 -0
- data/spec/async/io/unix_socket_spec.rb +55 -0
- data/spec/async/io/wrap/tcp_spec.rb +40 -0
- data/spec/spec_helper.rb +32 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3b884696374f63d12ec25bbeddbb32bdb6fc9dda
|
4
|
+
data.tar.gz: 43be6b24df7e4e244afaac328fdea0f95ecf612b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5d4a4c420cff06424564949249ee0c5c442f02c581eb94268902dd98f39d7187c1af4525ba0483ec9c438a9395182141838dab42bb7ca498d5d994f014ee3001
|
7
|
+
data.tar.gz: 1a75fce2c71dc1ad463ece49508068e9aff94529578b8c468b65fd679fc7857d15a2cc3c3f6b58d2dbd61f2aec06ad8940307daea6095965aeb5c008cfaf7a2b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in async-io.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem 'pry'
|
8
|
+
gem 'guard-rspec'
|
9
|
+
gem 'guard-yard'
|
10
|
+
|
11
|
+
gem 'yard'
|
12
|
+
end
|
13
|
+
|
14
|
+
group :test do
|
15
|
+
gem 'benchmark-ips'
|
16
|
+
gem 'ruby-prof', platforms: :mri
|
17
|
+
|
18
|
+
gem 'simplecov'
|
19
|
+
gem 'coveralls', require: false
|
20
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Async::IO
|
2
|
+
|
3
|
+
Async::IO provides builds on [async] and provides asynchronous wrappers for `IO`, `Socket`, and related classes.
|
4
|
+
|
5
|
+
[async]: https://github.com/socketry/async
|
6
|
+
|
7
|
+
[![Build Status](https://secure.travis-ci.org/socketry/async-io.svg)](http://travis-ci.org/socketry/async-io)
|
8
|
+
[![Code Climate](https://codeclimate.com/github/socketry/async-io.svg)](https://codeclimate.com/github/socketry/async-io)
|
9
|
+
[![Coverage Status](https://coveralls.io/repos/socketry/async-io/badge.svg)](https://coveralls.io/r/socketry/async-io)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'async-io'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install async-socket
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Basic echo server (from `spec/async/io/echo_spec.rb`):
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'async/io'
|
33
|
+
|
34
|
+
def echo_server(server_address)
|
35
|
+
Async::Reactor.run do |task|
|
36
|
+
# This is a synchronous block within the current task:
|
37
|
+
Async::IO::Socket.accept(server_address) do |client|
|
38
|
+
# This is an asynchronous block within the current reactor:
|
39
|
+
data = client.read(512)
|
40
|
+
|
41
|
+
# This produces out-of-order responses.
|
42
|
+
task.sleep(rand * 0.01)
|
43
|
+
|
44
|
+
client.write(data.reverse)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def echo_client(server_address, data)
|
50
|
+
Async::Reactor.run do |task|
|
51
|
+
Async::IO::Socket.connect(server_address) do |peer|
|
52
|
+
result = peer.write(data)
|
53
|
+
|
54
|
+
message = peer.read(512)
|
55
|
+
|
56
|
+
puts "Sent #{data}, got response: #{message}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
Async::Reactor.run do
|
62
|
+
server_address = Async::IO::Address.tcp('0.0.0.0', 9000)
|
63
|
+
|
64
|
+
server = echo_server(server_address)
|
65
|
+
|
66
|
+
5.times.collect do |i|
|
67
|
+
echo_client(server_address, "Hello World #{i}")
|
68
|
+
end.each(&:wait)
|
69
|
+
|
70
|
+
server.stop
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
## Contributing
|
75
|
+
|
76
|
+
1. Fork it
|
77
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
78
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
79
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
80
|
+
5. Create new Pull Request
|
81
|
+
|
82
|
+
## See Also
|
83
|
+
|
84
|
+
- [async](https://github.com/socketry/async) — Asynchronous event-driven reactor.
|
85
|
+
- [async-dns](https://github.com/socketry/async-dns) — Asynchronous DNS resolver and server.
|
86
|
+
- [async-rspec](https://github.com/socketry/async-rspec) — Shared contexts for running async specs.
|
87
|
+
- [rubydns](https://github.com/ioquatix/rubydns) — A easy to use Ruby DNS server.
|
88
|
+
|
89
|
+
## License
|
90
|
+
|
91
|
+
Released under the MIT license.
|
92
|
+
|
93
|
+
Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
94
|
+
|
95
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
96
|
+
of this software and associated documentation files (the "Software"), to deal
|
97
|
+
in the Software without restriction, including without limitation the rights
|
98
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
99
|
+
copies of the Software, and to permit persons to whom the Software is
|
100
|
+
furnished to do so, subject to the following conditions:
|
101
|
+
|
102
|
+
The above copyright notice and this permission notice shall be included in
|
103
|
+
all copies or substantial portions of the Software.
|
104
|
+
|
105
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
106
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
107
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
108
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
109
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
110
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
111
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
data/async-io.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require_relative 'lib/async/io/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "async-io"
|
6
|
+
spec.version = Async::IO::VERSION
|
7
|
+
spec.authors = ["Samuel Williams"]
|
8
|
+
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
9
|
+
|
10
|
+
spec.summary = "Provides support for asynchonous TCP, UDP, UNIX and SSL sockets."
|
11
|
+
spec.homepage = "https://github.com/socketry/async-io"
|
12
|
+
|
13
|
+
spec.files = `git ls-files`.split($/)
|
14
|
+
spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
spec.has_rdoc = "yard"
|
18
|
+
|
19
|
+
spec.add_dependency "async", "~> 0.14"
|
20
|
+
spec.add_development_dependency "async-rspec", "~> 1.0"
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
end
|
data/lib/async/io.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'async'
|
22
|
+
|
23
|
+
require_relative "io/generic"
|
24
|
+
require_relative "io/socket"
|
25
|
+
require_relative "io/address"
|
26
|
+
require_relative "io/version"
|
27
|
+
|
28
|
+
module Async
|
29
|
+
module IO
|
30
|
+
def self.try_convert(io)
|
31
|
+
Generic::WRAPPERS[io.class].wrap(io)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'socket'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
module IO
|
25
|
+
class Address < Struct.new(:specification, :options)
|
26
|
+
include ::Socket::Constants
|
27
|
+
include Comparable
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def tcp(*args, **options)
|
31
|
+
self.new([:tcp, *args], **options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def udp(*args, **options)
|
35
|
+
self.new([:udp, *args], **options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def unix(*args, **options)
|
39
|
+
self.new([:unix, *args], **options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def each(specifications, &block)
|
43
|
+
specifications.each do |specification|
|
44
|
+
if specification.is_a? self
|
45
|
+
yield self
|
46
|
+
else
|
47
|
+
# Perhaps detect options here?
|
48
|
+
yield self.new(specification)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(specification, **options)
|
55
|
+
super(specification, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def == other
|
59
|
+
self.to_sockaddr == other.to_sockaddr
|
60
|
+
end
|
61
|
+
|
62
|
+
def <=> other
|
63
|
+
self.to_sockaddr <=> other.to_sockaddr
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_sockaddr
|
67
|
+
addrinfo.to_sockaddr
|
68
|
+
end
|
69
|
+
|
70
|
+
# This is how addresses are internally converted, e.g. within `Socket#sendto`.
|
71
|
+
alias to_str to_sockaddr
|
72
|
+
|
73
|
+
def socktype
|
74
|
+
addrinfo.socktype
|
75
|
+
end
|
76
|
+
|
77
|
+
# Preferred accessor for socket type.
|
78
|
+
alias type socktype
|
79
|
+
|
80
|
+
def afamily
|
81
|
+
addrinfo.afamily
|
82
|
+
end
|
83
|
+
|
84
|
+
# Preferred accessor for address family.
|
85
|
+
alias family afamily
|
86
|
+
|
87
|
+
# def connect? accept? DatagramHandler StreamHandler
|
88
|
+
|
89
|
+
def bind(&block)
|
90
|
+
case specification
|
91
|
+
when Addrinfo
|
92
|
+
Socket.bind(specification, **options, &block)
|
93
|
+
when Array
|
94
|
+
Socket.bind(Addrinfo.send(*specification), **options, &block)
|
95
|
+
when ::BasicSocket
|
96
|
+
yield Socket.new(specification)
|
97
|
+
when BasicSocket
|
98
|
+
yield specification
|
99
|
+
else
|
100
|
+
raise ArgumentError, "Not sure how to bind to #{specification}!"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def accept(&block)
|
105
|
+
backlog = self.options.fetch(:backlog, SOMAXCONN)
|
106
|
+
|
107
|
+
bind do |socket|
|
108
|
+
socket.listen(backlog)
|
109
|
+
socket.accept_each(&block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def connect(&block)
|
114
|
+
case specification
|
115
|
+
when Addrinfo, Array
|
116
|
+
Socket.connect(self, &block)
|
117
|
+
when ::BasicSocket
|
118
|
+
yield Async::IO.try_convert(specification)
|
119
|
+
when BasicSocket
|
120
|
+
yield specification
|
121
|
+
else
|
122
|
+
raise ArgumentError, "Not sure how to bind to #{specification}!"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def addrinfo
|
129
|
+
@addrinfo ||= case specification
|
130
|
+
when Addrinfo
|
131
|
+
specification
|
132
|
+
when Array
|
133
|
+
Addrinfo.send(*specification)
|
134
|
+
when ::BasicSocket
|
135
|
+
specification.local_address
|
136
|
+
when BasicSocket
|
137
|
+
specification.local_address
|
138
|
+
else
|
139
|
+
raise ArgumentError, "Not sure how to convert #{specification} into address!"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'async/wrapper'
|
22
|
+
require 'forwardable'
|
23
|
+
|
24
|
+
module Async
|
25
|
+
module IO
|
26
|
+
# Represents an asynchronous IO within a reactor.
|
27
|
+
class Generic < Wrapper
|
28
|
+
extend Forwardable
|
29
|
+
|
30
|
+
WRAPPERS = {}
|
31
|
+
|
32
|
+
class << self
|
33
|
+
# @!macro [attach] wrap_blocking_method
|
34
|
+
# @method $1
|
35
|
+
# Invokes `$2` on the underlying {io}. If the operation would block, the current task is paused until the operation can succeed, at which point it's resumed and the operation is completed.
|
36
|
+
def wrap_blocking_method(new_name, method_name, invert: true, &block)
|
37
|
+
define_method(new_name) do |*args|
|
38
|
+
async_send(method_name, *args)
|
39
|
+
end
|
40
|
+
|
41
|
+
if invert
|
42
|
+
# We define the original _nonblock method to call the async variant. We ignore options.
|
43
|
+
# define_method(method_name) do |*args, **options|
|
44
|
+
# self.__send__(new_name, *args)
|
45
|
+
# end
|
46
|
+
def_delegators :@io, method_name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr :wrapped_klass
|
51
|
+
|
52
|
+
def wraps(klass, *additional_methods)
|
53
|
+
@wrapped_klass = klass
|
54
|
+
WRAPPERS[klass] = self
|
55
|
+
|
56
|
+
# These are methods implemented by the wrapped class, that we aren't overriding, that may be of interest:
|
57
|
+
# fallback_methods = klass.instance_methods(false) - instance_methods
|
58
|
+
# puts "Forwarding #{klass} methods #{fallback_methods} to @io"
|
59
|
+
|
60
|
+
def_delegators :@io, *additional_methods
|
61
|
+
end
|
62
|
+
|
63
|
+
# Instantiate a wrapped instance of the class, and optionally yield it to a given block, closing it afterwards.
|
64
|
+
def wrap(*args)
|
65
|
+
wrapper = self.new(@wrapped_klass.new(*args))
|
66
|
+
|
67
|
+
if block_given?
|
68
|
+
begin
|
69
|
+
yield wrapper
|
70
|
+
ensure
|
71
|
+
wrapper.close
|
72
|
+
end
|
73
|
+
else
|
74
|
+
return wrapper
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
wraps ::IO, :external_encoding, :internal_encoding, :autoclose?, :autoclose=, :pid, :stat, :binmode, :flush, :set_encoding, :to_io, :to_i, :reopen, :fileno, :fsync, :fdatasync, :sync, :sync=, :tell, :seek, :rewind, :pos, :pos=, :eof, :eof?, :close_on_exec?, :close_on_exec=, :closed?, :close_read, :close_write, :isatty, :tty?, :binmode?, :sysseek, :advise, :ioctl, :fcntl
|
80
|
+
|
81
|
+
# @example
|
82
|
+
# data = io.read(512)
|
83
|
+
wrap_blocking_method :read, :read_nonblock
|
84
|
+
|
85
|
+
# @example
|
86
|
+
# io.write("Hello World")
|
87
|
+
wrap_blocking_method :write, :write_nonblock
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
if RUBY_VERSION >= "2.3"
|
92
|
+
def async_send(*args)
|
93
|
+
async do
|
94
|
+
@io.__send__(*args, exception: false)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else
|
98
|
+
def async_send(*args)
|
99
|
+
async do
|
100
|
+
@io.__send__(*args)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def async
|
106
|
+
while true
|
107
|
+
begin
|
108
|
+
result = yield
|
109
|
+
|
110
|
+
case result
|
111
|
+
when :wait_readable
|
112
|
+
wait_readable
|
113
|
+
when :wait_writable
|
114
|
+
wait_writable
|
115
|
+
else
|
116
|
+
return result
|
117
|
+
end
|
118
|
+
rescue ::IO::WaitReadable
|
119
|
+
wait_readable
|
120
|
+
rescue ::IO::WaitWritable
|
121
|
+
wait_writable
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|