async-io 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --warnings
3
+ --require spec_helper
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+ sudo: false
3
+ dist: trusty
4
+ cache: bundler
5
+ rvm:
6
+ - 2.0
7
+ - 2.1
8
+ - 2.2
9
+ - 2.3
10
+ - 2.4
11
+ - jruby-head
12
+ - ruby-head
13
+ - rbx-3
14
+ matrix:
15
+ allow_failures:
16
+ - rvm: ruby-head
17
+ - rvm: jruby-head
18
+ - rvm: rbx-3
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
@@ -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.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:test)
5
+
6
+ task :default => :test
@@ -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
@@ -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