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,117 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Demonstrates Quicsilver::Server#stats.
|
|
5
|
+
#
|
|
6
|
+
# ruby examples/server_stats.rb
|
|
7
|
+
# PORT=4444 ruby examples/server_stats.rb
|
|
8
|
+
#
|
|
9
|
+
# The stats snapshot combines:
|
|
10
|
+
# - Quicsilver app-server state: running, connections, active requests, queue.
|
|
11
|
+
# - QUIC transport counters: process-wide connection/stream/packet/worker state.
|
|
12
|
+
|
|
13
|
+
require_relative "example_helper"
|
|
14
|
+
require "json"
|
|
15
|
+
|
|
16
|
+
HOST = ENV.fetch("HOST", "localhost")
|
|
17
|
+
PORT = Integer(ENV.fetch("PORT", "4433"))
|
|
18
|
+
|
|
19
|
+
server = nil
|
|
20
|
+
server_thread = nil
|
|
21
|
+
clients = []
|
|
22
|
+
|
|
23
|
+
begin
|
|
24
|
+
app = ->(env) {
|
|
25
|
+
case env["PATH_INFO"]
|
|
26
|
+
when "/hello"
|
|
27
|
+
[200, { "content-type" => "text/plain" }, ["hello\n"]]
|
|
28
|
+
when %r{\A/slow}
|
|
29
|
+
sleep 0.25
|
|
30
|
+
[200, { "content-type" => "text/plain" }, ["slow response\n"]]
|
|
31
|
+
else
|
|
32
|
+
[404, { "content-type" => "text/plain" }, ["not found\n"]]
|
|
33
|
+
end
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
server = Quicsilver::Server.new(PORT, app: app, server_configuration: EXAMPLE_TLS_CONFIG)
|
|
37
|
+
server_thread = Thread.new { server.start }
|
|
38
|
+
|
|
39
|
+
# Keep examples self-contained without requiring test helpers.
|
|
40
|
+
def wait_until(timeout: 3)
|
|
41
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
42
|
+
until yield
|
|
43
|
+
raise "timed out waiting" if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
44
|
+
|
|
45
|
+
sleep 0.01
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
wait_until { server.running? }
|
|
50
|
+
|
|
51
|
+
# Print the useful subset first, then include the whole transport hash so it is
|
|
52
|
+
# easy to see every counter name currently exposed by Quicsilver.transport_counters.
|
|
53
|
+
def print_stats(title, server)
|
|
54
|
+
stats = server.stats
|
|
55
|
+
transport = stats["transport"] || {}
|
|
56
|
+
|
|
57
|
+
puts "\n#{title}"
|
|
58
|
+
puts "-" * title.length
|
|
59
|
+
puts JSON.pretty_generate(
|
|
60
|
+
"server" => {
|
|
61
|
+
"running" => stats["running"],
|
|
62
|
+
"shutting_down" => stats["shutting_down"],
|
|
63
|
+
"connections" => stats["connections"],
|
|
64
|
+
"requests" => stats["requests"],
|
|
65
|
+
"scheduler" => stats["scheduler"]
|
|
66
|
+
},
|
|
67
|
+
"transport_summary" => {
|
|
68
|
+
"connections_active" => transport["connections_active"],
|
|
69
|
+
"connections_connected" => transport["connections_connected"],
|
|
70
|
+
"streams_active" => transport["streams_active"],
|
|
71
|
+
"connection_queue_depth" => transport["connection_queue_depth"],
|
|
72
|
+
"connection_operations_queue_depth" => transport["connection_operations_queue_depth"],
|
|
73
|
+
"worker_operations_queue_depth" => transport["worker_operations_queue_depth"],
|
|
74
|
+
"connections_load_rejected" => transport["connections_load_rejected"],
|
|
75
|
+
"packets_dropped" => transport["packets_dropped"],
|
|
76
|
+
"app_bytes_received" => transport["app_bytes_received"],
|
|
77
|
+
"app_bytes_sent" => transport["app_bytes_sent"]
|
|
78
|
+
},
|
|
79
|
+
"transport" => transport
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
puts "Quicsilver Server Stats Demo"
|
|
84
|
+
puts "Listening on https://#{HOST}:#{PORT}"
|
|
85
|
+
puts "Note: transport counters are process-wide, so they include this example's clients too."
|
|
86
|
+
|
|
87
|
+
print_stats("1. Fresh server", server)
|
|
88
|
+
|
|
89
|
+
clients = 2.times.map { Quicsilver::Client.new(HOST, PORT, unsecure: true) }
|
|
90
|
+
clients.each_with_index do |client, index|
|
|
91
|
+
response = client.get("/hello")
|
|
92
|
+
puts "client #{index + 1}: GET /hello -> #{response.status}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
wait_until { server.stats.dig("connections", "active") >= 2 }
|
|
96
|
+
print_stats("2. After two client connections", server)
|
|
97
|
+
|
|
98
|
+
slow_requests = clients.each_with_index.map do |client, index|
|
|
99
|
+
Thread.new do
|
|
100
|
+
response = client.get("/slow/#{index + 1}")
|
|
101
|
+
puts "client #{index + 1}: GET /slow/#{index + 1} -> #{response.status}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
wait_until { server.stats.dig("requests", "active") >= 2 }
|
|
106
|
+
print_stats("3. While slow requests are in flight", server)
|
|
107
|
+
|
|
108
|
+
slow_requests.each(&:join)
|
|
109
|
+
print_stats("4. After slow requests complete", server)
|
|
110
|
+
|
|
111
|
+
puts "\nDone."
|
|
112
|
+
ensure
|
|
113
|
+
clients.each { |client| client.disconnect rescue nil }
|
|
114
|
+
Quicsilver::Client.close_pool rescue nil
|
|
115
|
+
server&.stop rescue nil
|
|
116
|
+
server_thread&.join(2)
|
|
117
|
+
end
|
|
@@ -13,9 +13,9 @@ begin
|
|
|
13
13
|
|
|
14
14
|
response = client.get("/")
|
|
15
15
|
|
|
16
|
-
puts "Status: #{response
|
|
17
|
-
puts "Headers: #{response
|
|
18
|
-
puts "Body: #{response
|
|
16
|
+
puts "Status: #{response.status}"
|
|
17
|
+
puts "Headers: #{response.headers.inspect}"
|
|
18
|
+
puts "Body: #{response.body}"
|
|
19
19
|
|
|
20
20
|
rescue => e
|
|
21
21
|
puts "ā Error: #{e.class} - #{e.message}"
|
data/examples/trailers.rb
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
# ruby examples/trailers.rb
|
|
9
9
|
|
|
10
10
|
require_relative "example_helper"
|
|
11
|
+
require "digest"
|
|
11
12
|
|
|
12
13
|
PORT = 4433
|
|
13
14
|
HOST = "localhost"
|
|
@@ -17,26 +18,21 @@ app = ->(env) {
|
|
|
17
18
|
|
|
18
19
|
case path
|
|
19
20
|
when "/stream-with-checksum"
|
|
20
|
-
# Stream data, then send a checksum trailer
|
|
21
21
|
body = ["chunk1\n", "chunk2\n", "chunk3\n"]
|
|
22
22
|
checksum = Digest::SHA256.hexdigest(body.join)[0..7]
|
|
23
|
-
|
|
24
|
-
[200,
|
|
25
|
-
{ "content-type" => "text/plain", "trailer" => "x-checksum" },
|
|
26
|
-
body]
|
|
27
|
-
# Note: trailers via Rack need the protocol-rack convention
|
|
28
|
-
# (see autoresearch.ideas.md for the Samuel Williams discussion)
|
|
23
|
+
env["rack.trailers"] = { "x-checksum" => checksum }
|
|
24
|
+
[200, { "content-type" => "text/plain" }, body]
|
|
29
25
|
|
|
30
26
|
when "/grpc-style"
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
env["rack.trailers"] = { "grpc-status" => "0", "grpc-message" => "OK" }
|
|
28
|
+
[200, { "content-type" => "application/grpc" }, ['{"result":"processed"}']]
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
when "/grpc-error"
|
|
31
|
+
env["rack.trailers"] = { "grpc-status" => "13", "grpc-message" => "INTERNAL" }
|
|
32
|
+
[200, { "content-type" => "application/grpc" }, ['{"error":"something broke"}']]
|
|
37
33
|
|
|
38
34
|
else
|
|
39
|
-
[200, { "content-type" => "text/plain" }, ["Try /stream-with-checksum or /grpc-
|
|
35
|
+
[200, { "content-type" => "text/plain" }, ["Try /stream-with-checksum, /grpc-style, or /grpc-error"]]
|
|
40
36
|
end
|
|
41
37
|
}
|
|
42
38
|
|
|
@@ -44,26 +40,30 @@ server = Quicsilver::Server.new(PORT, app: app, server_configuration: EXAMPLE_TL
|
|
|
44
40
|
server_thread = Thread.new { server.start }
|
|
45
41
|
sleep 0.3
|
|
46
42
|
|
|
47
|
-
puts "
|
|
43
|
+
puts "HTTP/3 Trailers Demo"
|
|
48
44
|
puts "=" * 50
|
|
49
45
|
|
|
50
46
|
client = Quicsilver::Client.new(HOST, PORT, unsecure: true)
|
|
51
47
|
|
|
52
48
|
puts "\n Streaming with checksum trailer:"
|
|
53
49
|
response = client.get("/stream-with-checksum")
|
|
54
|
-
puts " Status:
|
|
55
|
-
puts " Body:
|
|
50
|
+
puts " Status: #{response.status}"
|
|
51
|
+
puts " Body: #{response.body.inspect}"
|
|
52
|
+
puts " Trailers: #{response.trailers}"
|
|
56
53
|
|
|
57
|
-
puts "\n gRPC-style
|
|
54
|
+
puts "\n gRPC-style success:"
|
|
58
55
|
response = client.get("/grpc-style")
|
|
59
|
-
puts " Status:
|
|
60
|
-
puts " Body:
|
|
56
|
+
puts " Status: #{response.status}"
|
|
57
|
+
puts " Body: #{response.body}"
|
|
58
|
+
puts " Trailers: #{response.trailers}"
|
|
61
59
|
|
|
62
|
-
puts "\n
|
|
63
|
-
|
|
64
|
-
puts "
|
|
60
|
+
puts "\n gRPC-style error:"
|
|
61
|
+
response = client.get("/grpc-error")
|
|
62
|
+
puts " Status: #{response.status}"
|
|
63
|
+
puts " Body: #{response.body}"
|
|
64
|
+
puts " Trailers: #{response.trailers}"
|
|
65
65
|
|
|
66
66
|
client.disconnect
|
|
67
67
|
server.stop
|
|
68
68
|
server_thread.join(2)
|
|
69
|
-
puts "\
|
|
69
|
+
puts "\nDone"
|