async 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'async'
4
-
5
- reactor = Async::Reactor.new
6
-
7
- puts "Creating server"
8
- server = TCPServer.new("localhost", 6777)
9
-
10
- REPEATS = 10
11
-
12
- timer = reactor.after(1) do
13
- puts "Reactor timed out!"
14
- reactor.stop
15
- end
16
-
17
- reactor.async(server) do |server, task|
18
- REPEATS.times do |i|
19
- puts "Accepting peer on server #{server}"
20
- task.with(server.accept) do |peer|
21
- puts "Sending data to peer"
22
- peer << "data #{i}"
23
- peer.shutdown
24
- end
25
- end
26
-
27
- puts "Server finished, canceling timer"
28
- timer.cancel
29
- end
30
-
31
- REPEATS.times do |i|
32
- # This aspect of the connection is synchronous.
33
- puts "Creating client #{i}"
34
- client = TCPSocket.new("localhost", 6777)
35
-
36
- reactor.async(client) do |client|
37
- puts "Reading data on client #{i}"
38
- puts client.read(1024)
39
- end
40
- end
41
-
42
- reactor.run
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'async'
4
- require 'async/tcp_socket'
5
-
6
- def echo_server
7
- Async::Reactor.run do |task|
8
- # This is a synchronous block within the current task:
9
- task.with(TCPServer.new('localhost', 9000)) do |server|
10
-
11
- # This is an asynchronous block within the current reactor:
12
- task.reactor.with(server.accept) do |client|
13
- data = client.read(512)
14
-
15
- task.sleep(rand)
16
-
17
- client.write(data)
18
- end while true
19
- end
20
- end
21
- end
22
-
23
- def echo_client(data)
24
- Async::Reactor.run do |task|
25
- Async::TCPServer.connect('localhost', 9000) do |socket|
26
- socket.write(data)
27
- puts "echo_client: #{socket.read(512)}"
28
- end
29
- end
30
- end
31
-
32
- Async::Reactor.run do
33
- server = echo_server
34
-
35
- 5.times.collect do |i|
36
- echo_client("Hello World #{i}")
37
- end.each(&:wait)
38
-
39
- server.stop
40
- end
@@ -1,109 +0,0 @@
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 'wrapper'
22
-
23
- require 'forwardable'
24
-
25
- module Async
26
- # Represents an asynchronous IO within a reactor.
27
- class IO < Wrapper
28
- extend Forwardable
29
-
30
- WRAPPERS = {}
31
-
32
- # Return the wrapper for a given native IO instance.
33
- def self.[] instance
34
- WRAPPERS[instance.class]
35
- end
36
-
37
- class << self
38
- # @!macro [attach] wrap_blocking_method
39
- # @method $1
40
- # 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.
41
- def wrap_blocking_method(new_name, method_name, &block)
42
- if block_given?
43
- define_method(new_name, &block)
44
- else
45
- define_method(new_name) do |*args|
46
- async_send(method_name, *args)
47
- end
48
- end
49
- end
50
-
51
- def wraps(klass, *additional_methods)
52
- WRAPPERS[klass] = self
53
-
54
- # klass.instance_methods(false).grep(/(.*)_nonblock/) do |method_name|
55
- # wrap_blocking_method($1, method_name)
56
- # end
57
-
58
- def_delegators :@io, *(additional_methods - instance_methods(false))
59
- end
60
- end
61
-
62
- wraps ::IO
63
-
64
- # @example
65
- # data = io.read(512)
66
- wrap_blocking_method :read, :read_nonblock
67
-
68
- # @example
69
- # io.write("Hello World")
70
- wrap_blocking_method :write, :write_nonblock
71
-
72
- protected
73
-
74
- if RUBY_VERSION >= "2.3"
75
- def async_send(*args)
76
- async do
77
- @io.__send__(*args, exception: false)
78
- end
79
- end
80
- else
81
- def async_send(*args)
82
- async do
83
- @io.__send__(*args)
84
- end
85
- end
86
- end
87
-
88
- def async
89
- while true
90
- begin
91
- result = yield
92
-
93
- case result
94
- when :wait_readable
95
- wait_readable
96
- when :wait_writable
97
- wait_writable
98
- else
99
- return result
100
- end
101
- rescue ::IO::WaitReadable
102
- wait_readable
103
- rescue ::IO::WaitWritable
104
- wait_writable
105
- end
106
- end
107
- end
108
- end
109
- end
@@ -1,109 +0,0 @@
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 'io'
22
-
23
- require 'socket'
24
-
25
- module Async
26
- class BasicSocket < IO
27
- wraps ::BasicSocket
28
-
29
- wrap_blocking_method :recv, :recv_nonblock
30
- wrap_blocking_method :recvmsg, :recvmsg_nonblock
31
-
32
- wrap_blocking_method :recvfrom, :recvfrom_nonblock
33
-
34
- wrap_blocking_method :send, :sendmsg_nonblock
35
- wrap_blocking_method :sendmsg, :sendmsg_nonblock
36
- end
37
-
38
- class Socket < BasicSocket
39
- wraps ::Socket
40
-
41
- wrap_blocking_method :accept, :accept_nonblock
42
-
43
- wrap_blocking_method :connect, :connect_nonblock do |*args|
44
- begin
45
- async_send(:connect_nonblock, *args)
46
- rescue Errno::EISCONN
47
- # We are now connected.
48
- end
49
- end
50
-
51
- # Establish a connection to a given `remote_address`.
52
- # @example
53
- # socket = Async::Socket.connect(Addrinfo.tcp("8.8.8.8", 53))
54
- # @param remote_address [Addrinfo] The remote address to connect to.
55
- # @param local_address [Addrinfo] The local address to bind to before connecting.
56
- # @option protcol [Integer] The socket protocol to use.
57
- def self.connect(remote_address, local_address = nil, protocol: 0, task: Task.current)
58
- socket = ::Socket.new(remote_address.afamily, remote_address.socktype, protocol)
59
-
60
- if local_address
61
- socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
62
- socket.bind(local_address) if local_address
63
- end
64
-
65
- if block_given?
66
- task.with(socket) do |wrapper|
67
- wrapper.connect(remote_address.to_sockaddr)
68
-
69
- yield wrapper
70
- end
71
- else
72
- task.bind(socket).connect(remote_address.to_sockaddr)
73
-
74
- return socket
75
- end
76
- end
77
-
78
- # Bind to a local address.
79
- # @example
80
- # socket = Async::Socket.bind(Addrinfo.tcp("0.0.0.0", 9090))
81
- # @param local_address [Addrinfo] The local address to bind to.
82
- # @option protcol [Integer] The socket protocol to use.
83
- def self.bind(local_address, backlog: nil, protocol: 0, task: Task.current, &block)
84
- socket = ::Socket.new(local_address.afamily, local_address.socktype, protocol)
85
-
86
- socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
87
- socket.bind(local_address)
88
-
89
- socket.listen(backlog) if backlog
90
-
91
- if block_given?
92
- task.with(socket, &block)
93
- else
94
- return socket
95
- end
96
- end
97
-
98
- # Bind to a local address and accept connections in a loop.
99
- def self.accept(*args, task: Task.current, &block)
100
- bind(*args, task: task) do |wrapper|
101
- task.with(*wrapper.accept, &block) while true
102
- end
103
- end
104
- end
105
-
106
- class IPSocket < BasicSocket
107
- wraps ::IPSocket
108
- end
109
- end
@@ -1,35 +0,0 @@
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
- # Asynchronous TCP socket wrapper.
25
- class TCPSocket < IPSocket
26
- wraps ::TCPSocket
27
- end
28
-
29
- # Asynchronous TCP server wrappper.
30
- class TCPServer < TCPSocket
31
- wraps ::TCPServer
32
-
33
- wrap_blocking_method :accept, :accept_nonblock
34
- end
35
- end
@@ -1,33 +0,0 @@
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
- class UNIXServer < BasicSocket
25
- wraps ::UNIXServer
26
-
27
- wrap_blocking_method :accept, :accept_nonblock
28
- end
29
-
30
- class UNIXSocket < BasicSocket
31
- wraps ::UNIXSocket
32
- end
33
- end
@@ -1,106 +0,0 @@
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
- RSpec.describe Async::Reactor do
22
- include_context "closes all io"
23
-
24
- # Shared port for localhost network tests.
25
- let(:server_address) {Addrinfo.tcp("localhost", 6779)}
26
- let(:data) {"The quick brown fox jumped over the lazy dog."}
27
-
28
- around(:each) do |example|
29
- # Accept a single incoming connection and then finish.
30
- subject.async do |task|
31
- Async::Socket.bind(server_address, backlog: 128) do |server|
32
- task.with(*server.accept) do |peer, address|
33
- data = peer.read(512)
34
- peer.write(data)
35
- end
36
- end
37
- end
38
-
39
- result = example.run
40
-
41
- return result if result.is_a? Exception
42
-
43
- subject.run
44
- end
45
-
46
- describe 'basic tcp server' do
47
- it "should start server and send data" do
48
- subject.async do
49
- Async::Socket.connect(server_address) do |client|
50
- client.write(data)
51
- expect(client.read(512)).to be == data
52
- end
53
- end
54
- end
55
- end
56
-
57
- describe 'non-blocking tcp connect' do
58
- it "should start server and send data" do
59
- subject.async do |task|
60
- Async::Socket.connect(server_address) do |client|
61
- client.write(data)
62
- expect(client.read(512)).to be == data
63
- end
64
- end
65
- end
66
-
67
- it "can connect socket and read/write in a different task" do
68
- socket = nil
69
-
70
- subject.async do |task|
71
- socket = Async::Socket.connect(server_address)
72
-
73
- # Stop the reactor once the connection was made.
74
- subject.stop
75
- end
76
-
77
- subject.run
78
-
79
- expect(socket).to_not be_nil
80
-
81
- subject.async(socket) do |client|
82
- client.write(data)
83
- expect(client.read(512)).to be == data
84
- end
85
-
86
- subject.run
87
- end
88
-
89
- it "can't use a socket in nested tasks" do
90
- subject.async do |task|
91
- socket = Async::Socket.connect(server_address)
92
-
93
- # I'm not sure if this is the right behaviour or not. Without a significant amont of work, async sockets are tied to the task that creates them.
94
- expect do
95
- subject.async(socket) do |client|
96
- client.write(data)
97
- expect(client.read(512)).to be == data
98
- end
99
- end.to raise_error(ArgumentError, /already registered with selector/)
100
-
101
- # We need to explicitly close the socket, since we explicitly opened it.
102
- socket.close
103
- end
104
- end
105
- end
106
- end