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,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