patient_http-sidekiq 1.2.0 → 1.4.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +31 -10
  3. data/CHANGELOG.md +32 -0
  4. data/README.md +126 -2
  5. data/VERSION +1 -1
  6. data/lib/patient_http/sidekiq/configuration.rb +258 -1
  7. data/lib/patient_http/sidekiq/direct_task_handler.rb +46 -0
  8. data/lib/patient_http/sidekiq/processor_observer.rb +162 -21
  9. data/lib/patient_http/sidekiq/redis_pool.rb +88 -0
  10. data/lib/patient_http/sidekiq/request_executor.rb +40 -10
  11. data/lib/patient_http/sidekiq/request_worker.rb +5 -2
  12. data/lib/patient_http/sidekiq/stats.rb +230 -33
  13. data/lib/patient_http/sidekiq/task_handler.rb +9 -3
  14. data/lib/patient_http/sidekiq/task_monitor.rb +410 -121
  15. data/lib/patient_http/sidekiq/task_monitor_thread.rb +41 -7
  16. data/lib/patient_http/sidekiq/web_ui/assets/patient-http/css/patient_http.css +29 -71
  17. data/lib/patient_http/sidekiq/web_ui/locales/ar.yml +10 -5
  18. data/lib/patient_http/sidekiq/web_ui/locales/cs.yml +10 -5
  19. data/lib/patient_http/sidekiq/web_ui/locales/da.yml +10 -5
  20. data/lib/patient_http/sidekiq/web_ui/locales/de.yml +10 -5
  21. data/lib/patient_http/sidekiq/web_ui/locales/el.yml +10 -5
  22. data/lib/patient_http/sidekiq/web_ui/locales/en.yml +10 -5
  23. data/lib/patient_http/sidekiq/web_ui/locales/es.yml +10 -5
  24. data/lib/patient_http/sidekiq/web_ui/locales/fa.yml +10 -5
  25. data/lib/patient_http/sidekiq/web_ui/locales/fr.yml +10 -5
  26. data/lib/patient_http/sidekiq/web_ui/locales/gd.yml +10 -5
  27. data/lib/patient_http/sidekiq/web_ui/locales/he.yml +10 -5
  28. data/lib/patient_http/sidekiq/web_ui/locales/hi.yml +10 -5
  29. data/lib/patient_http/sidekiq/web_ui/locales/it.yml +10 -5
  30. data/lib/patient_http/sidekiq/web_ui/locales/ja.yml +10 -5
  31. data/lib/patient_http/sidekiq/web_ui/locales/ko.yml +10 -5
  32. data/lib/patient_http/sidekiq/web_ui/locales/lt.yml +10 -5
  33. data/lib/patient_http/sidekiq/web_ui/locales/nb.yml +10 -5
  34. data/lib/patient_http/sidekiq/web_ui/locales/nl.yml +10 -5
  35. data/lib/patient_http/sidekiq/web_ui/locales/pl.yml +10 -5
  36. data/lib/patient_http/sidekiq/web_ui/locales/pt-BR.yml +10 -5
  37. data/lib/patient_http/sidekiq/web_ui/locales/pt.yml +10 -5
  38. data/lib/patient_http/sidekiq/web_ui/locales/ru.yml +10 -5
  39. data/lib/patient_http/sidekiq/web_ui/locales/sv.yml +10 -5
  40. data/lib/patient_http/sidekiq/web_ui/locales/ta.yml +10 -5
  41. data/lib/patient_http/sidekiq/web_ui/locales/tr.yml +10 -5
  42. data/lib/patient_http/sidekiq/web_ui/locales/uk.yml +10 -5
  43. data/lib/patient_http/sidekiq/web_ui/locales/ur.yml +10 -5
  44. data/lib/patient_http/sidekiq/web_ui/locales/vi.yml +10 -5
  45. data/lib/patient_http/sidekiq/web_ui/locales/zh-CN.yml +10 -5
  46. data/lib/patient_http/sidekiq/web_ui/locales/zh-TW.yml +10 -5
  47. data/lib/patient_http/sidekiq/web_ui/views/patient_http.html.erb +99 -44
  48. data/lib/patient_http/sidekiq/web_ui.rb +53 -1
  49. data/lib/patient_http/sidekiq.rb +303 -31
  50. data/patient_http-sidekiq.gemspec +1 -1
  51. metadata +6 -4
@@ -77,7 +77,9 @@ module PatientHttp
77
77
  autoload :CallbackWorker, File.join(__dir__, "sidekiq/callback_worker")
78
78
  autoload :Configuration, File.join(__dir__, "sidekiq/configuration")
79
79
  autoload :Context, File.join(__dir__, "sidekiq/context")
80
+ autoload :DirectTaskHandler, File.join(__dir__, "sidekiq/direct_task_handler")
80
81
  autoload :ProcessorObserver, File.join(__dir__, "sidekiq/processor_observer")
82
+ autoload :RedisPool, File.join(__dir__, "sidekiq/redis_pool")
81
83
  autoload :RequestExecutor, File.join(__dir__, "sidekiq/request_executor")
82
84
  autoload :RequestWorker, File.join(__dir__, "sidekiq/request_worker")
83
85
  autoload :LifecycleHooks, File.join(__dir__, "sidekiq/lifecycle_hooks")
@@ -87,13 +89,17 @@ module PatientHttp
87
89
  autoload :TaskMonitorThread, File.join(__dir__, "sidekiq/task_monitor_thread")
88
90
  autoload :WebUI, File.join(__dir__, "sidekiq/web_ui")
89
91
 
90
- @processor = nil
92
+ @processors = {}
91
93
  @configuration = nil
92
94
  @after_completion_callbacks = []
93
95
  @after_error_callbacks = []
94
96
  @external_storage = nil
95
97
  @request_handler = nil
96
98
  @lifecycle_mutex = Mutex.new
99
+ @redis_pool = nil
100
+ @stats = nil
101
+ @task_monitor = nil
102
+ @monitor_thread = nil
97
103
 
98
104
  class << self
99
105
  attr_writer :configuration
@@ -110,6 +116,9 @@ module PatientHttp
110
116
  yield(configuration) if block_given?
111
117
  @configuration = configuration
112
118
  @external_storage = nil
119
+ # Rebuild the stats aggregator from the new configuration unless a
120
+ # running processor already owns it.
121
+ @stats = nil unless running?
113
122
  register_handler
114
123
  PatientHttp.default_configuration = configuration
115
124
  configuration
@@ -160,33 +169,33 @@ module PatientHttp
160
169
  end
161
170
  end
162
171
 
163
- # Check if the processor is running.
172
+ # Check if any processor is running.
164
173
  #
165
174
  # @return [Boolean]
166
175
  def running?
167
- !!@processor&.running?
176
+ @processors.values.any?(&:running?)
168
177
  end
169
178
 
170
- # Check if the processor is draining (not accepting new requests
179
+ # Check if any processor is draining (not accepting new requests
171
180
  # but still processing in-flight ones).
172
181
  #
173
182
  # @return [Boolean]
174
183
  def draining?
175
- !!@processor&.draining?
184
+ @processors.values.any?(&:draining?)
176
185
  end
177
186
 
178
- # Check if the processor is in the process of stopping.
187
+ # Check if any processor is in the process of stopping.
179
188
  #
180
189
  # @return [Boolean]
181
190
  def stopping?
182
- !!@processor&.stopping?
191
+ @processors.values.any?(&:stopping?)
183
192
  end
184
193
 
185
- # Check if the processor is stopped or has not been started.
194
+ # Check if all processors are stopped or none have been started.
186
195
  #
187
196
  # @return [Boolean]
188
197
  def stopped?
189
- @processor.nil? || @processor.stopped?
198
+ @processors.values.all?(&:stopped?)
190
199
  end
191
200
 
192
201
  # Get an ExternalStorage instance for storing and fetching payloads.
@@ -220,7 +229,9 @@ module PatientHttp
220
229
  # Nested calls merge options with the innermost values taking precedence.
221
230
  # If the options include a queue, the callback job for the request is
222
231
  # enqueued on that queue as well. Options only apply to requests enqueued
223
- # in the same fiber as the block. This method has no effect
232
+ # in the same fiber as the block. Requests made in the block always go
233
+ # through the Sidekiq queue, even when direct execution is enabled, so
234
+ # that Sidekiq applies the options. This method has no effect
224
235
  # when jobs run inline with Sidekiq::Testing.inline!.
225
236
  #
226
237
  # @param options [Hash] Sidekiq job options (symbol or string keys)
@@ -253,14 +264,18 @@ module PatientHttp
253
264
  # +error.callback_args+ using symbol or string keys.
254
265
  # @param raise_error_responses [Boolean] If true, treats non-2xx responses as errors
255
266
  # and calls +on_error+ instead of +on_complete+. Defaults to false.
267
+ # @param processor [Symbol, String, nil] Name of the processor profile that should
268
+ # execute the request. Defaults to the request's own processor name, a
269
+ # "processor" value from with_sidekiq_options, or :default.
256
270
  # @return [String] the request ID
257
- def execute(request, callback:, callback_args: nil, raise_error_responses: false)
271
+ def execute(request, callback:, callback_args: nil, raise_error_responses: false, processor: nil)
258
272
  PatientHttp::CallbackValidator.validate!(callback)
259
273
  callback_name = callback.is_a?(Class) ? callback.name : callback.to_s
260
274
  callback_args = PatientHttp::CallbackValidator.validate_callback_args(callback_args)
261
275
  request_id = SecureRandom.uuid
262
276
 
263
- encrypted = encrypt(request.as_json)
277
+ request_json = request.as_json
278
+ encrypted = encrypt(request_json)
264
279
 
265
280
  data = if external_storage.enabled?
266
281
  external_storage.store(encrypted, max_size: configuration.payload_store_threshold)
@@ -269,12 +284,28 @@ module PatientHttp
269
284
  end
270
285
 
271
286
  options = current_sidekiq_options
287
+ processor_name = resolve_processor_name(processor, request, options)
272
288
  if options&.any?
289
+ options = options.except("processor")
273
290
  queue = options["queue"]
274
291
  options = options.merge("patient_http_callback_queue" => queue.to_s) if queue
275
- RequestWorker.set(options).perform_async(data, callback_name, raise_error_responses, callback_args, request_id)
292
+ end
293
+ args = [data, callback_name, raise_error_responses, callback_args, request_id, processor_name]
294
+
295
+ if direct_execution?(options)
296
+ execute_on_local_processor(
297
+ request_json,
298
+ args,
299
+ callback_name: callback_name,
300
+ raise_error_responses: raise_error_responses,
301
+ callback_args: callback_args,
302
+ request_id: request_id,
303
+ processor_name: processor_name
304
+ )
305
+ elsif options&.any?
306
+ RequestWorker.set(options).perform_async(*args)
276
307
  else
277
- RequestWorker.perform_async(data, callback_name, raise_error_responses, callback_args, request_id)
308
+ RequestWorker.perform_async(*args)
278
309
  end
279
310
 
280
311
  request_id
@@ -297,36 +328,62 @@ module PatientHttp
297
328
  PatientHttp.register_handler(@request_handler)
298
329
  end
299
330
 
300
- # Start the processor
331
+ # Start a processor for each configured processor profile, along with the
332
+ # shared crash-recovery monitor and stats.
301
333
  #
302
334
  # @return [void]
303
335
  def start
304
336
  @lifecycle_mutex.synchronize do
305
- return if @processor && !@processor.stopped?
337
+ return if @processors.any? && !@processors.values.all?(&:stopped?)
338
+
339
+ warn_about_blocking_redis_driver
306
340
 
307
- @processor = PatientHttp::Processor.new(configuration)
308
- @processor.observe(ProcessorObserver.new(@processor))
309
- configuration.observers.each do |observer|
310
- @processor.observe(observer)
341
+ @redis_pool ||= RedisPool.new(configuration)
342
+ @stats ||= Stats.new(configuration)
343
+ @task_monitor ||= TaskMonitor.new(
344
+ configuration,
345
+ processors: -> { processor_capacity_snapshot }
346
+ )
347
+
348
+ @processors = {}
349
+ configuration.processor_profiles.each_key do |name|
350
+ processor = PatientHttp::Processor.new(configuration.processor_config(name), name: name)
351
+ processor.observe(ProcessorObserver.new(processor, stats: @stats, task_monitor: @task_monitor))
352
+ configuration.observers.each do |observer|
353
+ processor.observe(observer)
354
+ end
355
+ @processors[name] = processor
311
356
  end
312
- @processor.start
357
+ @processors.each_value(&:start)
358
+
359
+ # A restart after the processors stopped on their own (e.g. a reactor
360
+ # error) leaves the previous monitor thread running; stop it before
361
+ # replacing the reference so only one thread ever heartbeats.
362
+ @monitor_thread&.stop
363
+ @monitor_thread = TaskMonitorThread.new(
364
+ configuration,
365
+ @task_monitor,
366
+ -> { @processors.values.flat_map(&:tracked_request_ids) },
367
+ stats: @stats
368
+ )
369
+ @monitor_thread.start
313
370
  end
314
371
 
315
372
  register_handler
316
373
  end
317
374
 
318
- # Signal the processor to drain (stop accepting new requests)
375
+ # Signal all processors to drain (stop accepting new requests)
319
376
  #
320
377
  # @return [void]
321
378
  def quiet
322
379
  @lifecycle_mutex.synchronize do
323
380
  return unless running?
324
381
 
325
- @processor.drain
382
+ @processors.each_value(&:drain)
326
383
  end
327
384
  end
328
385
 
329
- # Stop the processor gracefully
386
+ # Stop all processors gracefully
330
387
  #
331
388
  # @param timeout [Float, nil] maximum time to wait for in-flight requests to complete
332
389
  # @return [void]
@@ -336,10 +393,12 @@ module PatientHttp
336
393
  end
337
394
 
338
395
  @lifecycle_mutex.synchronize do
339
- return unless @processor
396
+ # Shared services can outlive the processors when a start failed part
397
+ # way through, so tear them down whenever any of them exist.
398
+ return if @processors.empty? && @redis_pool.nil? && @task_monitor.nil? && @monitor_thread.nil?
340
399
 
341
- @processor.stop(timeout: timeout)
342
- @processor = nil
400
+ stop_processors(timeout)
401
+ shutdown_shared_services
343
402
  end
344
403
  end
345
404
 
@@ -349,8 +408,8 @@ module PatientHttp
349
408
  # @api private
350
409
  def reset!
351
410
  @lifecycle_mutex.synchronize do
352
- @processor&.stop(timeout: 0)
353
- @processor = nil
411
+ stop_processors(0)
412
+ shutdown_shared_services
354
413
  end
355
414
  @configuration = nil
356
415
  @external_storage = nil
@@ -381,20 +440,233 @@ module PatientHttp
381
440
  end
382
441
  end
383
442
 
384
- # Returns the processor instance (internal accessor)
443
+ # Returns a processor instance by name (internal accessor).
385
444
  #
445
+ # @param name [Symbol, String] the processor name
386
446
  # @return [PatientHttp::Processor, nil]
387
447
  # @api private
388
- attr_accessor :processor
448
+ def processor(name = :default)
449
+ @processors[name.to_sym]
450
+ end
451
+
452
+ # Set the default processor (internal, for testing).
453
+ #
454
+ # @param value [PatientHttp::Processor, nil]
455
+ # @api private
456
+ def processor=(value)
457
+ if value.nil?
458
+ @processors.delete(:default)
459
+ else
460
+ @processors[:default] = value
461
+ end
462
+ end
463
+
464
+ # The gem's dedicated Redis pool, or nil when no processor has started
465
+ # in this process (e.g. web or client processes).
466
+ #
467
+ # @return [RedisPool, nil]
468
+ # @api private
469
+ attr_reader :redis_pool
470
+
471
+ # The shared stats aggregator for this process. Available before start so
472
+ # rejection stats can be recorded from any path.
473
+ #
474
+ # @return [Stats]
475
+ # @api private
476
+ def stats
477
+ @stats ||= Stats.new(configuration)
478
+ end
479
+
480
+ # Yield a Redis connection: the gem's dedicated pool when it exists,
481
+ # falling back to Sidekiq's pool selection otherwise.
482
+ #
483
+ # @param retry_on_connection_error [Boolean] whether a connection-level
484
+ # failure may replay the block. Pass false when the block is not
485
+ # idempotent, such as a batch of counter increments the server may
486
+ # already have applied.
487
+ # @yield [conn] the Redis connection
488
+ # @return [Object] the block's return value
489
+ # @api private
490
+ def redis(retry_on_connection_error: true, &block)
491
+ pool = @redis_pool
492
+ if pool
493
+ pool.with(retry_on_connection_error: retry_on_connection_error, &block)
494
+ else
495
+ ::Sidekiq.redis(&block)
496
+ end
497
+ end
498
+
499
+ # Run a block with Sidekiq client pushes routed through the gem's
500
+ # dedicated Redis pool. Used for pushes made from gem-owned threads
501
+ # (completion workers, the monitor thread) so they do not contend with
502
+ # Sidekiq's internal pool.
503
+ #
504
+ # @return [Object] the block's return value
505
+ # @api private
506
+ def with_redis_pool(&block)
507
+ pool = @redis_pool&.pool
508
+ if pool
509
+ ::Sidekiq::Client.via(pool, &block)
510
+ else
511
+ yield
512
+ end
513
+ end
389
514
 
390
515
  private
391
516
 
517
+ # Snapshot of each processor's capacity in this process, published with
518
+ # the heartbeat so the Web UI can report capacity per processor. The
519
+ # inflight count is the number of requests counted against the
520
+ # processor's capacity: queued, pending, and in flight.
521
+ #
522
+ # @return [Hash] processor name => { inflight:, max_capacity: }
523
+ def processor_capacity_snapshot
524
+ @processors.each_with_object({}) do |(name, processor), snapshot|
525
+ snapshot[name] = {
526
+ inflight: processor.total_count,
527
+ max_capacity: processor.config.max_connections
528
+ }
529
+ end
530
+ end
531
+
532
+ # Stop every processor and clear the registry. Each processor waits up to
533
+ # the full timeout for its in-flight requests, so they are stopped in
534
+ # parallel; stopping them in sequence would multiply the shutdown
535
+ # deadline by the number of processor profiles and overrun the time
536
+ # Sidekiq allows before it kills the process.
537
+ #
538
+ # @param timeout [Float, nil] maximum time to wait for in-flight requests
539
+ # @return [void]
540
+ def stop_processors(timeout)
541
+ processors = @processors.values
542
+ @processors = {}
543
+ return if processors.empty?
544
+
545
+ if processors.size == 1
546
+ processors.first.stop(timeout: timeout)
547
+ return
548
+ end
549
+
550
+ processors.map { |processor|
551
+ Thread.new do
552
+ processor.stop(timeout: timeout)
553
+ rescue => e
554
+ configuration.logger&.error(
555
+ "[PatientHttp::Sidekiq] Failed to stop processor #{processor.name}: #{e.inspect}"
556
+ )
557
+ end
558
+ }.each(&:join)
559
+ end
560
+
561
+ # Stop the shared monitor thread, flush pending stats, remove this
562
+ # process from the registry, and shut down the Redis pool. Called with
563
+ # the lifecycle mutex held after all processors have stopped.
564
+ def shutdown_shared_services
565
+ @monitor_thread&.stop
566
+ @monitor_thread = nil
567
+ begin
568
+ @stats&.flush
569
+ rescue => e
570
+ configuration.logger&.error("[PatientHttp::Sidekiq] Failed to flush stats during shutdown: #{e.inspect}")
571
+ end
572
+ begin
573
+ @task_monitor&.remove_process
574
+ rescue => e
575
+ configuration.logger&.error("[PatientHttp::Sidekiq] Failed to remove process registration: #{e.inspect}")
576
+ end
577
+ @task_monitor = nil
578
+ @stats = nil
579
+ @redis_pool&.shutdown
580
+ @redis_pool = nil
581
+ end
582
+
583
+ # Log a warning when a blocking C-level Redis driver is installed.
584
+ # Blocking Redis I/O on the reactor thread stalls every in-flight HTTP
585
+ # request; the gem's own calls run on worker threads, but application
586
+ # observers may still call Redis from processor callbacks.
587
+ def warn_about_blocking_redis_driver
588
+ return unless defined?(RedisClient) && RedisClient.default_driver.name.to_s.include?("Hiredis")
589
+
590
+ configuration.logger&.warn(
591
+ "[PatientHttp::Sidekiq] hiredis-client detected. The hiredis driver performs blocking I/O " \
592
+ "that does not yield to the fiber scheduler. Avoid Redis calls from processor observers " \
593
+ "or callbacks that run on the reactor thread."
594
+ )
595
+ rescue => e
596
+ configuration.logger&.debug("[PatientHttp::Sidekiq] Redis driver check failed: #{e.inspect}")
597
+ end
598
+
392
599
  # Current scoped Sidekiq options, if any.
393
600
  #
394
601
  # @return [Hash, nil]
395
602
  def current_sidekiq_options
396
603
  Thread.current[:patient_http_sidekiq_options]
397
604
  end
605
+
606
+ # Resolve the processor profile name for a request. Precedence: the
607
+ # explicit +processor:+ argument, the request's own processor name, a
608
+ # "processor" value from with_sidekiq_options, then :default.
609
+ #
610
+ # @param explicit [Symbol, String, nil] the processor: argument
611
+ # @param request [PatientHttp::Request] the request
612
+ # @param options [Hash, nil] scoped Sidekiq options
613
+ # @return [String] the processor name
614
+ def resolve_processor_name(explicit, request, options)
615
+ name = explicit || request.processor || options&.[]("processor") || :default
616
+ name.to_s
617
+ end
618
+
619
+ # Check if the request can go directly to a processor running in the current
620
+ # process. Requests made in a with_sidekiq_options block always go through
621
+ # the queue so that Sidekiq applies the options (queue routing, scheduling,
622
+ # retry), and Sidekiq testing modes must keep their normal enqueue semantics.
623
+ #
624
+ # @param options [Hash, nil] scoped Sidekiq options for the request
625
+ # @return [Boolean]
626
+ def direct_execution?(options)
627
+ return false unless options.nil?
628
+ return false unless configuration.direct_execution?
629
+ return false unless running?
630
+ return false if defined?(::Sidekiq::Testing) && ::Sidekiq::Testing.enabled?
631
+
632
+ true
633
+ end
634
+
635
+ # Hand the request to the processor running in the current process. If the
636
+ # processor cannot accept the request (at capacity or shutting down), the
637
+ # request is enqueued as a normal RequestWorker job instead through the
638
+ # same retry path the processor uses when it drains.
639
+ #
640
+ # @param request_json [Hash] the serialized request
641
+ # @param args [Array] the RequestWorker job arguments
642
+ # @param callback_name [String] the callback service class name
643
+ # @param raise_error_responses [Boolean] whether non-2xx responses are errors
644
+ # @param callback_args [Hash, nil] arguments to pass to the callback
645
+ # @param request_id [String] unique request ID
646
+ # @param processor_name [String] name of the processor profile to run on
647
+ # @return [void]
648
+ def execute_on_local_processor(request_json, args, callback_name:, raise_error_responses:, callback_args:, request_id:, processor_name: "default")
649
+ task_handler = DirectTaskHandler.new(args)
650
+
651
+ begin
652
+ # Reload the request from its serialized form so the direct path
653
+ # processes the same reconstructed request a RequestWorker job would.
654
+ RequestExecutor.execute(
655
+ PatientHttp::Request.load(request_json),
656
+ callback: callback_name,
657
+ raise_error_responses: raise_error_responses,
658
+ callback_args: callback_args,
659
+ task_handler: task_handler,
660
+ request_id: request_id,
661
+ processor_name: processor_name
662
+ )
663
+ rescue PatientHttp::NotRunningError, PatientHttp::MaxCapacityError => e
664
+ configuration.logger&.info(
665
+ "[PatientHttp::Sidekiq] Falling back to enqueuing request: #{e.message}"
666
+ )
667
+ task_handler.retry
668
+ end
669
+ end
398
670
  end
399
671
  end
400
672
 
@@ -40,5 +40,5 @@ Gem::Specification.new do |spec|
40
40
  spec.required_ruby_version = ">= 3.2"
41
41
 
42
42
  spec.add_dependency "sidekiq", ">= 7.0"
43
- spec.add_dependency "patient_http", ">= 1.3.0"
43
+ spec.add_dependency "patient_http", ">= 1.5.0"
44
44
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patient_http-sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 1.3.0
32
+ version: 1.5.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 1.3.0
39
+ version: 1.5.0
40
40
  description: This gem provides a mechanism to offload long-running HTTP requests from
41
41
  Sidekiq workers to a dedicated async I/O processor running in the same process,
42
42
  freeing the worker thread immediately while the HTTP request is in flight.
@@ -56,8 +56,10 @@ files:
56
56
  - lib/patient_http/sidekiq/callback_worker.rb
57
57
  - lib/patient_http/sidekiq/configuration.rb
58
58
  - lib/patient_http/sidekiq/context.rb
59
+ - lib/patient_http/sidekiq/direct_task_handler.rb
59
60
  - lib/patient_http/sidekiq/lifecycle_hooks.rb
60
61
  - lib/patient_http/sidekiq/processor_observer.rb
62
+ - lib/patient_http/sidekiq/redis_pool.rb
61
63
  - lib/patient_http/sidekiq/request_executor.rb
62
64
  - lib/patient_http/sidekiq/request_worker.rb
63
65
  - lib/patient_http/sidekiq/stats.rb
@@ -120,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
122
  - !ruby/object:Gem::Version
121
123
  version: '0'
122
124
  requirements: []
123
- rubygems_version: 3.6.9
125
+ rubygems_version: 4.0.3
124
126
  specification_version: 4
125
127
  summary: Offload long-running HTTP requests from Sidekiq workers to a dedicated async
126
128
  I/O processor