connectrpc-async 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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +542 -0
- data/lib/connectrpc/async/application.rb +173 -0
- data/lib/connectrpc/async/completion_join.rb +249 -0
- data/lib/connectrpc/async/connection.rb +711 -0
- data/lib/connectrpc/async/connection_manager.rb +358 -0
- data/lib/connectrpc/async/connection_policy.rb +60 -0
- data/lib/connectrpc/async/host.rb +171 -0
- data/lib/connectrpc/async/http1_connection.rb +144 -0
- data/lib/connectrpc/async/http1_exchange.rb +27 -0
- data/lib/connectrpc/async/http1_stream.rb +76 -0
- data/lib/connectrpc/async/http1_transport.rb +642 -0
- data/lib/connectrpc/async/ingress.rb +100 -0
- data/lib/connectrpc/async/internal.rb +270 -0
- data/lib/connectrpc/async/server_session.rb +457 -0
- data/lib/connectrpc/async/stream_exchange.rb +324 -0
- data/lib/connectrpc/async/tls.rb +124 -0
- data/lib/connectrpc/async/tls_configuration.rb +309 -0
- data/lib/connectrpc/async/transport.rb +488 -0
- data/lib/connectrpc/async/version.rb +7 -0
- data/lib/connectrpc/async.rb +39 -0
- data/sig/connectrpc-async.rbs +165 -0
- data/sig/manifest.yaml +4 -0
- metadata +202 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConnectRPC
|
|
4
|
+
module Async
|
|
5
|
+
class ServerSession
|
|
6
|
+
include Internal::Affinity
|
|
7
|
+
|
|
8
|
+
def initialize(application:, tls: nil, tls_provider: nil, max_connections: 128, max_requests: 128,
|
|
9
|
+
handshake_timeout: 10, idle_timeout: 60, refresh_interval: 60, refresh_jitter: 0.1,
|
|
10
|
+
refresh_timeout: 10, on_tls_error: nil, alpn_protocols: ["h2"], http1: nil, stop_accepting: nil,
|
|
11
|
+
on_request_rejected: nil, lifecycle_factory: nil)
|
|
12
|
+
initialize_affinity
|
|
13
|
+
raise ArgumentError, "application must be callable" unless application.respond_to?(:call)
|
|
14
|
+
raise ArgumentError, "stop_accepting must be callable" if stop_accepting && !stop_accepting.respond_to?(:call)
|
|
15
|
+
if on_request_rejected && !on_request_rejected.respond_to?(:call)
|
|
16
|
+
raise ArgumentError, "on_request_rejected must be callable"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@lifecycle_factory = lifecycle_factory
|
|
20
|
+
@application = application
|
|
21
|
+
@http1 = http1
|
|
22
|
+
@stop_accepting = stop_accepting
|
|
23
|
+
@on_request_rejected = on_request_rejected
|
|
24
|
+
@selector = ::Async::HTTP::Protocol::HTTP.new if http1
|
|
25
|
+
@max_connections = Internal.positive_integer(max_connections, "max_connections")
|
|
26
|
+
@max_requests = Internal.positive_integer(max_requests, "max_requests")
|
|
27
|
+
@handshake_timeout = Internal.seconds(handshake_timeout, "handshake_timeout")
|
|
28
|
+
@idle_timeout = Internal.seconds(idle_timeout, "idle_timeout") unless idle_timeout.nil?
|
|
29
|
+
@tls = TLSConfiguration.new(tls: tls, tls_provider: tls_provider, refresh_interval: refresh_interval,
|
|
30
|
+
refresh_jitter: refresh_jitter, refresh_timeout: refresh_timeout,
|
|
31
|
+
on_error: on_tls_error, alpn_protocols: alpn_protocols)
|
|
32
|
+
@retirement_floor = 0
|
|
33
|
+
@admitted = {}
|
|
34
|
+
@h1_wire = {}
|
|
35
|
+
@identities = {}
|
|
36
|
+
install_owner_guard
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def inspect = "#<#{self.class} state=#{@state} tls_generation=#{tls_generation.inspect}>"
|
|
40
|
+
|
|
41
|
+
def start
|
|
42
|
+
preflight_start
|
|
43
|
+
return self if @state == :running
|
|
44
|
+
|
|
45
|
+
@tls.start
|
|
46
|
+
@state = :running
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reload_tls(certificate_chain:, private_key:, client_ca:)
|
|
51
|
+
check_running_tls!
|
|
52
|
+
@tls.reload(certificate_chain: certificate_chain, private_key: private_key, client_ca: client_ca)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def tls_generation = @tls.generation
|
|
56
|
+
def tls_status = @tls.status
|
|
57
|
+
def refresh_tls = @tls.refresh
|
|
58
|
+
|
|
59
|
+
def close(grace: 5)
|
|
60
|
+
check_affinity!
|
|
61
|
+
@tls.send(:check_recursion!)
|
|
62
|
+
Internal.seconds(grace, "grace", zero: true)
|
|
63
|
+
return @close_outcome if @state == :closed
|
|
64
|
+
|
|
65
|
+
return nil if @cleanup&.current? || @closer&.current?
|
|
66
|
+
|
|
67
|
+
owned = resource_owned_task?
|
|
68
|
+
launch = @state != :closing
|
|
69
|
+
if launch
|
|
70
|
+
@state = :closing
|
|
71
|
+
@closer = ::Async::Task.new(@owner, finished: false) do
|
|
72
|
+
begin
|
|
73
|
+
close_owned(Internal.now + grace)
|
|
74
|
+
ensure
|
|
75
|
+
abort_owned
|
|
76
|
+
end
|
|
77
|
+
@close_outcome
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
if owned
|
|
81
|
+
@closer.run if launch
|
|
82
|
+
return @state == :closed ? @close_outcome : nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
waiter = @closer || @cleanup
|
|
86
|
+
Internal.join_complete(waiter, failure: true, on_interrupt: method(:abort_owned)) { @closer.run if launch }
|
|
87
|
+
@close_outcome
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def abort
|
|
91
|
+
check_affinity!
|
|
92
|
+
@tls.send(:check_recursion!)
|
|
93
|
+
abort_owned
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def drain_tls(grace: 5)
|
|
98
|
+
check_running_tls!
|
|
99
|
+
Internal.seconds(grace, "grace", zero: true)
|
|
100
|
+
cutoff = tls_generation
|
|
101
|
+
@retirement_floor = [@retirement_floor, cutoff].max
|
|
102
|
+
targets = @resources.reject { |resource| resource.send(:retirement_completed?) }
|
|
103
|
+
.select { |resource| resource.generation < cutoff }
|
|
104
|
+
targets.each { |resource| resource.draining = true }
|
|
105
|
+
drain_resources(targets, Internal.now + grace)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def peer_certificate
|
|
109
|
+
return unless Thread.current.equal?(@thread) && Fiber.scheduler.equal?(@reactor)
|
|
110
|
+
|
|
111
|
+
der = @identities[::Async::Task.current?]
|
|
112
|
+
OpenSSL::X509::Certificate.new(der.dup) if der
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Accepted work must outlive cancellation of a listener's accept loop.
|
|
116
|
+
def schedule(&)
|
|
117
|
+
check_affinity!
|
|
118
|
+
@lifetime.async(finished: false, &)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Hosting SPI. Capture must happen in the accepting task before it
|
|
122
|
+
# schedules any handshake work. The returned handle owns this one peer.
|
|
123
|
+
def accept(raw)
|
|
124
|
+
check_affinity!
|
|
125
|
+
@resources.reject! { |resource| resource.send(:retirement_completed?) }
|
|
126
|
+
unless @state == :running && @resources.size < @max_connections
|
|
127
|
+
Internal.raw_close(raw)
|
|
128
|
+
return
|
|
129
|
+
end
|
|
130
|
+
capture = @tls.send(:capture, raw)
|
|
131
|
+
resource = Internal::Resource.new(progress: @progress, generation: capture.generation, snapshot: capture)
|
|
132
|
+
resource.send(:retirement_parent=, @lifetime)
|
|
133
|
+
resource.raw = raw
|
|
134
|
+
@resources << resource
|
|
135
|
+
Accepted.new(self, resource, @progress)
|
|
136
|
+
rescue LifecycleError
|
|
137
|
+
raise
|
|
138
|
+
rescue StandardError
|
|
139
|
+
Internal.raw_close(raw)
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class Accepted
|
|
144
|
+
def initialize(session, resource, progress)
|
|
145
|
+
@session = session
|
|
146
|
+
@resource = resource
|
|
147
|
+
@progress = progress
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def serve
|
|
151
|
+
@session.send(:serve, @resource) { |resource| @resource = resource }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def close
|
|
155
|
+
@resource.abort unless @resource.finishing?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def wait_closed
|
|
159
|
+
@progress.wait until @resource.send(:retirement_completed?)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def timeout=(value)
|
|
163
|
+
@resource.raw.timeout = value if @resource.raw.respond_to?(:timeout=)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def remote_address = @resource.raw.remote_address
|
|
167
|
+
def inspect = "#<#{self.class} accepted connection>"
|
|
168
|
+
end
|
|
169
|
+
private_constant :Accepted
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
# Nonyielding calling-task validation, shared with Host before startup
|
|
174
|
+
# cleanup can retire anything. A running session stays idempotent.
|
|
175
|
+
def preflight_start
|
|
176
|
+
check_affinity!
|
|
177
|
+
return if @state == :running
|
|
178
|
+
raise LifecycleError, "Server session cannot start after close" unless @state == :new
|
|
179
|
+
|
|
180
|
+
@tls.send(:check_recursion!)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def check_running_tls!
|
|
184
|
+
check_affinity!
|
|
185
|
+
raise LifecycleError, "A running TLS host is required" unless @state == :running && tls_generation
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def serve(resource)
|
|
189
|
+
return if resource.finished?
|
|
190
|
+
|
|
191
|
+
resource.task = ::Async::Task.current
|
|
192
|
+
expires = Internal.now + @handshake_timeout
|
|
193
|
+
setup = resource.track(::Async::Task.new(::Async::Task.current, finished: false) { setup_connection(resource) })
|
|
194
|
+
setup.run
|
|
195
|
+
resource = Internal.wait(setup, expires)
|
|
196
|
+
yield resource if block_given?
|
|
197
|
+
return if resource.draining || (tls_generation && resource.generation < @retirement_floor)
|
|
198
|
+
|
|
199
|
+
resource.ready = true
|
|
200
|
+
resource.changed
|
|
201
|
+
resource.connection.start_connection if resource.connection.respond_to?(:start_connection)
|
|
202
|
+
idle = @lifetime.async(finished: false) { watch_idle(resource) } if @idle_timeout
|
|
203
|
+
resource.connection.each { |request| dispatch(resource, request) }
|
|
204
|
+
rescue StandardError
|
|
205
|
+
# Socket/setup/protocol failures terminate only this owned connection.
|
|
206
|
+
nil
|
|
207
|
+
ensure
|
|
208
|
+
Internal.cancel(idle)
|
|
209
|
+
resource.abort unless resource.finishing?
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def setup_connection(resource)
|
|
213
|
+
socket = resource.snapshot.handshake { |socket| resource.socket = socket }
|
|
214
|
+
resource.socket = socket
|
|
215
|
+
stream = ::IO::Stream(socket)
|
|
216
|
+
if @http1 && ((!resource.snapshot.tls? &&
|
|
217
|
+
@selector.protocol_for(stream) == ::Async::HTTP::Protocol::HTTP1) ||
|
|
218
|
+
(resource.snapshot.tls? && socket.alpn_protocol != "h2"))
|
|
219
|
+
return resource if resource.draining || (tls_generation && resource.generation < @retirement_floor)
|
|
220
|
+
|
|
221
|
+
replacement = @http1.call(stream, progress: @progress, generation: resource.generation)
|
|
222
|
+
replacement.send(:retirement_parent=, @lifetime)
|
|
223
|
+
replacement.raw = resource.raw
|
|
224
|
+
replacement.socket = socket
|
|
225
|
+
replacement.snapshot = resource.snapshot
|
|
226
|
+
replacement.task = resource.task
|
|
227
|
+
@resources[@resources.index(resource)] = replacement
|
|
228
|
+
return replacement
|
|
229
|
+
end
|
|
230
|
+
if resource.snapshot.tls? && socket.alpn_protocol != "h2"
|
|
231
|
+
raise OpenSSL::SSL::SSLError, "HTTP/2 ALPN is required"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
connection = Internal::ServerConnection.new(stream)
|
|
235
|
+
resource.connection = connection
|
|
236
|
+
connection.resource = resource
|
|
237
|
+
connection.read_connection_preface(Internal.settings(@max_requests))
|
|
238
|
+
resource
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def watch_idle(resource)
|
|
242
|
+
loop do
|
|
243
|
+
if resource.empty?
|
|
244
|
+
remaining = @idle_timeout - (Internal.now - resource.activity_at)
|
|
245
|
+
if remaining <= 0
|
|
246
|
+
resource.abort
|
|
247
|
+
break
|
|
248
|
+
end
|
|
249
|
+
begin
|
|
250
|
+
::Async::Task.current.with_timeout([remaining, Internal::MAX_WAIT].min) { resource.progress.wait }
|
|
251
|
+
rescue ::Async::TimeoutError
|
|
252
|
+
next
|
|
253
|
+
end
|
|
254
|
+
else
|
|
255
|
+
resource.progress.wait
|
|
256
|
+
end
|
|
257
|
+
break if resource.finished?
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def dispatch(resource, request)
|
|
262
|
+
if resource.respond_to?(:prepare_stream_input) && @application.respond_to?(:http1_streaming_route?, true) &&
|
|
263
|
+
@application.send(:http1_streaming_route?, request)
|
|
264
|
+
retirement = resource.prepare_stream_input(request.body)
|
|
265
|
+
end
|
|
266
|
+
lifecycle = @lifecycle_factory&.call(request)
|
|
267
|
+
if lifecycle
|
|
268
|
+
exchange = Internal::Exchange.new(lifecycle)
|
|
269
|
+
if request.respond_to?(:stream)
|
|
270
|
+
request.stream.exchange = exchange
|
|
271
|
+
else
|
|
272
|
+
resource.exchange = exchange
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
@admitted.delete_if { |stream, completed| completed && stream.respond_to?(:closed?) && stream.closed? }
|
|
276
|
+
key = request.respond_to?(:stream) ? request.stream : request
|
|
277
|
+
if @state != :running || resource.draining || @admitted.size >= @max_requests
|
|
278
|
+
if exchange
|
|
279
|
+
exchange.decision = :admission_rejected
|
|
280
|
+
exchange.http_status = 503
|
|
281
|
+
end
|
|
282
|
+
@on_request_rejected&.call
|
|
283
|
+
retirement&.processor_finished
|
|
284
|
+
retirement&.head_resolved(503)
|
|
285
|
+
return @application.admission_response(request) if @application.respond_to?(:admission_response)
|
|
286
|
+
|
|
287
|
+
return ::Protocol::HTTP::Response[503, {}, nil]
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
@admitted[key] = false
|
|
291
|
+
begin
|
|
292
|
+
retirement&.on_close do
|
|
293
|
+
complete_admission(key, wire: true)
|
|
294
|
+
resource.changed
|
|
295
|
+
end
|
|
296
|
+
publishes = request.respond_to?(:stream) ||
|
|
297
|
+
(@application.respond_to?(:publication?, true) && @application.send(:publication?, request))
|
|
298
|
+
publication = Internal::Publication.new if publishes
|
|
299
|
+
handler = ::Async::Task.new(@lifetime, finished: false) do |task|
|
|
300
|
+
@identities[task] = resource.snapshot.authenticated_leaf
|
|
301
|
+
work = lambda do
|
|
302
|
+
if publication && @application.respond_to?(:call_with_publication)
|
|
303
|
+
@application.call_with_publication(request, lifecycle: lifecycle, publication: publication,
|
|
304
|
+
resource: resource)
|
|
305
|
+
elsif lifecycle
|
|
306
|
+
@application.call(request, lifecycle: lifecycle)
|
|
307
|
+
else
|
|
308
|
+
@application.call(request)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
application_work = work
|
|
312
|
+
work = -> { lifecycle.send(:with_processing_completion, &application_work) } if lifecycle && publication
|
|
313
|
+
result = lifecycle ? lifecycle.activate(&work) : work.call
|
|
314
|
+
publication.publish(result) if publication && !publication.published?
|
|
315
|
+
result
|
|
316
|
+
rescue StandardError => e
|
|
317
|
+
raise unless publication
|
|
318
|
+
|
|
319
|
+
lifecycle&.record_failure(e)
|
|
320
|
+
publication.complete(e)
|
|
321
|
+
nil
|
|
322
|
+
ensure
|
|
323
|
+
complete_processor(publication, task, key, resource)
|
|
324
|
+
end
|
|
325
|
+
resource.track(handler)
|
|
326
|
+
handler.run
|
|
327
|
+
response = publication ? publication.response : handler.wait
|
|
328
|
+
retirement&.head_resolved(response&.status)
|
|
329
|
+
exchange.http_status = response&.status if exchange
|
|
330
|
+
if !request.respond_to?(:stream) && response
|
|
331
|
+
response.body = Internal::AdmissionBody.new(response.body) do
|
|
332
|
+
# Connect retirement owns completion through post-delivery hygiene.
|
|
333
|
+
complete_admission(key, wire: true) unless retirement
|
|
334
|
+
resource.changed
|
|
335
|
+
end
|
|
336
|
+
deferred = true
|
|
337
|
+
# An early Web head can stop an HTTP/1 client's unfinished upload.
|
|
338
|
+
# Keep input cleanup in the original processor before serialization;
|
|
339
|
+
# consumed unary input still permits incremental server streams.
|
|
340
|
+
# Connect retirement owns input hygiene after response delivery.
|
|
341
|
+
if publication && !retirement && GRPCWeb.intent?(Internal.headers(request)) &&
|
|
342
|
+
request.body && !request.body.empty?
|
|
343
|
+
begin
|
|
344
|
+
publication.wait
|
|
345
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- cancellation must close deferred ownership too
|
|
346
|
+
begin
|
|
347
|
+
response.body.close(e)
|
|
348
|
+
ensure
|
|
349
|
+
raise e
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
response
|
|
355
|
+
ensure
|
|
356
|
+
complete_admission(key, processor: true) unless handler
|
|
357
|
+
complete_admission(key, wire: true) unless deferred || retirement
|
|
358
|
+
resource.changed
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def complete_processor(publication, task, key, resource)
|
|
363
|
+
@identities.delete(task)
|
|
364
|
+
complete_admission(key, processor: true)
|
|
365
|
+
resource.changed
|
|
366
|
+
publication&.complete($ERROR_INFO)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def complete_admission(key, processor: false, wire: false)
|
|
370
|
+
# An absent admission is terminal. Late or duplicate events cannot
|
|
371
|
+
# recreate either side of the processor/wire join.
|
|
372
|
+
return unless @admitted.key?(key)
|
|
373
|
+
|
|
374
|
+
@admitted[key] = true if processor
|
|
375
|
+
if key.respond_to?(:closed?)
|
|
376
|
+
@admitted.delete(key) if @admitted[key] && (@aborting || key.closed?)
|
|
377
|
+
else
|
|
378
|
+
@h1_wire[key] = true if wire
|
|
379
|
+
if @admitted[key] && @h1_wire[key]
|
|
380
|
+
@admitted.delete(key)
|
|
381
|
+
@h1_wire.delete(key)
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def drain_resources(resources, expires)
|
|
387
|
+
tasks = resources.map do |resource|
|
|
388
|
+
@lifetime.async(finished: false) { resource.drain(@lifetime, expires) }
|
|
389
|
+
end
|
|
390
|
+
tasks.map(&:wait).all?
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def stop_accepting
|
|
394
|
+
@stop_accepting&.call
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def close_owned(expires)
|
|
398
|
+
stop_accepting
|
|
399
|
+
@tls.close
|
|
400
|
+
@resources.each { |resource| resource.draining = true }
|
|
401
|
+
drained = drain_resources(@resources, expires)
|
|
402
|
+
@admitted.clear
|
|
403
|
+
@h1_wire.clear
|
|
404
|
+
decide_close_outcome(drained)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def install_owner_guard
|
|
408
|
+
@lifetime = @owner.async do
|
|
409
|
+
::Async::Condition.new.wait
|
|
410
|
+
ensure
|
|
411
|
+
abort_owned unless @state == :closed
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def resource_owned_task?
|
|
416
|
+
@resources.any? { |resource| resource.send(:owns_task?, ::Async::Task.current) }
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def decide_close_outcome(outcome)
|
|
420
|
+
return if @close_decided
|
|
421
|
+
|
|
422
|
+
@close_decided = true
|
|
423
|
+
@close_outcome = outcome
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def abort_owned
|
|
427
|
+
return if @cleanup&.current?
|
|
428
|
+
|
|
429
|
+
owned = resource_owned_task?
|
|
430
|
+
launch = !@cleanup
|
|
431
|
+
if launch
|
|
432
|
+
@aborting = true
|
|
433
|
+
@state = :closing unless @state == :closed
|
|
434
|
+
decide_close_outcome(false)
|
|
435
|
+
@cleanup = ::Async::Task.new(@lifetime, transient: true, finished: false) { retire_session }
|
|
436
|
+
end
|
|
437
|
+
if owned
|
|
438
|
+
@cleanup.run if launch
|
|
439
|
+
else
|
|
440
|
+
Internal.join_complete(@cleanup, failure: true) { @cleanup.run if launch }
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def retire_session
|
|
445
|
+
# Listener cleanup can yield. Fail every peer before crossing it.
|
|
446
|
+
@resources.each { |resource| Internal.raw_close(resource.raw) }
|
|
447
|
+
stop_accepting
|
|
448
|
+
@resources.each(&:abort)
|
|
449
|
+
@admitted.clear
|
|
450
|
+
@h1_wire.clear
|
|
451
|
+
@tls.close
|
|
452
|
+
finish_closed(@close_outcome)
|
|
453
|
+
Internal.cancel(@lifetime)
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
end
|