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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -1
  3. data/.github/workflows/cibuildgem.yaml +29 -5
  4. data/.gitignore +5 -0
  5. data/CHANGELOG.md +50 -0
  6. data/Gemfile.lock +8 -1
  7. data/README.md +128 -15
  8. data/Rakefile +10 -3
  9. data/benchmarks/baseline.rb +175 -0
  10. data/benchmarks/compare.rb +398 -0
  11. data/benchmarks/helpers.rb +138 -64
  12. data/benchmarks/latency.rb +128 -0
  13. data/benchmarks/loss.rb +193 -0
  14. data/benchmarks/mobile.rb +125 -0
  15. data/docs/connection_identity.md +141 -0
  16. data/examples/benchmark.rb +80 -0
  17. data/examples/connection_pool_demo.rb +1 -1
  18. data/examples/example_helper.rb +51 -0
  19. data/examples/feature_demo.rb +7 -7
  20. data/examples/grpc_style.rb +2 -2
  21. data/examples/latency_aware_routing.rb +382 -0
  22. data/examples/lifecycle_drain.rb +264 -0
  23. data/examples/priorities.rb +2 -2
  24. data/examples/rails_feature_test.rb +92 -72
  25. data/examples/server_stats.rb +117 -0
  26. data/examples/simple_client_test.rb +3 -3
  27. data/examples/trailers.rb +23 -23
  28. data/examples/webtransport_smoke.rb +546 -0
  29. data/examples/worker_load_balancing.rb +425 -0
  30. data/ext/quicsilver/extconf.rb +100 -15
  31. data/ext/quicsilver/include/msquic.h +2226 -0
  32. data/ext/quicsilver/include/msquic_posix.h +696 -0
  33. data/ext/quicsilver/include/quic_sal_stub.h +314 -0
  34. data/ext/quicsilver/msquic_patch.rb +68 -0
  35. data/ext/quicsilver/patches/reliable-reset.patch +723 -0
  36. data/ext/quicsilver/patches/stream-final-size.patch +47 -0
  37. data/ext/quicsilver/quicsilver.c +1640 -222
  38. data/lib/quicsilver/client/client.rb +419 -60
  39. data/lib/quicsilver/client/connection_pool.rb +122 -26
  40. data/lib/quicsilver/client/request.rb +106 -14
  41. data/lib/quicsilver/errors.rb +39 -0
  42. data/lib/quicsilver/libmsquic.2.dylib +0 -0
  43. data/lib/quicsilver/protocol/adapter.rb +41 -21
  44. data/lib/quicsilver/protocol/capsule.rb +38 -0
  45. data/lib/quicsilver/protocol/connect_stream_decoder.rb +75 -0
  46. data/lib/quicsilver/protocol/control_stream_parser.rb +12 -1
  47. data/lib/quicsilver/protocol/datagram.rb +40 -0
  48. data/lib/quicsilver/protocol/frame_parser.rb +5 -6
  49. data/lib/quicsilver/protocol/frame_reader.rb +31 -2
  50. data/lib/quicsilver/protocol/frames.rb +128 -12
  51. data/lib/quicsilver/protocol/priority.rb +9 -0
  52. data/lib/quicsilver/protocol/qpack/decoder.rb +4 -42
  53. data/lib/quicsilver/protocol/qpack/encoder.rb +10 -20
  54. data/lib/quicsilver/protocol/qpack/huffman.rb +4 -4
  55. data/lib/quicsilver/protocol/request_encoder.rb +6 -3
  56. data/lib/quicsilver/protocol/request_parser.rb +16 -2
  57. data/lib/quicsilver/protocol/response_encoder.rb +18 -8
  58. data/lib/quicsilver/protocol/response_parser.rb +6 -0
  59. data/lib/quicsilver/protocol/stream_input.rb +3 -3
  60. data/lib/quicsilver/protocol/unidirectional_stream.rb +12 -0
  61. data/lib/quicsilver/protocol/web_transport.rb +214 -0
  62. data/lib/quicsilver/rack/context.rb +33 -0
  63. data/lib/quicsilver/response.rb +40 -0
  64. data/lib/quicsilver/server/rack_adapter.rb +77 -0
  65. data/lib/quicsilver/server/receive_queue.rb +106 -0
  66. data/lib/quicsilver/server/request_handler.rb +24 -13
  67. data/lib/quicsilver/server/request_registry.rb +18 -5
  68. data/lib/quicsilver/server/scheduler.rb +50 -0
  69. data/lib/quicsilver/server/schedulers/fiber_scheduler.rb +79 -0
  70. data/lib/quicsilver/server/schedulers/thread_scheduler.rb +76 -0
  71. data/lib/quicsilver/server/server.rb +655 -185
  72. data/lib/quicsilver/server/web_transport_flow_control.rb +270 -0
  73. data/lib/quicsilver/server/web_transport_manager.rb +281 -0
  74. data/lib/quicsilver/server/web_transport_registry.rb +40 -0
  75. data/lib/quicsilver/server/web_transport_session.rb +812 -0
  76. data/lib/quicsilver/server/web_transport_stream.rb +326 -0
  77. data/lib/quicsilver/transport/configuration.rb +103 -10
  78. data/lib/quicsilver/transport/connection.rb +302 -87
  79. data/lib/quicsilver/transport/connection_stats.rb +53 -0
  80. data/lib/quicsilver/transport/event_loop.rb +36 -0
  81. data/lib/quicsilver/transport/inbound_stream.rb +21 -1
  82. data/lib/quicsilver/transport/stream.rb +18 -3
  83. data/lib/quicsilver/transport/stream_event.rb +25 -2
  84. data/lib/quicsilver/transport/stream_id.rb +26 -0
  85. data/lib/quicsilver/version.rb +1 -1
  86. data/lib/quicsilver.rb +22 -11
  87. data/lib/rackup/handler/quicsilver.rb +57 -38
  88. data/quicsilver.gemspec +10 -3
  89. metadata +54 -8
  90. data/benchmarks/concurrent.rb +0 -110
  91. data/benchmarks/quicsilver_server.rb +0 -46
  92. data/benchmarks/rails.rb +0 -170
  93. data/benchmarks/throughput.rb +0 -113
  94. 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[:status]}"
17
- puts "Headers: #{response[:headers].inspect}"
18
- puts "Body: #{response[:body]}"
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
- # gRPC-style response: status comes in trailers, not headers
32
- body = ['{"result":"processed"}']
27
+ env["rack.trailers"] = { "grpc-status" => "0", "grpc-message" => "OK" }
28
+ [200, { "content-type" => "application/grpc" }, ['{"result":"processed"}']]
33
29
 
34
- [200,
35
- { "content-type" => "application/json" },
36
- body]
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-style"]]
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 "šŸ“Ž HTTP/3 Trailers Demo"
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: #{response[:status]}"
55
- puts " Body: #{response[:body].inspect}"
50
+ puts " Status: #{response.status}"
51
+ puts " Body: #{response.body.inspect}"
52
+ puts " Trailers: #{response.trailers}"
56
53
 
57
- puts "\n gRPC-style response:"
54
+ puts "\n gRPC-style success:"
58
55
  response = client.get("/grpc-style")
59
- puts " Status: #{response[:status]}"
60
- puts " Body: #{response[:body]}"
56
+ puts " Status: #{response.status}"
57
+ puts " Body: #{response.body}"
58
+ puts " Trailers: #{response.trailers}"
61
59
 
62
- puts "\n Trailer support is built into the protocol layer."
63
- puts " ResponseEncoder can send trailers after DATA frames."
64
- puts " Full Rack integration needs protocol-rack update."
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 "\nāœ… Done"
69
+ puts "\nDone"