polyphony 0.13 → 0.14

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.
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