polyphony 0.13 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitbook.yaml +5 -0
  3. data/.gitignore +55 -0
  4. data/.rubocop.yml +49 -0
  5. data/CHANGELOG.md +13 -2
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +31 -0
  8. data/LICENSE +21 -0
  9. data/README.md +35 -18
  10. data/Rakefile +20 -0
  11. data/TODO.md +49 -0
  12. data/docs/getting-started/getting-started.md +10 -0
  13. data/docs/getting-started/tutorial.md +2 -0
  14. data/docs/summary.md +9 -0
  15. data/examples/core/cancel.rb +10 -0
  16. data/examples/core/channel_echo.rb +43 -0
  17. data/examples/core/enumerator.rb +14 -0
  18. data/examples/core/fork.rb +22 -0
  19. data/examples/core/genserver.rb +74 -0
  20. data/examples/core/lock.rb +20 -0
  21. data/examples/core/move_on.rb +11 -0
  22. data/examples/core/move_on_twice.rb +17 -0
  23. data/examples/core/move_on_with_ensure.rb +17 -0
  24. data/examples/core/multiple_async.rb +17 -0
  25. data/examples/core/nested_async.rb +18 -0
  26. data/examples/core/nested_cancel.rb +41 -0
  27. data/examples/core/nested_multiple_async.rb +19 -0
  28. data/examples/core/next_tick.rb +13 -0
  29. data/examples/core/pulse.rb +13 -0
  30. data/examples/core/resource.rb +29 -0
  31. data/examples/core/resource_cancel.rb +34 -0
  32. data/examples/core/resource_delegate.rb +32 -0
  33. data/examples/core/sleep.rb +9 -0
  34. data/examples/core/sleep2.rb +13 -0
  35. data/examples/core/spawn.rb +15 -0
  36. data/examples/core/spawn_cancel.rb +19 -0
  37. data/examples/core/spawn_error.rb +28 -0
  38. data/examples/core/supervisor.rb +22 -0
  39. data/examples/core/supervisor_with_cancel_scope.rb +24 -0
  40. data/examples/core/supervisor_with_error.rb +23 -0
  41. data/examples/core/supervisor_with_manual_move_on.rb +25 -0
  42. data/examples/core/thread.rb +30 -0
  43. data/examples/core/thread_cancel.rb +30 -0
  44. data/examples/core/thread_pool.rb +60 -0
  45. data/examples/core/throttle.rb +17 -0
  46. data/examples/fs/read.rb +37 -0
  47. data/examples/interfaces/pg_client.rb +38 -0
  48. data/examples/interfaces/pg_pool.rb +37 -0
  49. data/examples/interfaces/pg_query.rb +32 -0
  50. data/examples/interfaces/redis_channels.rb +119 -0
  51. data/examples/interfaces/redis_client.rb +21 -0
  52. data/examples/interfaces/redis_pubsub.rb +26 -0
  53. data/examples/interfaces/redis_pubsub_perf.rb +65 -0
  54. data/examples/io/config.ru +3 -0
  55. data/examples/io/echo_client.rb +22 -0
  56. data/examples/io/echo_server.rb +14 -0
  57. data/examples/io/echo_server_with_timeout.rb +33 -0
  58. data/examples/io/echo_stdin.rb +15 -0
  59. data/examples/io/happy_eyeballs.rb +32 -0
  60. data/examples/io/http_client.rb +19 -0
  61. data/examples/io/http_server.js +24 -0
  62. data/examples/io/http_server.rb +16 -0
  63. data/examples/io/http_server_forked.rb +27 -0
  64. data/examples/io/http_server_throttled.rb +16 -0
  65. data/examples/io/http_ws_server.rb +42 -0
  66. data/examples/io/https_client.rb +17 -0
  67. data/examples/io/https_server.rb +23 -0
  68. data/examples/io/https_wss_server.rb +46 -0
  69. data/examples/io/rack_server.rb +19 -0
  70. data/examples/io/rack_server_https.rb +24 -0
  71. data/examples/io/rack_server_https_forked.rb +32 -0
  72. data/examples/io/websocket_server.rb +33 -0
  73. data/examples/io/ws_page.html +34 -0
  74. data/examples/io/wss_page.html +34 -0
  75. data/examples/performance/perf_multi_snooze.rb +21 -0
  76. data/examples/performance/perf_snooze.rb +30 -0
  77. data/examples/performance/thread-vs-fiber/polyphony_server.rb +63 -0
  78. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  79. data/examples/streams/lines.rb +27 -0
  80. data/examples/streams/stdio.rb +18 -0
  81. data/ext/ev/async.c +168 -0
  82. data/ext/ev/child.c +169 -0
  83. data/ext/ev/ev.h +32 -0
  84. data/ext/ev/ev_ext.c +20 -0
  85. data/ext/ev/ev_module.c +222 -0
  86. data/ext/ev/io.c +405 -0
  87. data/ext/ev/libev.h +9 -0
  88. data/ext/ev/signal.c +119 -0
  89. data/ext/ev/timer.c +197 -0
  90. data/ext/libev/Changes +513 -0
  91. data/ext/libev/LICENSE +37 -0
  92. data/ext/libev/README +58 -0
  93. data/ext/libev/README.embed +3 -0
  94. data/ext/libev/ev.c +5214 -0
  95. data/ext/libev/ev.h +849 -0
  96. data/ext/libev/ev_epoll.c +285 -0
  97. data/ext/libev/ev_kqueue.c +218 -0
  98. data/ext/libev/ev_poll.c +151 -0
  99. data/ext/libev/ev_port.c +189 -0
  100. data/ext/libev/ev_select.c +316 -0
  101. data/ext/libev/ev_vars.h +204 -0
  102. data/ext/libev/ev_win32.c +162 -0
  103. data/ext/libev/ev_wrap.h +200 -0
  104. data/ext/libev/test_libev_win32.c +123 -0
  105. data/lib/polyphony.rb +7 -2
  106. data/lib/polyphony/core.rb +1 -1
  107. data/lib/polyphony/core/{coroutine.rb → coprocess.rb} +10 -10
  108. data/lib/polyphony/core/exceptions.rb +5 -5
  109. data/lib/polyphony/core/supervisor.rb +16 -16
  110. data/lib/polyphony/core/thread.rb +1 -1
  111. data/lib/polyphony/extensions/io.rb +43 -42
  112. data/lib/polyphony/extensions/kernel.rb +10 -34
  113. data/lib/polyphony/extensions/postgres.rb +3 -2
  114. data/lib/polyphony/extensions/redis.rb +1 -1
  115. data/lib/polyphony/extensions/socket.rb +8 -4
  116. data/lib/polyphony/extensions/ssl.rb +0 -54
  117. data/lib/polyphony/http/agent.rb +4 -10
  118. data/lib/polyphony/http/http1.rb +25 -25
  119. data/lib/polyphony/http/http1_request.rb +38 -26
  120. data/lib/polyphony/http/http2.rb +4 -5
  121. data/lib/polyphony/http/http2_request.rb +12 -18
  122. data/lib/polyphony/http/rack.rb +1 -3
  123. data/lib/polyphony/http/server.rb +9 -9
  124. data/lib/polyphony/net.rb +2 -2
  125. data/lib/polyphony/resource_pool.rb +5 -1
  126. data/lib/polyphony/version.rb +1 -1
  127. data/lib/polyphony/websocket.rb +52 -0
  128. data/polyphony.gemspec +31 -0
  129. data/test/test_coprocess.rb +131 -0
  130. data/test/test_core.rb +274 -0
  131. data/test/test_ev.rb +117 -0
  132. data/test/test_io.rb +38 -0
  133. metadata +113 -7
  134. data/lib/polyphony/core/async.rb +0 -36
  135. data/lib/polyphony/net_old.rb +0 -299
@@ -0,0 +1,117 @@
1
+ require 'minitest/autorun'
2
+ require 'modulation'
3
+
4
+ module EVTest
5
+ Core = import('../lib/polyphony/core')
6
+
7
+ class RunTest < Minitest::Test
8
+ def setup
9
+ EV.break
10
+ EV.restart
11
+ end
12
+
13
+ def test_that_run_loop_returns_immediately_if_no_watchers
14
+ t0 = Time.now
15
+ suspend
16
+ t1 = Time.now
17
+ assert (t1 - t0) < 0.001
18
+ end
19
+ end
20
+
21
+ class TimerTest < MiniTest::Test
22
+ def setup
23
+ EV.break
24
+ EV.restart
25
+ end
26
+
27
+ def test_that_one_shot_timer_works
28
+ count = 0
29
+ t = EV::Timer.new(0.01, 0)
30
+ t.start { count += 1}
31
+ suspend
32
+ assert_equal(1, count)
33
+ end
34
+
35
+ def test_that_repeating_timer_works
36
+ count = 0
37
+ t = EV::Timer.new(0.001, 0.001)
38
+ t.start { count += 1; t.stop if count >= 3}
39
+ suspend
40
+ assert_equal(3, count)
41
+ end
42
+ end
43
+
44
+ class IOTest < MiniTest::Test
45
+ def setup
46
+ EV.break
47
+ EV.restart
48
+ end
49
+
50
+ def test_that_reading_works
51
+ i, o = IO.pipe
52
+ data = +''
53
+ w = EV::IO.new(i, :r)
54
+ w.start do
55
+ i.read_nonblock(8192, data)
56
+ w.stop unless data.empty?
57
+ end
58
+ EV::Timer.new(0, 0).start { o << 'hello' }
59
+ suspend
60
+ assert_equal('hello', data)
61
+ end
62
+ end
63
+
64
+ class SignalTest < MiniTest::Test
65
+ def setup
66
+ EV.break
67
+ EV.restart
68
+ end
69
+
70
+ def test_that_signal_watcher_receives_signal
71
+ sig = Signal.list['USR1']
72
+ count = 0
73
+ w = EV::Signal.new(sig) { count += 1; w.stop }
74
+ Thread.new { sync_sleep 0.001; Process.kill(:USR1, Process.pid) }
75
+ suspend
76
+ assert_equal(1, count)
77
+ end
78
+
79
+ def test_that_signal_watcher_receives_signal
80
+ count = 0
81
+ w = Core.trap(:usr1, true) { count += 1; w.stop }
82
+ assert_kind_of(EV::Signal, w)
83
+ Thread.new { sync_sleep 0.001; Process.kill(:USR1, Process.pid) }
84
+ suspend
85
+ assert_equal(1, count)
86
+ end
87
+ end
88
+
89
+ class AsyncTest < MiniTest::Test
90
+ def setup
91
+ EV.break
92
+ EV.restart
93
+ end
94
+
95
+ def test_that_async_watcher_receives_signal_across_threads
96
+ count = 0
97
+ a = EV::Async.new { count += 1; a.stop }
98
+ Thread.new { sync_sleep 0.001; a.signal! }
99
+ suspend
100
+ assert_equal(1, count)
101
+ end
102
+
103
+ def test_that_async_watcher_coalesces_signals
104
+ count = 0
105
+ a = EV::Async.new do
106
+ count += 1
107
+ EV::Timer.new(0.01, 0).start { a.stop }
108
+ end
109
+ Thread.new do
110
+ sync_sleep 0.001
111
+ 3.times { a.signal! }
112
+ end
113
+ suspend
114
+ assert_equal(1, count)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,38 @@
1
+ require 'minitest/autorun'
2
+ require 'modulation'
3
+
4
+ module IOTests
5
+ Core = import('../lib/polyphony/core')
6
+ import('../lib/polyphony/extensions/io')
7
+
8
+ class IOTest < MiniTest::Test
9
+ def setup
10
+ EV.rerun
11
+ end
12
+
13
+ def test_that_io_does_not_block
14
+ i, o = IO.pipe
15
+ count = 0
16
+ msg = nil
17
+ [
18
+ spawn {
19
+ o.write("hello")
20
+ o.close
21
+ },
22
+
23
+ spawn {
24
+ while count < 5
25
+ sleep 0.01
26
+ count += 1
27
+ end
28
+ },
29
+
30
+ spawn {
31
+ msg = i.read
32
+ }
33
+ ].each(&:await)
34
+ assert_equal(5, count)
35
+ assert_equal("hello", msg)
36
+ end
37
+ end
38
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.13'
4
+ version: '0.14'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-05 00:00:00.000000000 Z
11
+ date: 2019-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: modulation
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: '0.18'
19
+ version: '0.23'
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: '0.18'
26
+ version: '0.23'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: http_parser.rb
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -102,15 +102,116 @@ extensions:
102
102
  extra_rdoc_files:
103
103
  - README.md
104
104
  files:
105
+ - ".gitbook.yaml"
106
+ - ".gitignore"
107
+ - ".rubocop.yml"
108
+ - ".vscode/launch.json"
105
109
  - CHANGELOG.md
110
+ - Gemfile
111
+ - Gemfile.lock
112
+ - LICENSE
106
113
  - README.md
114
+ - Rakefile
115
+ - TODO.md
116
+ - docs/getting-started/getting-started.md
117
+ - docs/getting-started/tutorial.md
118
+ - docs/summary.md
119
+ - examples/core/cancel.rb
120
+ - examples/core/channel_echo.rb
121
+ - examples/core/enumerator.rb
122
+ - examples/core/fork.rb
123
+ - examples/core/genserver.rb
124
+ - examples/core/lock.rb
125
+ - examples/core/move_on.rb
126
+ - examples/core/move_on_twice.rb
127
+ - examples/core/move_on_with_ensure.rb
128
+ - examples/core/multiple_async.rb
129
+ - examples/core/nested_async.rb
130
+ - examples/core/nested_cancel.rb
131
+ - examples/core/nested_multiple_async.rb
132
+ - examples/core/next_tick.rb
133
+ - examples/core/pulse.rb
134
+ - examples/core/resource.rb
135
+ - examples/core/resource_cancel.rb
136
+ - examples/core/resource_delegate.rb
137
+ - examples/core/sleep.rb
138
+ - examples/core/sleep2.rb
139
+ - examples/core/spawn.rb
140
+ - examples/core/spawn_cancel.rb
141
+ - examples/core/spawn_error.rb
142
+ - examples/core/supervisor.rb
143
+ - examples/core/supervisor_with_cancel_scope.rb
144
+ - examples/core/supervisor_with_error.rb
145
+ - examples/core/supervisor_with_manual_move_on.rb
146
+ - examples/core/thread.rb
147
+ - examples/core/thread_cancel.rb
148
+ - examples/core/thread_pool.rb
149
+ - examples/core/throttle.rb
150
+ - examples/fs/read.rb
151
+ - examples/interfaces/pg_client.rb
152
+ - examples/interfaces/pg_pool.rb
153
+ - examples/interfaces/pg_query.rb
154
+ - examples/interfaces/redis_channels.rb
155
+ - examples/interfaces/redis_client.rb
156
+ - examples/interfaces/redis_pubsub.rb
157
+ - examples/interfaces/redis_pubsub_perf.rb
158
+ - examples/io/config.ru
159
+ - examples/io/echo_client.rb
160
+ - examples/io/echo_server.rb
161
+ - examples/io/echo_server_with_timeout.rb
162
+ - examples/io/echo_stdin.rb
163
+ - examples/io/happy_eyeballs.rb
164
+ - examples/io/http_client.rb
165
+ - examples/io/http_server.js
166
+ - examples/io/http_server.rb
167
+ - examples/io/http_server_forked.rb
168
+ - examples/io/http_server_throttled.rb
169
+ - examples/io/http_ws_server.rb
170
+ - examples/io/https_client.rb
171
+ - examples/io/https_server.rb
172
+ - examples/io/https_wss_server.rb
173
+ - examples/io/rack_server.rb
174
+ - examples/io/rack_server_https.rb
175
+ - examples/io/rack_server_https_forked.rb
176
+ - examples/io/websocket_server.rb
177
+ - examples/io/ws_page.html
178
+ - examples/io/wss_page.html
179
+ - examples/performance/perf_multi_snooze.rb
180
+ - examples/performance/perf_snooze.rb
181
+ - examples/performance/thread-vs-fiber/polyphony_server.rb
182
+ - examples/performance/thread-vs-fiber/threaded_server.rb
183
+ - examples/streams/lines.rb
184
+ - examples/streams/stdio.rb
185
+ - ext/ev/async.c
186
+ - ext/ev/child.c
187
+ - ext/ev/ev.h
188
+ - ext/ev/ev_ext.c
189
+ - ext/ev/ev_module.c
107
190
  - ext/ev/extconf.rb
191
+ - ext/ev/io.c
192
+ - ext/ev/libev.h
193
+ - ext/ev/signal.c
194
+ - ext/ev/timer.c
195
+ - ext/libev/Changes
196
+ - ext/libev/LICENSE
197
+ - ext/libev/README
198
+ - ext/libev/README.embed
199
+ - ext/libev/ev.c
200
+ - ext/libev/ev.h
201
+ - ext/libev/ev_epoll.c
202
+ - ext/libev/ev_kqueue.c
203
+ - ext/libev/ev_poll.c
204
+ - ext/libev/ev_port.c
205
+ - ext/libev/ev_select.c
206
+ - ext/libev/ev_vars.h
207
+ - ext/libev/ev_win32.c
208
+ - ext/libev/ev_wrap.h
209
+ - ext/libev/test_libev_win32.c
108
210
  - lib/polyphony.rb
109
211
  - lib/polyphony/core.rb
110
- - lib/polyphony/core/async.rb
111
212
  - lib/polyphony/core/cancel_scope.rb
112
213
  - lib/polyphony/core/channel.rb
113
- - lib/polyphony/core/coroutine.rb
214
+ - lib/polyphony/core/coprocess.rb
114
215
  - lib/polyphony/core/exceptions.rb
115
216
  - lib/polyphony/core/fiber_pool.rb
116
217
  - lib/polyphony/core/supervisor.rb
@@ -134,11 +235,16 @@ files:
134
235
  - lib/polyphony/http/server.rb
135
236
  - lib/polyphony/line_reader.rb
136
237
  - lib/polyphony/net.rb
137
- - lib/polyphony/net_old.rb
138
238
  - lib/polyphony/resource_pool.rb
139
239
  - lib/polyphony/server_task.rb
140
240
  - lib/polyphony/testing.rb
141
241
  - lib/polyphony/version.rb
242
+ - lib/polyphony/websocket.rb
243
+ - polyphony.gemspec
244
+ - test/test_coprocess.rb
245
+ - test/test_core.rb
246
+ - test/test_ev.rb
247
+ - test/test_io.rb
142
248
  homepage: http://github.com/digital-fabric/polyphony
143
249
  licenses:
144
250
  - MIT
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export :async_decorate, :call_proc_with_optional_block
4
-
5
- Coroutine = import('./coroutine')
6
- FiberPool = import('./fiber_pool')
7
-
8
- # Converts a regular method into an async method, i.e. a method that returns a
9
- # proc that eventually executes the original code.
10
- # @param receiver [Object] object receiving the method call
11
- # @param sym [Symbol] method name
12
- # @return [void]
13
- def async_decorate(receiver, sym)
14
- sync_sym = :"sync_#{sym}"
15
- receiver.alias_method(sync_sym, sym)
16
- receiver.define_method(sym) do |*args, &block|
17
- Coroutine.new { send(sync_sym, *args, &block) }
18
- end
19
- end
20
-
21
- # Calls a proc with a block if both are given. Otherwise, call the first
22
- # non-nil proc. This allows syntax such as:
23
- #
24
- # # in fact, the call to #nexus returns a proc which takes a block
25
- # await nexus { ... }
26
- #
27
- # @param proc [Proc] proc A
28
- # @param block [Proc] proc B
29
- # @return [any] return value of proc invocation
30
- def call_proc_with_optional_block(proc, block)
31
- if proc && block
32
- proc.call(&block)
33
- else
34
- (proc || block).call
35
- end
36
- end
@@ -1,299 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export :Server, :Socket
4
-
5
- require 'socket'
6
- require 'openssl'
7
-
8
- Core = import('./core')
9
- IO = import('./io')
10
-
11
- # Implements a TCP server
12
- class Server
13
- # Initializes server
14
- def initialize(opts = {})
15
- @opts = opts
16
- @callbacks = {}
17
- end
18
-
19
- # Listens on host and port given in opts
20
- # @param opts [Hash] options
21
- # @return [void]
22
- def listen(opts)
23
- @secure_context = opts[:secure_context]
24
- @server = opts[:socket] || create_server_socket(opts)
25
- @watcher = EV::IO.new(@server, :r, true) { accept_from_socket }
26
- end
27
-
28
- # Creates a server socket for listening to incoming connections
29
- # @param opts [Hash] listening options
30
- # @return [Net::Socket]
31
- def create_server_socket(opts)
32
- socket = TCPServer.new(opts[:host] || '127.0.0.1', opts[:port])
33
- if @secure_context
34
- socket = OpenSSL::SSL::SSLServer.new(socket, @secure_context)
35
- socket.start_immediately = false
36
- setup_alpn(opts[:alpn_protocols]) if opts[:alpn_protocols]
37
- end
38
- socket
39
- end
40
-
41
- # Sets up ALPN protocols negotiated during handshake
42
- # @param server_protocols [Array<String>] protocols supported by server
43
- # @return [void]
44
- def setup_alpn(server_protocols)
45
- @secure_context.alpn_protocols = server_protocols
46
- @secure_context.alpn_select_cb = proc do |client_protocols|
47
- # select first common protocol
48
- (server_protocols & client_protocols).first
49
- end
50
- end
51
-
52
- # Returns true if server is listening
53
- # @return [Boolean]
54
- def listening?
55
- @server
56
- end
57
-
58
- # Closes the server socket
59
- # @return [void]
60
- def close
61
- Core.unwatch(@server)
62
- @server.close
63
- @server = nil
64
- end
65
-
66
- # Accepts an incoming connection, triggers the :connection callback
67
- # @return [void]
68
- def accept_from_socket
69
- socket = @server.accept
70
- setup_connection(socket) if socket
71
- rescue StandardError => e
72
- puts "error in accept_from_socket: #{e.inspect}"
73
- puts e.backtrace.join("\n")
74
- end
75
-
76
- # Sets up an accepted connection
77
- # @param socket [TCPSocket] accepted socket
78
- # @return [Net::Socket]
79
- def setup_connection(socket)
80
- opts = { connected: true, secure_context: @secure_context }
81
- if @secure_context
82
- connection = SecureSocket.new(socket, opts)
83
- connection.on(:handshake, &@callbacks[:connection])
84
- else
85
- connection = Socket.new(socket, opts)
86
- @callbacks[:connection]&.(connection)
87
- end
88
- end
89
-
90
- # Registers a callback for given event
91
- # @param event [Symbol] event kind
92
- # @return [void]
93
- def on(event, &block)
94
- @callbacks[event] = block
95
- end
96
-
97
- # Returns a promise fulfilled upon the first incoming connection
98
- # @return [Promise]
99
- def connection(&block)
100
- Core.promise(then: block, catch: block) do |p|
101
- @callbacks[:connection] = p.to_proc
102
- end
103
- end
104
-
105
- # Creates a generator promise, iterating asynchronously over incoming
106
- # connections
107
- # @return [void]
108
- def each_connection(&block)
109
- Core.promise(recurring: true) do |p|
110
- @callbacks[:connection] = p.to_proc
111
- end.each(&block)
112
- end
113
- end
114
-
115
- # Client connection functionality
116
- module ClientConnection
117
- # Connects to the given host & port, returning a promise fulfilled once
118
- # connected. Options can include:
119
- # :timeout => connection timeout in seconds
120
- # @param host [String] host domain name or IP address
121
- # @param port [Integer] port number
122
- # @param opts [Hash] options
123
- # @return [Promise] connection promise
124
- def connect(host, port, opts = {})
125
- Core.promise do |p|
126
- socket = ::Socket.new(::Socket::AF_INET, ::Socket::SOCK_STREAM)
127
- p.timeout(opts[:timeout]) { connect_timeout(socket) } if opts[:timeout]
128
- connect_async(socket, host, port, p)
129
- end
130
- end
131
-
132
- # Connects asynchronously to a TCP Server
133
- # @param socket [TCPSocket] socket to use for connection
134
- # @param host [String] host domain name or ip address
135
- # @param port [Integer] server port number
136
- # @param promise [Promise] connection promise
137
- # @return [void]
138
- def connect_async(socket, host, port, promise)
139
- addr = ::Socket.sockaddr_in(port, host)
140
- result = socket.connect_nonblock addr, exception: false
141
- handle_connect_result(result, socket, host, port, promise)
142
- rescue StandardError => e
143
- promise.reject(e)
144
- end
145
-
146
- # Handles result of asynchronous connection
147
- # @param result [Integer, Symbol, nil] result of call to IO#connect_nonblock
148
- # @param socket [TCPSocket] socket to use for connection
149
- # @param host [String] host domain name or ip address
150
- # @param port [Integer] server port number
151
- # @param promise [Promise] connection promise
152
- # @return [void]
153
- def handle_connect_result(result, socket, host, port, promise)
154
- case result
155
- when :wait_writable
156
- connect_async_pending(socket, host, port, promise)
157
- when 0
158
- @connection_pending = false
159
- connect_success(socket, promise)
160
- else
161
- handle_invalid_connect_result(result, socket)
162
- end
163
- end
164
-
165
- # Handles result of asynchronous connection
166
- # @param result [Integer, Symbol, nil] result of call to IO#connect_nonblock
167
- # @param socket [TCPSocket] socket to use for connection
168
- def handle_invalid_connect_result(result, socket)
169
- invalid_connect_result(result, socket)
170
- @connection_pending = false
171
- Core.unwatch(socket)
172
- @monitor = nil
173
- raise "Invalid result from connect_nonblock: #{result.inspect}"
174
- end
175
-
176
- # Sets connection pending state
177
- # @param socket [TCPSocket] socket to use for connection
178
- # @param host [String] host domain name or ip address
179
- # @param port [Integer] server port number
180
- # @param promise [Promise] connection promise
181
- # @return [void]
182
- def connect_async_pending(socket, host, port, promise)
183
- create_watcher(socket, true, true)
184
- @connection_pending = [socket, host, port, promise]
185
- end
186
-
187
- # Overrides IO#write_to_io to support async connection
188
- # @return [void]
189
- def write_to_io
190
- @connection_pending ? connect_async(*@connection_pending) : super
191
- end
192
-
193
- # Sets socket and connected status on successful connection
194
- # @param socket [TCPSocket] TCP socket
195
- # @param promise [Promise] connection promise
196
- # @return [void]
197
- def connect_success(socket, promise)
198
- @io = socket
199
- @connected = true
200
- promise.resolve(socket)
201
- end
202
-
203
- # Called upon connection timeout, cleans up
204
- # @param socket [TCPSocket] TCP socket
205
- # @return [void]
206
- def connect_timeout(socket)
207
- @connection_pending = false
208
- Core.unwatch(socket)
209
- @monitor = nil
210
- socket.close
211
- end
212
- end
213
-
214
- # ALPN protocol
215
- module ALPN
216
- # returns the ALPN protocol used for the given socket
217
- # @return [String, nil]
218
- def alpn_protocol
219
- secure? && raw_io.alpn_protocol
220
- end
221
- end
222
-
223
- # Encapsulates a TCP socket
224
- class Socket < IO
225
- include ClientConnection
226
- include ALPN
227
-
228
- # Initializes socket
229
- def initialize(socket = nil, opts = {})
230
- super(socket, opts)
231
- @connected = opts[:connected]
232
- end
233
-
234
- # Returns true if socket is connected
235
- # @return [Boolean]
236
- def connected?
237
- @connected
238
- end
239
-
240
- # Returns false
241
- # @return [false]
242
- def secure?
243
- false
244
- end
245
-
246
- # Sets socket option
247
- # @return [void]
248
- def setsockopt(*args)
249
- @io.setsockopt(*args)
250
- end
251
- end
252
-
253
- # Socket with TLS handshake functionality
254
- class SecureSocket < Socket
255
- # Initializes secure socket
256
- def initialize(socket = nil, opts = {})
257
- super
258
- accept_secure_handshake
259
- end
260
-
261
- # Returns true
262
- # @return [true]
263
- def secure?
264
- true
265
- end
266
-
267
- # accepts secure handshake asynchronously
268
- # @return [void]
269
- def accept_secure_handshake
270
- @pending_secure_handshake = true
271
- result = @io.accept_nonblock(exception: false)
272
- handle_accept_secure_handshake_result(result)
273
- rescue StandardError => e
274
- close_on_error(e)
275
- end
276
-
277
- # Handles result of secure handshake
278
- # @param result [Integer, any] result of call to accept_nonblock
279
- # @return [void]
280
- def handle_accept_secure_handshake_result(result)
281
- case result
282
- when :wait_readable
283
- @watcher_r.start
284
- when :wait_writable
285
- @watcher_w.start
286
- else
287
- @pending_secure_handshake = false
288
- @watcher_r.start
289
- @watcher_w.stop
290
- @callbacks[:handshake]&.(self)
291
- end
292
- end
293
-
294
- # Overrides read_from_io to accept secure handshake
295
- # @return [void]
296
- def read_from_io
297
- @pending_secure_handshake ? accept_secure_handshake : super
298
- end
299
- end