quicsilver 0.4.0 → 0.5.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 +4 -4
- data/.github/workflows/ci.yml +9 -1
- data/.github/workflows/cibuildgem.yaml +29 -5
- data/.gitignore +5 -0
- data/CHANGELOG.md +50 -0
- data/Gemfile.lock +8 -1
- data/README.md +128 -15
- data/Rakefile +10 -3
- data/benchmarks/baseline.rb +175 -0
- data/benchmarks/compare.rb +398 -0
- data/benchmarks/helpers.rb +138 -64
- data/benchmarks/latency.rb +128 -0
- data/benchmarks/loss.rb +193 -0
- data/benchmarks/mobile.rb +125 -0
- data/docs/connection_identity.md +141 -0
- data/examples/benchmark.rb +80 -0
- data/examples/connection_pool_demo.rb +1 -1
- data/examples/example_helper.rb +51 -0
- data/examples/feature_demo.rb +7 -7
- data/examples/grpc_style.rb +2 -2
- data/examples/latency_aware_routing.rb +382 -0
- data/examples/lifecycle_drain.rb +264 -0
- data/examples/priorities.rb +2 -2
- data/examples/rails_feature_test.rb +92 -72
- data/examples/server_stats.rb +117 -0
- data/examples/simple_client_test.rb +3 -3
- data/examples/trailers.rb +23 -23
- data/examples/webtransport_smoke.rb +546 -0
- data/examples/worker_load_balancing.rb +425 -0
- data/ext/quicsilver/extconf.rb +100 -15
- data/ext/quicsilver/include/msquic.h +2226 -0
- data/ext/quicsilver/include/msquic_posix.h +696 -0
- data/ext/quicsilver/include/quic_sal_stub.h +314 -0
- data/ext/quicsilver/msquic_patch.rb +68 -0
- data/ext/quicsilver/patches/reliable-reset.patch +723 -0
- data/ext/quicsilver/patches/stream-final-size.patch +47 -0
- data/ext/quicsilver/quicsilver.c +1640 -222
- data/lib/quicsilver/client/client.rb +419 -60
- data/lib/quicsilver/client/connection_pool.rb +122 -26
- data/lib/quicsilver/client/request.rb +106 -14
- data/lib/quicsilver/errors.rb +39 -0
- data/lib/quicsilver/libmsquic.2.dylib +0 -0
- data/lib/quicsilver/protocol/adapter.rb +41 -21
- data/lib/quicsilver/protocol/capsule.rb +38 -0
- data/lib/quicsilver/protocol/connect_stream_decoder.rb +75 -0
- data/lib/quicsilver/protocol/control_stream_parser.rb +12 -1
- data/lib/quicsilver/protocol/datagram.rb +40 -0
- data/lib/quicsilver/protocol/frame_parser.rb +5 -6
- data/lib/quicsilver/protocol/frame_reader.rb +31 -2
- data/lib/quicsilver/protocol/frames.rb +128 -12
- data/lib/quicsilver/protocol/priority.rb +9 -0
- data/lib/quicsilver/protocol/qpack/decoder.rb +4 -42
- data/lib/quicsilver/protocol/qpack/encoder.rb +10 -20
- data/lib/quicsilver/protocol/qpack/huffman.rb +4 -4
- data/lib/quicsilver/protocol/request_encoder.rb +6 -3
- data/lib/quicsilver/protocol/request_parser.rb +16 -2
- data/lib/quicsilver/protocol/response_encoder.rb +18 -8
- data/lib/quicsilver/protocol/response_parser.rb +6 -0
- data/lib/quicsilver/protocol/stream_input.rb +3 -3
- data/lib/quicsilver/protocol/unidirectional_stream.rb +12 -0
- data/lib/quicsilver/protocol/web_transport.rb +214 -0
- data/lib/quicsilver/rack/context.rb +33 -0
- data/lib/quicsilver/response.rb +40 -0
- data/lib/quicsilver/server/rack_adapter.rb +77 -0
- data/lib/quicsilver/server/receive_queue.rb +106 -0
- data/lib/quicsilver/server/request_handler.rb +24 -13
- data/lib/quicsilver/server/request_registry.rb +18 -5
- data/lib/quicsilver/server/scheduler.rb +50 -0
- data/lib/quicsilver/server/schedulers/fiber_scheduler.rb +79 -0
- data/lib/quicsilver/server/schedulers/thread_scheduler.rb +76 -0
- data/lib/quicsilver/server/server.rb +655 -185
- data/lib/quicsilver/server/web_transport_flow_control.rb +270 -0
- data/lib/quicsilver/server/web_transport_manager.rb +281 -0
- data/lib/quicsilver/server/web_transport_registry.rb +40 -0
- data/lib/quicsilver/server/web_transport_session.rb +812 -0
- data/lib/quicsilver/server/web_transport_stream.rb +326 -0
- data/lib/quicsilver/transport/configuration.rb +103 -10
- data/lib/quicsilver/transport/connection.rb +302 -87
- data/lib/quicsilver/transport/connection_stats.rb +53 -0
- data/lib/quicsilver/transport/event_loop.rb +36 -0
- data/lib/quicsilver/transport/inbound_stream.rb +21 -1
- data/lib/quicsilver/transport/stream.rb +18 -3
- data/lib/quicsilver/transport/stream_event.rb +25 -2
- data/lib/quicsilver/transport/stream_id.rb +26 -0
- data/lib/quicsilver/version.rb +1 -1
- data/lib/quicsilver.rb +22 -11
- data/lib/rackup/handler/quicsilver.rb +57 -38
- data/quicsilver.gemspec +10 -3
- metadata +54 -8
- data/benchmarks/concurrent.rb +0 -110
- data/benchmarks/quicsilver_server.rb +0 -46
- data/benchmarks/rails.rb +0 -170
- data/benchmarks/throughput.rb +0 -113
- data/lib/quicsilver/quicsilver.bundle +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../scheduler"
|
|
4
|
+
|
|
5
|
+
module Quicsilver
|
|
6
|
+
class Server
|
|
7
|
+
module Schedulers
|
|
8
|
+
# Fiber-based scheduler — uses Async::Queue for cooperative scheduling.
|
|
9
|
+
# Expects to be running inside an Async reactor (e.g. Falcon provides one).
|
|
10
|
+
# Requires the async gem.
|
|
11
|
+
class FiberScheduler < Scheduler
|
|
12
|
+
def initialize(concurrency:, max_queue_size:, &handler)
|
|
13
|
+
require "async"
|
|
14
|
+
require "async/queue"
|
|
15
|
+
require "async/barrier"
|
|
16
|
+
|
|
17
|
+
@concurrency = concurrency
|
|
18
|
+
@max_queue_size = max_queue_size
|
|
19
|
+
@handler = handler
|
|
20
|
+
@queue = Async::Queue.new
|
|
21
|
+
@running = false
|
|
22
|
+
@barrier = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def enqueue(work)
|
|
26
|
+
@queue.enqueue(work)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def full?
|
|
30
|
+
@queue.size >= @max_queue_size
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def pending
|
|
34
|
+
@queue.size
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Spawn fiber workers inside the current Async reactor.
|
|
38
|
+
# Caller must already be inside Async do (e.g. Falcon).
|
|
39
|
+
def start
|
|
40
|
+
@running = true
|
|
41
|
+
@barrier = Async::Barrier.new
|
|
42
|
+
|
|
43
|
+
@concurrency.times do
|
|
44
|
+
@barrier.async do
|
|
45
|
+
while @running
|
|
46
|
+
work = @queue.dequeue
|
|
47
|
+
break if work == :shutdown
|
|
48
|
+
|
|
49
|
+
run(work)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# One unit of work must not take the worker fiber down with it; see
|
|
56
|
+
# the thread scheduler for the same reasoning.
|
|
57
|
+
private def run(work)
|
|
58
|
+
@handler.call(work)
|
|
59
|
+
rescue => e
|
|
60
|
+
Quicsilver.logger.error("Unhandled error in worker: #{e.class} - #{e.message}")
|
|
61
|
+
Quicsilver.logger.debug(e.backtrace&.first(5)&.join("\n").to_s)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def drain(timeout: 5)
|
|
65
|
+
deadline = Time.now + timeout
|
|
66
|
+
while @queue.size > 0 && Time.now < deadline
|
|
67
|
+
sleep 0.05
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def stop
|
|
72
|
+
@running = false
|
|
73
|
+
@concurrency.times { @queue.enqueue(:shutdown) }
|
|
74
|
+
@barrier&.wait
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../scheduler"
|
|
4
|
+
|
|
5
|
+
module Quicsilver
|
|
6
|
+
class Server
|
|
7
|
+
module Schedulers
|
|
8
|
+
# Thread-based scheduler — uses a thread pool with Thread::Queue.
|
|
9
|
+
class ThreadScheduler < Scheduler
|
|
10
|
+
def initialize(concurrency:, max_queue_size:, &handler)
|
|
11
|
+
@size = concurrency
|
|
12
|
+
@max_queue_size = max_queue_size
|
|
13
|
+
@handler = handler
|
|
14
|
+
@queue = Queue.new
|
|
15
|
+
@threads = []
|
|
16
|
+
@mutex = Mutex.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def enqueue(work)
|
|
20
|
+
@queue.push(work)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def full?
|
|
24
|
+
@queue.size >= @max_queue_size
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def pending
|
|
28
|
+
@queue.size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def start
|
|
32
|
+
@size.times do
|
|
33
|
+
thread = Thread.new do
|
|
34
|
+
while (work = @queue.pop)
|
|
35
|
+
break if work == :shutdown
|
|
36
|
+
|
|
37
|
+
run(work)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
@mutex.synchronize { @threads << thread }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def drain(timeout: 5)
|
|
45
|
+
deadline = Time.now + timeout
|
|
46
|
+
while @queue.size > 0 && Time.now < deadline
|
|
47
|
+
sleep 0.05
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# One unit of work must not take the worker down with it. An escaping
|
|
52
|
+
# error would end the thread, shrinking the pool for the rest of the
|
|
53
|
+
# process, and Thread#join would re-raise it in whoever stops the
|
|
54
|
+
# server. DrainTimeoutError is our own interruption, so it ends the
|
|
55
|
+
# worker as intended.
|
|
56
|
+
private def run(work)
|
|
57
|
+
@handler.call(work)
|
|
58
|
+
rescue DrainTimeoutError
|
|
59
|
+
raise
|
|
60
|
+
rescue => e
|
|
61
|
+
Quicsilver.logger.error("Unhandled error in worker: #{e.class} - #{e.message}")
|
|
62
|
+
Quicsilver.logger.debug(e.backtrace&.first(5)&.join("\n").to_s)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def stop
|
|
66
|
+
@size.times { @queue.push(:shutdown) }
|
|
67
|
+
@mutex.synchronize do
|
|
68
|
+
@threads.each { |t| t.join(2) }
|
|
69
|
+
@threads.each { |t| t.raise(DrainTimeoutError, "drain timeout") if t.alive? }
|
|
70
|
+
@threads.clear
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|