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
@@ -14,7 +14,7 @@ module Quicsilver
14
14
  end
15
15
 
16
16
  def bidirectional?
17
- (stream_id & 0x02) == 0
17
+ StreamId.bidirectional?(stream_id)
18
18
  end
19
19
 
20
20
  def writable?
@@ -28,6 +28,26 @@ module Quicsilver
28
28
  def data
29
29
  @buffer.string
30
30
  end
31
+
32
+ def send(data, fin: false)
33
+ return unless writable?
34
+ Quicsilver.send_stream(@stream_handle, data, fin)
35
+ end
36
+
37
+ def reset(error_code = Protocol::H3_REQUEST_CANCELLED)
38
+ return unless writable?
39
+ Quicsilver.stream_reset(@stream_handle, error_code)
40
+ end
41
+
42
+ def stop_sending(error_code = Protocol::H3_REQUEST_CANCELLED)
43
+ return unless writable?
44
+ Quicsilver.stream_stop_sending(@stream_handle, error_code)
45
+ end
46
+
47
+ def abort(error_code = Protocol::H3_REQUEST_CANCELLED)
48
+ return unless writable?
49
+ Quicsilver.stream_abort(@stream_handle, error_code)
50
+ end
31
51
  end
32
52
  end
33
53
  end
@@ -2,9 +2,7 @@
2
2
 
3
3
  module Quicsilver
4
4
  module Transport
5
- # Wraps a QUIC stream opened by Ruby code (client requests, server control streams).
6
- # Encapsulates the C handle — callers use send/reset/stop_sending methods instead
7
- # of passing raw pointers to Quicsilver.send_stream etc.
5
+ # The handle is an opaque native registry token, not a pointer.
8
6
  class Stream
9
7
  attr_reader :handle
10
8
 
@@ -12,10 +10,23 @@ module Quicsilver
12
10
  @handle = handle
13
11
  end
14
12
 
13
+ # Returns nil until asynchronous stream startup completes, or after shutdown.
14
+ def stream_id
15
+ Quicsilver.get_stream_id(@handle)
16
+ end
17
+
15
18
  def send(data, fin: false)
16
19
  Quicsilver.send_stream(@handle, data, fin)
17
20
  end
18
21
 
22
+ def reliable_offset=(offset)
23
+ Quicsilver.set_stream_reliable_offset(@handle, offset)
24
+ end
25
+
26
+ def grant_receive_credit(bytes, chunks)
27
+ Quicsilver.grant_stream_receive_credit(@handle, bytes, chunks)
28
+ end
29
+
19
30
  def reset(error_code = Protocol::H3_REQUEST_CANCELLED)
20
31
  Quicsilver.stream_reset(@handle, error_code)
21
32
  end
@@ -23,6 +34,10 @@ module Quicsilver
23
34
  def stop_sending(error_code = Protocol::H3_REQUEST_CANCELLED)
24
35
  Quicsilver.stream_stop_sending(@handle, error_code)
25
36
  end
37
+
38
+ def abort(error_code = Protocol::H3_REQUEST_CANCELLED)
39
+ Quicsilver.stream_abort(@handle, error_code)
40
+ end
26
41
  end
27
42
  end
28
43
  end
@@ -5,22 +5,45 @@ module Quicsilver
5
5
  # Parses the binary data packed by the C extension for stream completion events.
6
6
  # C packs events as:
7
7
  # RECEIVE_FIN: [stream_handle(8)][payload...]
8
- # STREAM_RESET: [stream_handle(8)][error_code(8)]
8
+ # STREAM_RESET: [stream_handle(8)][error_code(8)][final_size(8)]
9
9
  # STOP_SENDING: [stream_handle(8)][error_code(8)]
10
10
  class StreamEvent
11
+ # A reset stream's final size is unknown to MsQuic until RESET_STREAM or
12
+ # FIN settles it; the C extension reports that as this sentinel so it
13
+ # never has to be confused with a real length.
14
+ FINAL_SIZE_UNKNOWN = (2**64) - 1
15
+
11
16
  attr_reader :handle, :data, :error_code
12
17
 
18
+ # The total number of bytes the peer sent on this stream, from the
19
+ # RESET_STREAM frame (RFC 9000 4.5). nil when it is not settled or the
20
+ # event does not carry one. WebTransport session flow control needs it to
21
+ # charge the bytes a reset discarded (draft-ietf-webtrans-http3-16 5.4).
22
+ attr_reader :final_size
23
+
13
24
  def initialize(raw_data, event_type)
14
25
  @handle = raw_data[0, 8].unpack1("Q")
15
26
  remaining = raw_data[8..] || "".b
16
27
 
17
28
  case event_type
18
- when "RECEIVE_FIN"
29
+ when "RECEIVE", "RECEIVE_FIN"
19
30
  @data = remaining
20
31
  when "STREAM_RESET", "STOP_SENDING"
21
32
  @error_code = remaining.unpack1("Q")
33
+ @final_size = parse_final_size(remaining)
22
34
  end
23
35
  end
36
+
37
+ private
38
+
39
+ # Older packings carry only the error code, so a missing final size is
40
+ # not an error.
41
+ def parse_final_size(remaining)
42
+ return unless remaining.bytesize >= 16
43
+
44
+ value = remaining.unpack1("@8Q")
45
+ value unless value == FINAL_SIZE_UNKNOWN
46
+ end
24
47
  end
25
48
  end
26
49
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Transport
5
+ module StreamId
6
+ INITIATOR_MASK = 0x01
7
+ DIRECTION_MASK = 0x02
8
+ REQUEST_STREAM_MASK = INITIATOR_MASK | DIRECTION_MASK
9
+
10
+ module_function
11
+
12
+ def bidirectional?(stream_id)
13
+ (stream_id & DIRECTION_MASK).zero?
14
+ end
15
+
16
+ def unidirectional?(stream_id)
17
+ !bidirectional?(stream_id)
18
+ end
19
+
20
+ # HTTP/3 requests run on client-initiated bidirectional QUIC streams.
21
+ def request?(stream_id)
22
+ (stream_id & REQUEST_STREAM_MASK).zero?
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Quicsilver
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/quicsilver.rb CHANGED
@@ -2,9 +2,15 @@
2
2
 
3
3
  require "logger"
4
4
  require_relative "quicsilver/version"
5
+ require_relative "quicsilver/errors"
6
+ require_relative "quicsilver/response"
5
7
 
6
8
  # Protocol layer
7
9
  require_relative "quicsilver/protocol/frames"
10
+ require_relative "quicsilver/protocol/datagram"
11
+ require_relative "quicsilver/protocol/capsule"
12
+ require_relative "quicsilver/protocol/web_transport"
13
+ require_relative "quicsilver/protocol/unidirectional_stream"
8
14
  require_relative "quicsilver/protocol/priority"
9
15
  require_relative "quicsilver/protocol/qpack/encoder"
10
16
  require_relative "quicsilver/protocol/request_parser"
@@ -17,18 +23,26 @@ require_relative "quicsilver/protocol/adapter"
17
23
  require_relative "quicsilver/protocol/control_stream_parser"
18
24
  require "protocol/rack"
19
25
 
26
+ # Rack integration
27
+ require_relative "quicsilver/rack/context"
28
+
20
29
  # Transport layer
30
+ require_relative "quicsilver/transport/stream_id"
21
31
  require_relative "quicsilver/transport/stream"
22
32
  require_relative "quicsilver/transport/stream_event"
23
33
  require_relative "quicsilver/transport/inbound_stream"
24
34
  require_relative "quicsilver/transport/event_loop"
25
35
  require_relative "quicsilver/transport/configuration"
26
36
  require_relative "quicsilver/transport/connection"
37
+ require_relative "quicsilver/transport/connection_stats"
27
38
 
28
39
  # Server
29
40
  require_relative "quicsilver/server/listener_data"
30
41
  require_relative "quicsilver/server/request_registry"
31
42
  require_relative "quicsilver/server/request_handler"
43
+ require_relative "quicsilver/server/rack_adapter"
44
+ require_relative "quicsilver/server/web_transport_flow_control"
45
+ require_relative "quicsilver/server/web_transport_manager"
32
46
  require_relative "quicsilver/server/server"
33
47
 
34
48
  # Client
@@ -37,29 +51,26 @@ require_relative "quicsilver/client/connection_pool"
37
51
  require_relative "quicsilver/client/client"
38
52
 
39
53
  # C extension
40
- # Load precompiled binary if available, fall back to native extension
41
54
  begin
42
55
  ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
43
- require_relative "quicsilver/#{ruby_version}/quicsilver"
56
+ require "quicsilver/#{ruby_version}/quicsilver"
44
57
  rescue LoadError
45
- require_relative "quicsilver/quicsilver"
58
+ require "quicsilver/quicsilver"
46
59
  end
47
60
 
48
61
  # Rackup handler
49
62
  require_relative "rackup/handler/quicsilver"
50
63
 
51
64
  module Quicsilver
52
- class Error < StandardError; end
53
- class ServerIsRunningError < Error; end
54
- class ServerConfigurationError < Error; end
55
- class ServerListenerError < Error; end
56
- class ServerError < Error; end
57
- class ConnectionError < Error; end
58
- class TimeoutError < Error; end
59
-
60
65
  class << self
61
66
  attr_writer :logger
62
67
 
68
+ def stream_abort(handle, error_code)
69
+ receiving_stopped = stream_stop_sending(handle, error_code)
70
+ sending_reset = stream_reset(handle, error_code)
71
+ receiving_stopped || sending_reset
72
+ end
73
+
63
74
  def logger
64
75
  @logger ||= default_logger
65
76
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rackup/handler"
4
- require "localhost"
5
4
 
6
5
  module Quicsilver
7
6
  module RackHandler
@@ -10,54 +9,74 @@ module Quicsilver
10
9
  Port: 4433,
11
10
  }
12
11
 
13
- def self.run(app, **options)
14
- normalized_options = {
15
- host: options[:Host] || options[:host] || DEFAULT_OPTIONS[:Host],
16
- port: (options[:Port] || options[:port] || DEFAULT_OPTIONS[:Port]).to_i,
17
- }
12
+ class << self
13
+ def run(app, **options)
14
+ normalized_options = {
15
+ host: options[:Host] || options[:host] || DEFAULT_OPTIONS[:Host],
16
+ port: (options[:Port] || options[:port] || DEFAULT_OPTIONS[:Port]).to_i,
17
+ }
18
18
 
19
- cert_file = options[:cert_file]
20
- key_file = options[:key_file]
19
+ cert_file, key_file = certificate_paths(options)
20
+ config = ::Quicsilver::Transport::Configuration.new(cert_file, key_file)
21
21
 
22
- if cert_file.nil? && key_file.nil?
23
- env = options[:environment] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
22
+ server = ::Quicsilver::Server.new(
23
+ normalized_options[:port],
24
+ address: normalized_options[:host],
25
+ app: app,
26
+ server_configuration: config
27
+ )
24
28
 
25
- if env == 'production'
26
- raise ArgumentError, "cert_file and key_file are required in production"
29
+ yield server if block_given?
30
+
31
+ server.start
32
+ end
33
+
34
+ def valid_options
35
+ {
36
+ "Host=HOST" => "Hostname to listen on (default: 0.0.0.0)",
37
+ "Port=PORT" => "Port to listen on (default: 4433)",
38
+ "cert_file=PATH" => "Path to TLS certificate file (required in production)",
39
+ "key_file=PATH" => "Path to TLS key file (required in production)"
40
+ }
41
+ end
42
+
43
+ private
44
+
45
+ def certificate_paths(options)
46
+ cert_file = options[:cert_file]
47
+ key_file = options[:key_file]
48
+
49
+ if cert_file.nil? && key_file.nil?
50
+ if production?(options)
51
+ raise ArgumentError, "cert_file and key_file are required in production"
52
+ else
53
+ localhost_certificate_paths
54
+ end
55
+ elsif cert_file.nil? || key_file.nil?
56
+ raise ArgumentError, "cert_file and key_file must be provided together"
27
57
  else
28
- require 'localhost/authority'
29
- authority = Localhost::Authority.fetch
30
- cert_file = authority.certificate_path
31
- key_file = authority.key_path
32
- Quicsilver.logger.info("Using auto-generated certificates for localhost")
33
- Quicsilver.logger.info(" Cert: #{cert_file}")
34
- Quicsilver.logger.info(" Key: #{key_file}")
58
+ [cert_file, key_file]
35
59
  end
36
60
  end
37
61
 
38
- config = ::Quicsilver::Transport::Configuration.new(cert_file, key_file)
39
-
40
- server = ::Quicsilver::Server.new(
41
- normalized_options[:port],
42
- address: normalized_options[:host],
43
- app: app,
44
- server_configuration: config
45
- )
62
+ def production?(options)
63
+ env = options[:environment] || ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
64
+ env == "production"
65
+ end
46
66
 
47
- yield server if block_given?
67
+ def localhost_certificate_paths
68
+ require "localhost/authority"
69
+ authority = Localhost::Authority.fetch
70
+ cert_file = authority.certificate_path
71
+ key_file = authority.key_path
48
72
 
49
- server.start
50
- end
73
+ Quicsilver.logger.info("Using auto-generated certificates for localhost")
74
+ Quicsilver.logger.info(" Cert: #{cert_file}")
75
+ Quicsilver.logger.info(" Key: #{key_file}")
51
76
 
52
- def self.valid_options
53
- {
54
- "Host=HOST" => "Hostname to listen on (default: 0.0.0.0)",
55
- "Port=PORT" => "Port to listen on (default: 4433)",
56
- "cert_file=PATH" => "Path to TLS certificate file (required)",
57
- "key_file=PATH" => "Path to TLS key file (required)"
58
- }
77
+ [cert_file, key_file]
78
+ end
59
79
  end
60
-
61
80
  end
62
81
  end
63
82
 
data/quicsilver.gemspec CHANGED
@@ -30,9 +30,15 @@ Gem::Specification.new do |spec|
30
30
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
31
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
32
32
  files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|vendor)/}) }
33
- # Include precompiled binaries if present
34
- files += Dir["lib/quicsilver/libmsquic*"]
35
- files += Dir["lib/quicsilver/quicsilver.{bundle,so}"]
33
+
34
+ if ENV["RELEASE"]
35
+ if RUBY_PLATFORM.include?("darwin")
36
+ files += Dir["lib/quicsilver/libmsquic*.dylib"]
37
+ elsif RUBY_PLATFORM.include?("linux")
38
+ files += Dir["lib/quicsilver/libmsquic.so*"]
39
+ end
40
+ end
41
+
36
42
  files
37
43
  end
38
44
  spec.bindir = "exe"
@@ -46,6 +52,7 @@ Gem::Specification.new do |spec|
46
52
  spec.add_development_dependency "rake", "~> 13.0"
47
53
  spec.add_development_dependency 'rake-compiler', '~> 1.2'
48
54
  spec.add_development_dependency 'rake-compiler-dock', '~> 1.3'
55
+ spec.add_development_dependency "cibuildgem"
49
56
  spec.add_development_dependency "minitest", "~> 5.0"
50
57
  spec.add_development_dependency "minitest-focus", "~> 1.3"
51
58
  spec.add_development_dependency "benchmark-ips", "~> 2.12"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quicsilver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Haroon Ahmed
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-04-25 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bundler
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '1.3'
68
+ - !ruby/object:Gem::Dependency
69
+ name: cibuildgem
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
68
82
  - !ruby/object:Gem::Dependency
69
83
  name: minitest
70
84
  requirement: !ruby/object:Gem::Requirement
@@ -224,14 +238,16 @@ files:
224
238
  - LICENSE
225
239
  - README.md
226
240
  - Rakefile
241
+ - benchmarks/baseline.rb
242
+ - benchmarks/compare.rb
227
243
  - benchmarks/components.rb
228
- - benchmarks/concurrent.rb
229
244
  - benchmarks/helpers.rb
230
- - benchmarks/quicsilver_server.rb
231
- - benchmarks/rails.rb
232
- - benchmarks/throughput.rb
245
+ - benchmarks/latency.rb
246
+ - benchmarks/loss.rb
247
+ - benchmarks/mobile.rb
233
248
  - bin/console
234
249
  - bin/setup
250
+ - docs/connection_identity.md
235
251
  - examples/README.md
236
252
  - examples/benchmark.rb
237
253
  - examples/connection_pool_demo.rb
@@ -239,23 +255,38 @@ files:
239
255
  - examples/falcon_middleware.rb
240
256
  - examples/feature_demo.rb
241
257
  - examples/grpc_style.rb
258
+ - examples/latency_aware_routing.rb
259
+ - examples/lifecycle_drain.rb
242
260
  - examples/minimal_http3_server.rb
243
261
  - examples/priorities.rb
244
262
  - examples/protocol_http_server.rb
245
263
  - examples/rack_http3_server.rb
246
264
  - examples/rails_feature_test.rb
265
+ - examples/server_stats.rb
247
266
  - examples/simple_client_test.rb
248
267
  - examples/streaming_sse.rb
249
268
  - examples/trailers.rb
269
+ - examples/webtransport_smoke.rb
270
+ - examples/worker_load_balancing.rb
250
271
  - ext/quicsilver/extconf.rb
272
+ - ext/quicsilver/include/msquic.h
273
+ - ext/quicsilver/include/msquic_posix.h
274
+ - ext/quicsilver/include/quic_sal_stub.h
275
+ - ext/quicsilver/msquic_patch.rb
276
+ - ext/quicsilver/patches/reliable-reset.patch
277
+ - ext/quicsilver/patches/stream-final-size.patch
251
278
  - ext/quicsilver/quicsilver.c
252
279
  - lib/quicsilver.rb
253
280
  - lib/quicsilver/client/client.rb
254
281
  - lib/quicsilver/client/connection_pool.rb
255
282
  - lib/quicsilver/client/request.rb
283
+ - lib/quicsilver/errors.rb
256
284
  - lib/quicsilver/libmsquic.2.dylib
257
285
  - lib/quicsilver/protocol/adapter.rb
286
+ - lib/quicsilver/protocol/capsule.rb
287
+ - lib/quicsilver/protocol/connect_stream_decoder.rb
258
288
  - lib/quicsilver/protocol/control_stream_parser.rb
289
+ - lib/quicsilver/protocol/datagram.rb
259
290
  - lib/quicsilver/protocol/frame_parser.rb
260
291
  - lib/quicsilver/protocol/frame_reader.rb
261
292
  - lib/quicsilver/protocol/frames.rb
@@ -270,17 +301,32 @@ files:
270
301
  - lib/quicsilver/protocol/response_parser.rb
271
302
  - lib/quicsilver/protocol/stream_input.rb
272
303
  - lib/quicsilver/protocol/stream_output.rb
273
- - lib/quicsilver/quicsilver.bundle
304
+ - lib/quicsilver/protocol/unidirectional_stream.rb
305
+ - lib/quicsilver/protocol/web_transport.rb
306
+ - lib/quicsilver/rack/context.rb
307
+ - lib/quicsilver/response.rb
274
308
  - lib/quicsilver/server/listener_data.rb
309
+ - lib/quicsilver/server/rack_adapter.rb
310
+ - lib/quicsilver/server/receive_queue.rb
275
311
  - lib/quicsilver/server/request_handler.rb
276
312
  - lib/quicsilver/server/request_registry.rb
313
+ - lib/quicsilver/server/scheduler.rb
314
+ - lib/quicsilver/server/schedulers/fiber_scheduler.rb
315
+ - lib/quicsilver/server/schedulers/thread_scheduler.rb
277
316
  - lib/quicsilver/server/server.rb
317
+ - lib/quicsilver/server/web_transport_flow_control.rb
318
+ - lib/quicsilver/server/web_transport_manager.rb
319
+ - lib/quicsilver/server/web_transport_registry.rb
320
+ - lib/quicsilver/server/web_transport_session.rb
321
+ - lib/quicsilver/server/web_transport_stream.rb
278
322
  - lib/quicsilver/transport/configuration.rb
279
323
  - lib/quicsilver/transport/connection.rb
324
+ - lib/quicsilver/transport/connection_stats.rb
280
325
  - lib/quicsilver/transport/event_loop.rb
281
326
  - lib/quicsilver/transport/inbound_stream.rb
282
327
  - lib/quicsilver/transport/stream.rb
283
328
  - lib/quicsilver/transport/stream_event.rb
329
+ - lib/quicsilver/transport/stream_id.rb
284
330
  - lib/quicsilver/version.rb
285
331
  - lib/rackup/handler/quicsilver.rb
286
332
  - quicsilver.gemspec
@@ -306,7 +352,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
306
352
  - !ruby/object:Gem::Version
307
353
  version: '0'
308
354
  requirements: []
309
- rubygems_version: 3.6.2
355
+ rubygems_version: 3.6.9
310
356
  specification_version: 4
311
357
  summary: HTTP/3 server implementation for Ruby
312
358
  test_files: []
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # Concurrency benchmark: tests HTTP/3 multiplexing and multi-connection overhead.
3
- #
4
- # Self-contained:
5
- # ruby benchmarks/concurrent.rb
6
- #
7
- # External server:
8
- # HOST=127.0.0.1 PORT=4433 ruby benchmarks/concurrent.rb
9
-
10
- require_relative "helpers"
11
- require "benchmark"
12
-
13
- MULTIPLEX_REQUESTS = ENV.fetch("MULTIPLEX_REQUESTS", "50").to_i
14
- NUM_CLIENTS = ENV.fetch("NUM_CLIENTS", "20").to_i
15
- REQUESTS_PER_CLIENT = ENV.fetch("REQUESTS_PER_CLIENT", "5").to_i
16
- HOST = ENV["HOST"]
17
- PORT = ENV["PORT"]&.to_i
18
-
19
- def test_multiplexing(host, port)
20
- Benchmarks::Helpers.print_header(
21
- "Test 1: Single-connection multiplexing",
22
- requests: MULTIPLEX_REQUESTS
23
- )
24
-
25
- request_times = []
26
- mutex = Mutex.new
27
-
28
- client = Quicsilver::Client.new(host, port, unsecure: true)
29
- client.open_connection
30
-
31
- elapsed = Benchmark.realtime do
32
- threads = MULTIPLEX_REQUESTS.times.map do |i|
33
- Thread.new do
34
- start = Time.now
35
- client.get("/multiplex/#{i}")
36
- req_time = Time.now - start
37
- mutex.synchronize { request_times << req_time }
38
- end
39
- end
40
- threads.each(&:join)
41
- end
42
-
43
- client.disconnect
44
-
45
- concurrency_factor = request_times.any? ? (request_times.sum / elapsed).round(2) : 0
46
- puts " Wall clock: #{(elapsed * 1000).round(2)}ms"
47
- puts " Avg request time: #{(request_times.sum / request_times.size * 1000).round(2)}ms"
48
- puts " Throughput: #{(MULTIPLEX_REQUESTS / elapsed).round(2)} req/s"
49
- puts " Concurrency factor: #{concurrency_factor}x"
50
- Benchmarks::Helpers.print_stats("Latency", request_times)
51
- end
52
-
53
- def test_concurrent_clients(host, port)
54
- Benchmarks::Helpers.print_header(
55
- "Test 2: Concurrent clients",
56
- clients: NUM_CLIENTS,
57
- requests_per_client: REQUESTS_PER_CLIENT
58
- )
59
-
60
- request_times = []
61
- mutex = Mutex.new
62
- successful = 0
63
-
64
- elapsed = Benchmark.realtime do
65
- threads = NUM_CLIENTS.times.map do |i|
66
- Thread.new do
67
- client = Quicsilver::Client.new(host, port, unsecure: true)
68
- client.open_connection
69
-
70
- REQUESTS_PER_CLIENT.times do |req|
71
- start = Time.now
72
- client.get("/client#{i}/request#{req}")
73
- req_time = Time.now - start
74
- mutex.synchronize { request_times << req_time }
75
- end
76
-
77
- client.disconnect
78
- mutex.synchronize { successful += 1 }
79
- rescue => e
80
- $stderr.puts " Client #{i} error: #{e.message}"
81
- end
82
- end
83
- threads.each(&:join)
84
- end
85
-
86
- total_requests = request_times.size
87
- concurrency_factor = request_times.any? ? (request_times.sum / elapsed).round(2) : 0
88
-
89
- puts " Successful clients: #{successful}/#{NUM_CLIENTS}"
90
- puts " Total requests: #{total_requests}"
91
- puts " Wall clock: #{(elapsed * 1000).round(2)}ms"
92
- puts " Avg request time: #{request_times.any? ? (request_times.sum / request_times.size * 1000).round(2) : "N/A"}ms"
93
- puts " Throughput: #{(total_requests / elapsed).round(2)} req/s"
94
- puts " Concurrency factor: #{concurrency_factor}x"
95
- Benchmarks::Helpers.print_stats("Latency", request_times)
96
- end
97
-
98
- def run_all(host, port)
99
- test_multiplexing(host, port)
100
- test_concurrent_clients(host, port)
101
- end
102
-
103
- if HOST && PORT
104
- run_all(HOST, PORT)
105
- else
106
- puts "Booting inline server..."
107
- Benchmarks::Helpers.with_server(Benchmarks::Helpers.benchmark_app) do |port|
108
- run_all("127.0.0.1", port)
109
- end
110
- end
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "quicsilver"
5
- require 'localhost/authority'
6
- require 'json'
7
-
8
- SIMPLE_APP = lambda do |env|
9
- case env['PATH_INFO']
10
- when '/'
11
- [200, {'Content-Type' => 'text/plain'}, ['OK']]
12
- when '/json'
13
- body = JSON.generate({status: 'ok', timestamp: Time.now.to_i})
14
- [200, {'Content-Type' => 'application/json'}, [body]]
15
- when '/echo'
16
- [200, {'Content-Type' => 'text/plain'}, [env['REQUEST_METHOD']]]
17
- else
18
- [404, {'Content-Type' => 'text/plain'}, ['Not Found']]
19
- end
20
- end
21
-
22
- port = ENV["PORT"] || 4433
23
- default_host = ENV["HOST"] || "0.0.0.0"
24
-
25
- authority = Localhost::Authority.fetch
26
- cert_file = authority.certificate_path
27
- key_file = authority.key_path
28
-
29
- config = ::Quicsilver::Transport::Configuration.new(cert_file, key_file)
30
-
31
- server = ::Quicsilver::Server.new(
32
- port.to_i,
33
- address: default_host,
34
- app: SIMPLE_APP,
35
- server_configuration: config
36
- )
37
-
38
- puts "Starting Quicsilver on port #{port}..."
39
-
40
- trap("INT") do
41
- puts "\nStopping server..."
42
- server.stop
43
- exit
44
- end
45
-
46
- server.start # Blocks until shutdown