spinel-spinner 0.1.0.pre.1
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/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +164 -0
- data/lib/spinner/body_storage.rb +106 -0
- data/lib/spinner/chunk_decoder.rb +72 -0
- data/lib/spinner/host_authority.rb +86 -0
- data/lib/spinner/input.rb +58 -0
- data/lib/spinner/proxy_metadata.rb +93 -0
- data/lib/spinner/reactor.rb +575 -0
- data/lib/spinner/request_parser.rb +183 -0
- data/lib/spinner/response_writer.rb +123 -0
- data/lib/spinner/server.rb +384 -0
- data/lib/spinner/task_pool.rb +62 -0
- data/lib/spinner/thread_pool.rb +229 -0
- data/lib/spinner/tls.rb +116 -0
- data/lib/spinner/transport_socket.rb +121 -0
- data/lib/spinner/upgrade.rb +20 -0
- data/lib/spinner/version.rb +5 -0
- data/lib/spinner/websocket.rb +179 -0
- data/lib/spinner.rb +21 -0
- data/sig/spinner.rbs +4 -0
- metadata +63 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
module Spinner
|
|
2
|
+
# A socket belongs to this state machine until a complete request is handed
|
|
3
|
+
# to a worker. It also writes protocol errors and queue rejection responses.
|
|
4
|
+
class PendingClient
|
|
5
|
+
attr_reader :socket, :parser, :state, :id, :remainder
|
|
6
|
+
|
|
7
|
+
def initialize(socket, port, header_limit, read_timeout, body_timeout, write_timeout,
|
|
8
|
+
capacity, queue_timeout, id, keepalive_timeout, max_requests,
|
|
9
|
+
tls_context = nil, options = {})
|
|
10
|
+
@options = options
|
|
11
|
+
@storage_pool = options[:storage_pool]
|
|
12
|
+
@storage_pending = false
|
|
13
|
+
@storage_finished = false
|
|
14
|
+
@storage_error = nil
|
|
15
|
+
@storage_status = 503
|
|
16
|
+
@storage = nil
|
|
17
|
+
@close_lock = Mutex.new
|
|
18
|
+
@peer = socket.respond_to?(:peeraddr) ? socket.peeraddr[3] : ""
|
|
19
|
+
@peer = "" if @peer.nil?
|
|
20
|
+
@socket = TLS::Socket.new(socket, tls_context)
|
|
21
|
+
@port = port
|
|
22
|
+
@scheme = tls_context.nil? ? "http" : "https"
|
|
23
|
+
@header_limit = header_limit
|
|
24
|
+
@parser = RequestParser.new(header_limit, port, @scheme, @options[:max_body_bytes])
|
|
25
|
+
@state = tls_context.nil? ? :headers : :handshake
|
|
26
|
+
@read_timeout = read_timeout
|
|
27
|
+
@body_timeout = body_timeout
|
|
28
|
+
@write_timeout = write_timeout
|
|
29
|
+
@capacity = capacity
|
|
30
|
+
@queue_timeout = queue_timeout
|
|
31
|
+
@id = id
|
|
32
|
+
@deadline = Transport.now + read_timeout
|
|
33
|
+
@body_size = 0
|
|
34
|
+
@body_complete = false
|
|
35
|
+
@remainder = "".b
|
|
36
|
+
@chunk_decoder = nil
|
|
37
|
+
@requests_served = 0
|
|
38
|
+
@keepalive_timeout = keepalive_timeout
|
|
39
|
+
@max_requests = max_requests
|
|
40
|
+
@idle = false
|
|
41
|
+
@chunks = []
|
|
42
|
+
@chunk_index = 0
|
|
43
|
+
@chunk_offset = 0
|
|
44
|
+
@wait_direction = :read
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def storage_pending?
|
|
48
|
+
@storage_pending
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reading?
|
|
52
|
+
!@storage_pending && (@state == :handshake || @state == :headers || @state == :body)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def writing?
|
|
56
|
+
@state == :writing || @state == :interim
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def wait_readable?
|
|
60
|
+
(reading? || writing?) && @wait_direction == :read
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def wait_writable?
|
|
64
|
+
(reading? || writing?) && @wait_direction == :write
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def buffered_tls_data?
|
|
68
|
+
@state != :handshake && reading? && @socket.respond_to?(:pending) && @socket.pending > 0
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def io_step
|
|
72
|
+
if @state == :handshake
|
|
73
|
+
handshake_step
|
|
74
|
+
elsif reading?
|
|
75
|
+
read_step
|
|
76
|
+
elsif writing?
|
|
77
|
+
write_step
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def handshake_step
|
|
82
|
+
@socket.accept_nonblock
|
|
83
|
+
@state = :headers
|
|
84
|
+
@wait_direction = :read
|
|
85
|
+
@deadline = Transport.now + @read_timeout
|
|
86
|
+
rescue IO::WaitReadable
|
|
87
|
+
@wait_direction = :read
|
|
88
|
+
rescue IO::WaitWritable
|
|
89
|
+
@wait_direction = :write
|
|
90
|
+
rescue EOFError, IOError, SystemCallError => error
|
|
91
|
+
warn "SPINNER_METRIC event=tls_failure message=#{error.message}"
|
|
92
|
+
close
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ready?
|
|
96
|
+
@state == :ready
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def closed?
|
|
100
|
+
@state == :closed
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def idle?
|
|
104
|
+
@idle
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def deadline
|
|
108
|
+
@deadline
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def queued!
|
|
112
|
+
@state = :queued
|
|
113
|
+
@queued_at = Transport.now
|
|
114
|
+
@deadline = @queued_at + @queue_timeout
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def claim!
|
|
118
|
+
@state = :worker
|
|
119
|
+
@queue_wait_ms = ((Transport.now - @queued_at) * 1000).round
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def queue_wait_ms
|
|
123
|
+
@queue_wait_ms
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def queued?
|
|
127
|
+
@state == :queued
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def body
|
|
131
|
+
@storage.input
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def environment
|
|
135
|
+
@options[:proxy_policy].apply(@parser.environment(body), @peer)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def cleanup_body
|
|
139
|
+
@storage.close unless @storage.nil? || (@storage_pending && !@storage_finished)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def storage_step
|
|
143
|
+
return unless @storage_pending && @storage_finished
|
|
144
|
+
@storage_pending = false
|
|
145
|
+
if closed?
|
|
146
|
+
cleanup_body
|
|
147
|
+
elsif !@storage_error.nil?
|
|
148
|
+
reject(@storage_status)
|
|
149
|
+
else
|
|
150
|
+
@state = :ready if @body_complete
|
|
151
|
+
finish_continue
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def store_chunk(chunk)
|
|
156
|
+
if @body_size + chunk.bytesize <= @options[:body_memory_bytes] && @chunk_decoder.nil?
|
|
157
|
+
append_body(chunk)
|
|
158
|
+
@state = :ready if @body_complete
|
|
159
|
+
finish_continue
|
|
160
|
+
return
|
|
161
|
+
end
|
|
162
|
+
@storage_pending = true
|
|
163
|
+
@storage_finished = false
|
|
164
|
+
@storage_error = nil
|
|
165
|
+
@storage_status = 503
|
|
166
|
+
accepted = @storage_pool.submit(proc { @storage.close }) do
|
|
167
|
+
begin
|
|
168
|
+
append_body(chunk)
|
|
169
|
+
rescue RequestError => error
|
|
170
|
+
@storage_status = error.status
|
|
171
|
+
@storage_error = error
|
|
172
|
+
rescue StandardError => error
|
|
173
|
+
@storage_error = error
|
|
174
|
+
ensure
|
|
175
|
+
@storage_finished = true
|
|
176
|
+
@storage.close if closed?
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
unless accepted
|
|
180
|
+
@storage_pending = false
|
|
181
|
+
reject(503)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def finish_continue
|
|
186
|
+
if @parser.expect_continue? && @state == :body && !@continued
|
|
187
|
+
@continued = true
|
|
188
|
+
@chunks = ["HTTP/1.1 100 Continue\r\n\r\n"]
|
|
189
|
+
@chunk_index = 0
|
|
190
|
+
@chunk_offset = 0
|
|
191
|
+
@state = :interim
|
|
192
|
+
@wait_direction = :write
|
|
193
|
+
@deadline = Transport.now + @write_timeout
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
attr_accessor :stream_deadline
|
|
198
|
+
|
|
199
|
+
def expire_stream(now)
|
|
200
|
+
close if !@stream_deadline.nil? && now >= @stream_deadline
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def keep_alive?
|
|
204
|
+
@parser.keep_alive? && @requests_served + 1 < @max_requests
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def next_request!
|
|
208
|
+
@requests_served += 1
|
|
209
|
+
bytes = @remainder
|
|
210
|
+
@remainder = "".b
|
|
211
|
+
cleanup_body
|
|
212
|
+
@storage = nil
|
|
213
|
+
@continued = false
|
|
214
|
+
@parser = RequestParser.new(@header_limit, @port, @scheme, @options[:max_body_bytes])
|
|
215
|
+
@body_size = 0
|
|
216
|
+
@body_complete = false
|
|
217
|
+
@chunk_decoder = nil
|
|
218
|
+
@state = :headers
|
|
219
|
+
@wait_direction = :read
|
|
220
|
+
@idle = true
|
|
221
|
+
@deadline = Transport.now + @keepalive_timeout
|
|
222
|
+
consume(bytes) unless bytes.empty?
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def read_step
|
|
226
|
+
chunk = @socket.read_nonblock(65536)
|
|
227
|
+
@wait_direction = :read
|
|
228
|
+
consume(chunk)
|
|
229
|
+
rescue IO::WaitReadable
|
|
230
|
+
@wait_direction = :read
|
|
231
|
+
rescue IO::WaitWritable
|
|
232
|
+
@wait_direction = :write
|
|
233
|
+
rescue EOFError
|
|
234
|
+
if @state == :headers
|
|
235
|
+
close
|
|
236
|
+
else
|
|
237
|
+
reject(400)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def consume(chunk)
|
|
242
|
+
if @idle && !chunk.empty?
|
|
243
|
+
@idle = false
|
|
244
|
+
@deadline = Transport.now + @read_timeout
|
|
245
|
+
end
|
|
246
|
+
if @state == :headers
|
|
247
|
+
if @parser.feed(chunk)
|
|
248
|
+
@state = :body
|
|
249
|
+
@body_deadline = Transport.now + @body_timeout
|
|
250
|
+
@deadline = @body_deadline
|
|
251
|
+
@storage = BodyStorage.new(@options[:max_body_bytes], @options[:body_memory_bytes],
|
|
252
|
+
@options[:spool_directory], @options[:storage_budget])
|
|
253
|
+
@chunk_decoder = ChunkDecoder.new(@storage, @options[:max_body_bytes]) if @parser.chunked?
|
|
254
|
+
store_chunk(@parser.remainder)
|
|
255
|
+
end
|
|
256
|
+
else
|
|
257
|
+
store_chunk(chunk)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def append_body(chunk)
|
|
262
|
+
unless @chunk_decoder.nil?
|
|
263
|
+
if @chunk_decoder.feed(chunk)
|
|
264
|
+
@remainder = @chunk_decoder.remainder
|
|
265
|
+
@body_complete = true
|
|
266
|
+
end
|
|
267
|
+
return
|
|
268
|
+
end
|
|
269
|
+
remaining = @parser.content_length - @body_size
|
|
270
|
+
count = [remaining, chunk.bytesize].min
|
|
271
|
+
if count > 0
|
|
272
|
+
@storage.append(chunk.byteslice(0, count).b)
|
|
273
|
+
@body_size += count
|
|
274
|
+
end
|
|
275
|
+
if @body_size == @parser.content_length
|
|
276
|
+
@remainder = chunk.byteslice(count, chunk.bytesize - count).to_s.b
|
|
277
|
+
@body_complete = true
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def reject(status)
|
|
282
|
+
cleanup_body
|
|
283
|
+
warn "SPINNER_METRIC event=rejected status=#{status} id=#{@id}"
|
|
284
|
+
writer = ResponseWriter.new
|
|
285
|
+
respond(writer.prepare([status, {"content-type" => "text/plain"}, [ResponseWriter.reason(status)]]))
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def respond(chunks)
|
|
289
|
+
@chunks = chunks
|
|
290
|
+
@chunk_index = 0
|
|
291
|
+
@chunk_offset = 0
|
|
292
|
+
@state = :writing
|
|
293
|
+
@wait_direction = :write
|
|
294
|
+
@deadline = Transport.now + @write_timeout
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def write_step
|
|
298
|
+
while @chunk_index < @chunks.length
|
|
299
|
+
chunk = @chunks[@chunk_index]
|
|
300
|
+
if @chunk_offset == chunk.bytesize
|
|
301
|
+
@chunk_index += 1
|
|
302
|
+
@chunk_offset = 0
|
|
303
|
+
next
|
|
304
|
+
end
|
|
305
|
+
# The binary local selects Spinel's length-aware socket write path.
|
|
306
|
+
binary = chunk.byteslice(@chunk_offset, chunk.bytesize - @chunk_offset).to_s.b
|
|
307
|
+
count = @socket.write_nonblock(binary)
|
|
308
|
+
@wait_direction = :write
|
|
309
|
+
raise IOError, "invalid write progress" if count <= 0 || count > binary.bytesize
|
|
310
|
+
@chunk_offset += count
|
|
311
|
+
return
|
|
312
|
+
end
|
|
313
|
+
if @state == :interim
|
|
314
|
+
@chunks = []
|
|
315
|
+
@state = :body
|
|
316
|
+
@wait_direction = :read
|
|
317
|
+
@deadline = @body_deadline
|
|
318
|
+
else
|
|
319
|
+
close
|
|
320
|
+
end
|
|
321
|
+
rescue IO::WaitWritable
|
|
322
|
+
@wait_direction = :write
|
|
323
|
+
rescue IO::WaitReadable
|
|
324
|
+
@wait_direction = :read
|
|
325
|
+
rescue EOFError, IOError, SystemCallError => error
|
|
326
|
+
warn "Spinner transport: #{error.message}"
|
|
327
|
+
close
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def expire(now)
|
|
331
|
+
return unless reading? || writing? || @storage_pending
|
|
332
|
+
return if now < @deadline
|
|
333
|
+
if @storage_pending
|
|
334
|
+
close
|
|
335
|
+
elsif reading?
|
|
336
|
+
@state == :handshake || @idle ? close : reject(408)
|
|
337
|
+
else
|
|
338
|
+
close
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def close
|
|
343
|
+
@close_lock.synchronize do
|
|
344
|
+
return if @state == :closed
|
|
345
|
+
@state = :closed
|
|
346
|
+
begin
|
|
347
|
+
@socket.close unless @socket.closed?
|
|
348
|
+
ensure
|
|
349
|
+
cleanup_body
|
|
350
|
+
@capacity.release
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
class Reactor
|
|
358
|
+
def initialize(listener, application, port, header_limit, read_timeout, body_timeout, write_timeout,
|
|
359
|
+
threads, queue_capacity, max_connections, queue_timeout, drain_timeout,
|
|
360
|
+
keepalive_timeout, max_requests, tls_context = nil, options = {})
|
|
361
|
+
@options = options
|
|
362
|
+
@options[:storage_pool] = TaskPool.new(options[:storage_workers], max_connections)
|
|
363
|
+
@options[:storage_budget] = StorageBudget.new(options[:spool_budget_bytes])
|
|
364
|
+
@listener = listener
|
|
365
|
+
@application = application
|
|
366
|
+
@port = port
|
|
367
|
+
@header_limit = header_limit
|
|
368
|
+
@read_timeout = read_timeout
|
|
369
|
+
@body_timeout = body_timeout
|
|
370
|
+
@write_timeout = write_timeout
|
|
371
|
+
@clients = []
|
|
372
|
+
@queued = []
|
|
373
|
+
@capacity = Capacity.new(max_connections)
|
|
374
|
+
@queue_timeout = queue_timeout
|
|
375
|
+
@drain_timeout = drain_timeout
|
|
376
|
+
@keepalive_timeout = keepalive_timeout
|
|
377
|
+
@max_requests = max_requests
|
|
378
|
+
@tls_context = tls_context
|
|
379
|
+
@reactor_heartbeat = options[:reactor_heartbeat]
|
|
380
|
+
@shutdown_requested = false
|
|
381
|
+
@graceful_shutdown = false
|
|
382
|
+
@force_shutdown_requested = false
|
|
383
|
+
@draining = false
|
|
384
|
+
@next_id = 0
|
|
385
|
+
@accept_backoff_until = 0.0
|
|
386
|
+
@reserve_fd = File.open("/dev/null", "r")
|
|
387
|
+
@reserve_fd.close # Initialize Spinel’s shared closed-file sentinel before descriptors can exhaust.
|
|
388
|
+
@reserve_fd = File.open("/dev/null", "r")
|
|
389
|
+
@reserve_reopen_count = nil
|
|
390
|
+
@last_heartbeat = Transport.now
|
|
391
|
+
@pool = ThreadPool.new(application, threads, queue_capacity, header_limit,
|
|
392
|
+
read_timeout, write_timeout, body_timeout, port, @capacity, @options)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def shutdown!(graceful = false)
|
|
396
|
+
@graceful_shutdown = true if graceful
|
|
397
|
+
@force_shutdown_requested = true unless graceful
|
|
398
|
+
@shutdown_requested = true
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def run
|
|
402
|
+
loop do
|
|
403
|
+
now = Transport.now
|
|
404
|
+
if @reactor_heartbeat && now - @last_heartbeat >= 1.0
|
|
405
|
+
warn "SPINNER_METRIC event=reactor pid=#{Process.pid} connections=#{@capacity.count} clients=#{@clients.length} queued=#{@queued.length} app_active=#{@pool.active_count}"
|
|
406
|
+
@last_heartbeat = now
|
|
407
|
+
end
|
|
408
|
+
if @reserve_fd.nil? && !@reserve_reopen_count.nil? && @capacity.count < @reserve_reopen_count
|
|
409
|
+
begin
|
|
410
|
+
@reserve_fd = File.open("/dev/null", "r")
|
|
411
|
+
@reserve_reopen_count = nil
|
|
412
|
+
rescue SystemCallError, IOError
|
|
413
|
+
nil
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
parent_pid = ENV["SPINNER_PARENT_PID"]
|
|
417
|
+
@shutdown_requested = true if !parent_pid.nil? && Process.ppid != parent_pid.to_i
|
|
418
|
+
if @shutdown_requested && !@draining
|
|
419
|
+
@draining = true
|
|
420
|
+
@drain_deadline = now + @drain_timeout
|
|
421
|
+
@listener.close unless @listener.closed?
|
|
422
|
+
if @graceful_shutdown
|
|
423
|
+
@clients.each { |client| client.close if client.idle? }
|
|
424
|
+
@clients.reject!(&:closed?)
|
|
425
|
+
else
|
|
426
|
+
@clients.each(&:close)
|
|
427
|
+
@clients.clear
|
|
428
|
+
@pool.request_stop(@drain_deadline)
|
|
429
|
+
end
|
|
430
|
+
warn "SPINNER_METRIC event=shutdown connections=#{@capacity.count} queued=#{@pool.queued_count} active=#{@pool.active_count} streams=#{@pool.stream_count} upgrades=#{@pool.upgrade_count} pid=#{Process.pid}"
|
|
431
|
+
end
|
|
432
|
+
if @draining && @graceful_shutdown && @force_shutdown_requested
|
|
433
|
+
@graceful_shutdown = false
|
|
434
|
+
@clients.each(&:close)
|
|
435
|
+
@clients.clear
|
|
436
|
+
@pool.request_stop(@drain_deadline)
|
|
437
|
+
end
|
|
438
|
+
break if @draining && (@capacity.count == 0 || now >= @drain_deadline)
|
|
439
|
+
@clients.each do |client|
|
|
440
|
+
client.storage_step
|
|
441
|
+
client.expire(now)
|
|
442
|
+
queue_client(client) if client.ready? && !client.storage_pending?
|
|
443
|
+
end
|
|
444
|
+
@clients.reject!(&:closed?)
|
|
445
|
+
@queued.reject! do |client|
|
|
446
|
+
if @pool.queued?(client)
|
|
447
|
+
if !@draining && now >= client.deadline && @pool.cancel(client)
|
|
448
|
+
client.reject(503)
|
|
449
|
+
@clients << client
|
|
450
|
+
true
|
|
451
|
+
else
|
|
452
|
+
false
|
|
453
|
+
end
|
|
454
|
+
else
|
|
455
|
+
true
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
@pool.expire_async
|
|
459
|
+
@pool.maintain(@application, @header_limit, @read_timeout,
|
|
460
|
+
@write_timeout, @body_timeout, @port) unless @draining
|
|
461
|
+
@pool.completed.each do |client|
|
|
462
|
+
if @draining || client.closed?
|
|
463
|
+
client.close
|
|
464
|
+
else
|
|
465
|
+
begin
|
|
466
|
+
client.next_request!
|
|
467
|
+
@clients << client
|
|
468
|
+
queue_client(client) if client.ready?
|
|
469
|
+
rescue RequestError => error
|
|
470
|
+
client.reject(error.status)
|
|
471
|
+
@clients << client
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
@clients.each do |client|
|
|
476
|
+
read_client(client) if !client.closed? && client.buffered_tls_data?
|
|
477
|
+
end
|
|
478
|
+
reads = []
|
|
479
|
+
reads << @listener unless @draining || @capacity.full? || now < @accept_backoff_until
|
|
480
|
+
writes = []
|
|
481
|
+
deadline = nil
|
|
482
|
+
@clients.each do |client|
|
|
483
|
+
reads << client.socket.to_io if client.wait_readable?
|
|
484
|
+
writes << client.socket.to_io if client.wait_writable?
|
|
485
|
+
if client.reading? || client.writing?
|
|
486
|
+
deadline = client.deadline if deadline.nil? || client.deadline < deadline
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
@queued.each do |client|
|
|
490
|
+
next if @draining
|
|
491
|
+
deadline = client.deadline if deadline.nil? || client.deadline < deadline
|
|
492
|
+
end
|
|
493
|
+
timeout = deadline.nil? ? 0.05 : [[deadline - Transport.now, 0.0].max, 0.05].min
|
|
494
|
+
timeout = [timeout, 0.001].min if @clients.any?(&:storage_pending?)
|
|
495
|
+
selected = IO.select(reads, writes, nil, timeout)
|
|
496
|
+
next if selected.nil?
|
|
497
|
+
selected[0].each do |socket|
|
|
498
|
+
if socket == @listener
|
|
499
|
+
accept_ready
|
|
500
|
+
else
|
|
501
|
+
client = @clients.find { |candidate| candidate.socket.to_io == socket }
|
|
502
|
+
read_client(client) unless client.nil?
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
selected[1].each do |socket|
|
|
506
|
+
client = @clients.find { |candidate| candidate.socket.to_io == socket }
|
|
507
|
+
read_client(client) unless client.nil? || client.closed?
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
ensure
|
|
511
|
+
warn "SPINNER_METRIC event=shutdown_complete pid=#{Process.pid} remaining=#{@capacity.count} outcome=#{@capacity.count == 0 ? 'drained' : 'forced'}"
|
|
512
|
+
@clients.each(&:close)
|
|
513
|
+
@queued.each do |client|
|
|
514
|
+
client.close if @pool.cancel(client)
|
|
515
|
+
end
|
|
516
|
+
@pool.stop
|
|
517
|
+
@options[:storage_pool].stop
|
|
518
|
+
@reserve_fd.close unless @reserve_fd.nil? || @reserve_fd.closed?
|
|
519
|
+
@pool.completed.each(&:close)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def accept_ready
|
|
523
|
+
return if @capacity.full?
|
|
524
|
+
socket = @listener.accept_nonblock
|
|
525
|
+
@capacity.acquire
|
|
526
|
+
@next_id += 1
|
|
527
|
+
begin
|
|
528
|
+
@clients << PendingClient.new(socket, @port, @header_limit, @read_timeout,
|
|
529
|
+
@body_timeout, @write_timeout,
|
|
530
|
+
@capacity, @queue_timeout, @next_id,
|
|
531
|
+
@keepalive_timeout, @max_requests,
|
|
532
|
+
@tls_context, @options)
|
|
533
|
+
rescue StandardError
|
|
534
|
+
socket.close unless socket.closed?
|
|
535
|
+
@capacity.release
|
|
536
|
+
raise
|
|
537
|
+
end
|
|
538
|
+
warn "SPINNER_METRIC event=accept id=#{@next_id} connections=#{@capacity.count} pid=#{Process.pid}"
|
|
539
|
+
rescue IO::WaitReadable
|
|
540
|
+
nil
|
|
541
|
+
rescue Errno::EMFILE, Errno::ENFILE, Errno::ECONNABORTED => error
|
|
542
|
+
warn "Spinner accept: #{error.message}"
|
|
543
|
+
unless @reserve_fd.nil?
|
|
544
|
+
@reserve_fd.close
|
|
545
|
+
@reserve_fd = nil
|
|
546
|
+
@reserve_reopen_count = @capacity.count
|
|
547
|
+
end
|
|
548
|
+
@accept_backoff_until = Transport.now + 0.1
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def read_client(client)
|
|
552
|
+
return if client.closed?
|
|
553
|
+
client.io_step
|
|
554
|
+
return unless client.ready? && !client.storage_pending?
|
|
555
|
+
queue_client(client)
|
|
556
|
+
rescue RequestError => error
|
|
557
|
+
client.reject(error.status)
|
|
558
|
+
rescue IOError, SystemCallError => error
|
|
559
|
+
warn "Spinner transport: #{error.message}"
|
|
560
|
+
client.close
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def queue_client(client)
|
|
564
|
+
if @pool.enqueue(client)
|
|
565
|
+
@clients.delete(client)
|
|
566
|
+
@queued << client
|
|
567
|
+
if @options[:request_metrics]
|
|
568
|
+
warn "SPINNER_METRIC event=queued id=#{client.id} queued=#{@pool.queued_count} connections=#{@capacity.count}"
|
|
569
|
+
end
|
|
570
|
+
else
|
|
571
|
+
client.reject(503)
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
end
|