quicsilver 0.5.0-x86_64-linux

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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +49 -0
  3. data/.github/workflows/cibuildgem.yaml +117 -0
  4. data/.gitignore +21 -0
  5. data/.gitmodules +3 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +175 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +67 -0
  10. data/LICENSE +21 -0
  11. data/README.md +261 -0
  12. data/Rakefile +130 -0
  13. data/benchmarks/baseline.rb +175 -0
  14. data/benchmarks/compare.rb +398 -0
  15. data/benchmarks/components.rb +191 -0
  16. data/benchmarks/helpers.rb +162 -0
  17. data/benchmarks/latency.rb +128 -0
  18. data/benchmarks/loss.rb +193 -0
  19. data/benchmarks/mobile.rb +125 -0
  20. data/bin/console +14 -0
  21. data/bin/setup +8 -0
  22. data/docs/connection_identity.md +141 -0
  23. data/examples/README.md +58 -0
  24. data/examples/benchmark.rb +191 -0
  25. data/examples/connection_pool_demo.rb +47 -0
  26. data/examples/example_helper.rb +69 -0
  27. data/examples/falcon_middleware.rb +44 -0
  28. data/examples/feature_demo.rb +125 -0
  29. data/examples/grpc_style.rb +97 -0
  30. data/examples/latency_aware_routing.rb +382 -0
  31. data/examples/lifecycle_drain.rb +264 -0
  32. data/examples/minimal_http3_server.rb +14 -0
  33. data/examples/priorities.rb +60 -0
  34. data/examples/protocol_http_server.rb +31 -0
  35. data/examples/rack_http3_server.rb +42 -0
  36. data/examples/rails_feature_test.rb +280 -0
  37. data/examples/server_stats.rb +117 -0
  38. data/examples/simple_client_test.rb +26 -0
  39. data/examples/streaming_sse.rb +33 -0
  40. data/examples/trailers.rb +69 -0
  41. data/examples/webtransport_smoke.rb +546 -0
  42. data/examples/worker_load_balancing.rb +425 -0
  43. data/ext/quicsilver/extconf.rb +124 -0
  44. data/ext/quicsilver/include/msquic.h +2226 -0
  45. data/ext/quicsilver/include/msquic_posix.h +696 -0
  46. data/ext/quicsilver/include/quic_sal_stub.h +314 -0
  47. data/ext/quicsilver/msquic_patch.rb +68 -0
  48. data/ext/quicsilver/patches/reliable-reset.patch +723 -0
  49. data/ext/quicsilver/patches/stream-final-size.patch +47 -0
  50. data/ext/quicsilver/quicsilver.c +2741 -0
  51. data/lib/quicsilver/3.4/quicsilver.so +0 -0
  52. data/lib/quicsilver/4.0/quicsilver.so +0 -0
  53. data/lib/quicsilver/client/client.rb +708 -0
  54. data/lib/quicsilver/client/connection_pool.rb +202 -0
  55. data/lib/quicsilver/client/request.rb +190 -0
  56. data/lib/quicsilver/errors.rb +39 -0
  57. data/lib/quicsilver/libmsquic.so.2 +0 -0
  58. data/lib/quicsilver/protocol/adapter.rb +196 -0
  59. data/lib/quicsilver/protocol/capsule.rb +38 -0
  60. data/lib/quicsilver/protocol/connect_stream_decoder.rb +75 -0
  61. data/lib/quicsilver/protocol/control_stream_parser.rb +117 -0
  62. data/lib/quicsilver/protocol/datagram.rb +40 -0
  63. data/lib/quicsilver/protocol/frame_parser.rb +141 -0
  64. data/lib/quicsilver/protocol/frame_reader.rb +84 -0
  65. data/lib/quicsilver/protocol/frames.rb +454 -0
  66. data/lib/quicsilver/protocol/priority.rb +65 -0
  67. data/lib/quicsilver/protocol/qpack/decoder.rb +127 -0
  68. data/lib/quicsilver/protocol/qpack/encoder.rb +217 -0
  69. data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +140 -0
  70. data/lib/quicsilver/protocol/qpack/huffman.rb +459 -0
  71. data/lib/quicsilver/protocol/request_encoder.rb +50 -0
  72. data/lib/quicsilver/protocol/request_parser.rb +289 -0
  73. data/lib/quicsilver/protocol/response_encoder.rb +107 -0
  74. data/lib/quicsilver/protocol/response_parser.rb +147 -0
  75. data/lib/quicsilver/protocol/stream_input.rb +98 -0
  76. data/lib/quicsilver/protocol/stream_output.rb +59 -0
  77. data/lib/quicsilver/protocol/unidirectional_stream.rb +12 -0
  78. data/lib/quicsilver/protocol/web_transport.rb +214 -0
  79. data/lib/quicsilver/rack/context.rb +33 -0
  80. data/lib/quicsilver/response.rb +40 -0
  81. data/lib/quicsilver/server/listener_data.rb +14 -0
  82. data/lib/quicsilver/server/rack_adapter.rb +77 -0
  83. data/lib/quicsilver/server/receive_queue.rb +106 -0
  84. data/lib/quicsilver/server/request_handler.rb +149 -0
  85. data/lib/quicsilver/server/request_registry.rb +63 -0
  86. data/lib/quicsilver/server/scheduler.rb +50 -0
  87. data/lib/quicsilver/server/schedulers/fiber_scheduler.rb +79 -0
  88. data/lib/quicsilver/server/schedulers/thread_scheduler.rb +76 -0
  89. data/lib/quicsilver/server/server.rb +1080 -0
  90. data/lib/quicsilver/server/web_transport_flow_control.rb +270 -0
  91. data/lib/quicsilver/server/web_transport_manager.rb +281 -0
  92. data/lib/quicsilver/server/web_transport_registry.rb +40 -0
  93. data/lib/quicsilver/server/web_transport_session.rb +812 -0
  94. data/lib/quicsilver/server/web_transport_stream.rb +326 -0
  95. data/lib/quicsilver/transport/configuration.rb +234 -0
  96. data/lib/quicsilver/transport/connection.rb +594 -0
  97. data/lib/quicsilver/transport/connection_stats.rb +53 -0
  98. data/lib/quicsilver/transport/event_loop.rb +74 -0
  99. data/lib/quicsilver/transport/inbound_stream.rb +53 -0
  100. data/lib/quicsilver/transport/stream.rb +43 -0
  101. data/lib/quicsilver/transport/stream_event.rb +49 -0
  102. data/lib/quicsilver/transport/stream_id.rb +26 -0
  103. data/lib/quicsilver/version.rb +3 -0
  104. data/lib/quicsilver.rb +96 -0
  105. data/lib/rackup/handler/quicsilver.rb +96 -0
  106. data/quicsilver.gemspec +66 -0
  107. metadata +362 -0
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ class Server
5
+ class RequestRegistry
6
+ def initialize
7
+ @requests = {}
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def track(stream_id, connection_handle, path:, method:, started_at: Time.now)
12
+ @mutex.synchronize do
13
+ @requests[key_for(stream_id, connection_handle)] = {
14
+ connection_handle: connection_handle,
15
+ stream_id: stream_id,
16
+ path: path,
17
+ method: method,
18
+ started_at: started_at
19
+ }
20
+ end
21
+ end
22
+
23
+ def complete(stream_id, connection_handle)
24
+ @mutex.synchronize do
25
+ @requests.delete(key_for(stream_id, connection_handle))
26
+ end
27
+ end
28
+
29
+ def active_count
30
+ @mutex.synchronize { @requests.size }
31
+ end
32
+
33
+ def active_requests
34
+ @mutex.synchronize { @requests.dup }
35
+ end
36
+
37
+ def requests_older_than(seconds)
38
+ cutoff = Time.now - seconds
39
+ @mutex.synchronize do
40
+ @requests.select { |_, r| r[:started_at] < cutoff }
41
+ end
42
+ end
43
+
44
+ def empty?
45
+ @mutex.synchronize { @requests.empty? }
46
+ end
47
+
48
+ def include?(stream_id, connection_handle)
49
+ @mutex.synchronize do
50
+ @requests.key?(key_for(stream_id, connection_handle))
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def key_for(stream_id, connection_handle)
57
+ raise ArgumentError, "Connection handle is required" if connection_handle.nil?
58
+
59
+ [connection_handle, stream_id]
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ class Server
5
+ # Base class for work schedulers.
6
+ #
7
+ # A scheduler controls how incoming request work units are executed —
8
+ # via threads, fibers, or any other concurrency model. Subclasses
9
+ # implement the delivery mechanism.
10
+ #
11
+ # Usage:
12
+ # scheduler = ThreadScheduler.new(size: 10, max_queue_size: 100, &handler)
13
+ # scheduler.start # start workers
14
+ # scheduler.enqueue(work) # schedule a work unit
15
+ # scheduler.drain(timeout: 5) # wait for queue to empty
16
+ # scheduler.stop # shut down workers
17
+ #
18
+ class Scheduler
19
+ # Schedule a work unit for execution.
20
+ def enqueue(work)
21
+ raise NotImplementedError
22
+ end
23
+
24
+ # Is the work queue at capacity?
25
+ def full?
26
+ raise NotImplementedError
27
+ end
28
+
29
+ # Number of pending work units.
30
+ def pending
31
+ raise NotImplementedError
32
+ end
33
+
34
+ # Wait for the queue to empty.
35
+ def drain(timeout: 5)
36
+ raise NotImplementedError
37
+ end
38
+
39
+ # Start processing work units.
40
+ def start
41
+ raise NotImplementedError
42
+ end
43
+
44
+ # Shut down workers.
45
+ def stop
46
+ raise NotImplementedError
47
+ end
48
+ end
49
+ end
50
+ end
@@ -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