async-io 0.1.0

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