rage-rb 1.19.2 → 1.20.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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/Appraisals +19 -0
- data/CHANGELOG.md +15 -1
- data/CODE_OF_CONDUCT.md +13 -17
- data/Gemfile +3 -0
- data/README.md +60 -63
- data/Rakefile +14 -0
- data/lib/rage/all.rb +3 -0
- data/lib/rage/cable/cable.rb +11 -7
- data/lib/rage/cable/channel.rb +6 -1
- data/lib/rage/cable/connection.rb +4 -0
- data/lib/rage/cable/router.rb +14 -9
- data/lib/rage/configuration.rb +235 -21
- data/lib/rage/controller/api.rb +49 -44
- data/lib/rage/deferred/context.rb +30 -2
- data/lib/rage/deferred/deferred.rb +18 -6
- data/lib/rage/deferred/metadata.rb +39 -0
- data/lib/rage/deferred/middleware_chain.rb +67 -0
- data/lib/rage/deferred/task.rb +45 -17
- data/lib/rage/events/events.rb +3 -3
- data/lib/rage/events/subscriber.rb +36 -25
- data/lib/rage/fiber.rb +33 -31
- data/lib/rage/fiber_scheduler.rb +6 -2
- data/lib/rage/logger/logger.rb +7 -1
- data/lib/rage/middleware/body_finalizer.rb +14 -0
- data/lib/rage/response.rb +10 -5
- data/lib/rage/rspec.rb +17 -17
- data/lib/rage/setup.rb +2 -2
- data/lib/rage/telemetry/handler.rb +131 -0
- data/lib/rage/telemetry/spans/await_fiber.rb +50 -0
- data/lib/rage/telemetry/spans/broadcast_cable_stream.rb +50 -0
- data/lib/rage/telemetry/spans/create_websocket_connection.rb +50 -0
- data/lib/rage/telemetry/spans/dispatch_fiber.rb +48 -0
- data/lib/rage/telemetry/spans/enqueue_deferred_task.rb +52 -0
- data/lib/rage/telemetry/spans/process_cable_action.rb +56 -0
- data/lib/rage/telemetry/spans/process_cable_connection.rb +56 -0
- data/lib/rage/telemetry/spans/process_controller_action.rb +56 -0
- data/lib/rage/telemetry/spans/process_deferred_task.rb +54 -0
- data/lib/rage/telemetry/spans/process_event_subscriber.rb +54 -0
- data/lib/rage/telemetry/spans/publish_event.rb +54 -0
- data/lib/rage/telemetry/spans/spawn_fiber.rb +50 -0
- data/lib/rage/telemetry/telemetry.rb +121 -0
- data/lib/rage/telemetry/tracer.rb +97 -0
- data/lib/rage/version.rb +1 -1
- data/rage.gemspec +4 -3
- metadata +38 -5
data/lib/rage/configuration.rb
CHANGED
|
@@ -203,6 +203,14 @@ class Rage::Configuration
|
|
|
203
203
|
end
|
|
204
204
|
# @!endgroup
|
|
205
205
|
|
|
206
|
+
# @!group Telemetry Configuration
|
|
207
|
+
# Allows configuring telemetry settings.
|
|
208
|
+
# @return [Rage::Configuration::Telemetry]
|
|
209
|
+
def telemetry
|
|
210
|
+
@telemetry ||= Telemetry.new
|
|
211
|
+
end
|
|
212
|
+
# @!endgroup
|
|
213
|
+
|
|
206
214
|
# @!group Session Configuration
|
|
207
215
|
# Allows configuring session settings.
|
|
208
216
|
# @return [Rage::Configuration::Session]
|
|
@@ -376,13 +384,13 @@ class Rage::Configuration
|
|
|
376
384
|
end
|
|
377
385
|
end
|
|
378
386
|
|
|
379
|
-
class
|
|
387
|
+
class MiddlewareRegistry
|
|
380
388
|
# @private
|
|
381
|
-
attr_reader :
|
|
389
|
+
attr_reader :objects
|
|
382
390
|
|
|
383
391
|
# @private
|
|
384
392
|
def initialize
|
|
385
|
-
@
|
|
393
|
+
@objects = []
|
|
386
394
|
end
|
|
387
395
|
|
|
388
396
|
# Add a new middleware to the end of the stack.
|
|
@@ -400,7 +408,8 @@ class Rage::Configuration
|
|
|
400
408
|
# end
|
|
401
409
|
# end
|
|
402
410
|
def use(new_middleware, *args, &block)
|
|
403
|
-
|
|
411
|
+
validate!(-1, new_middleware)
|
|
412
|
+
@objects.insert(-1, [new_middleware, args, block])
|
|
404
413
|
end
|
|
405
414
|
|
|
406
415
|
# Insert a new middleware before an existing middleware in the stack.
|
|
@@ -419,11 +428,9 @@ class Rage::Configuration
|
|
|
419
428
|
# end
|
|
420
429
|
# end
|
|
421
430
|
def insert_before(existing_middleware, new_middleware, *args, &block)
|
|
422
|
-
index =
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
end
|
|
426
|
-
@middlewares = (@middlewares[0...index] + [[new_middleware, args, block]] + @middlewares[index..]).uniq(&:first)
|
|
431
|
+
index = find_object_index(existing_middleware)
|
|
432
|
+
validate!(index, new_middleware)
|
|
433
|
+
@objects.insert(index, [new_middleware, args, block])
|
|
427
434
|
end
|
|
428
435
|
|
|
429
436
|
# Insert a new middleware after an existing middleware in the stack.
|
|
@@ -441,29 +448,65 @@ class Rage::Configuration
|
|
|
441
448
|
# end
|
|
442
449
|
# end
|
|
443
450
|
def insert_after(existing_middleware, new_middleware, *args, &block)
|
|
444
|
-
index =
|
|
445
|
-
|
|
451
|
+
index = find_object_index(existing_middleware) + 1
|
|
452
|
+
index = 0 if @objects.empty?
|
|
453
|
+
validate!(index, new_middleware)
|
|
454
|
+
@objects.insert(index, [new_middleware, args, block])
|
|
446
455
|
end
|
|
447
456
|
|
|
448
457
|
# Check if a middleware is included in the stack.
|
|
449
|
-
# @param middleware [Class
|
|
458
|
+
# @param middleware [Class] the middleware class
|
|
450
459
|
# @return [Boolean]
|
|
451
460
|
def include?(middleware)
|
|
452
|
-
|
|
461
|
+
@objects.any? { |o, _, _| o == middleware }
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Delete a middleware from the stack.
|
|
465
|
+
# @param middleware [Class] the middleware class
|
|
466
|
+
# @example
|
|
467
|
+
# Rage.configure do
|
|
468
|
+
# config.middleware.delete Rack::Cors
|
|
469
|
+
# end
|
|
470
|
+
def delete(middleware)
|
|
471
|
+
@objects.reject! { |o, _, _| o == middleware }
|
|
453
472
|
end
|
|
454
473
|
|
|
455
474
|
private
|
|
456
475
|
|
|
457
|
-
def
|
|
458
|
-
if
|
|
459
|
-
if
|
|
460
|
-
|
|
476
|
+
def find_object_index(object)
|
|
477
|
+
if object.is_a?(Integer)
|
|
478
|
+
if @objects[object] || object == 0
|
|
479
|
+
object
|
|
480
|
+
else
|
|
481
|
+
raise ArgumentError, "Could not find middleware at index #{object}"
|
|
461
482
|
end
|
|
462
|
-
middleware
|
|
463
483
|
else
|
|
464
|
-
@
|
|
465
|
-
|
|
466
|
-
|
|
484
|
+
index = @objects.index { |o, _, _| o == object }
|
|
485
|
+
raise ArgumentError, "Could not find `#{object}` in the middleware registry" unless index
|
|
486
|
+
index
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def validate!(_, _)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# See {Rage::Configuration::MiddlewareRegistry Rage::Configuration::MiddlewareRegistry} for details on available methods.
|
|
495
|
+
class Middleware < MiddlewareRegistry
|
|
496
|
+
# @private
|
|
497
|
+
alias_method :middlewares, :objects
|
|
498
|
+
|
|
499
|
+
# @private
|
|
500
|
+
def initialize
|
|
501
|
+
super
|
|
502
|
+
@objects = [[Rage::FiberWrapper]]
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
private
|
|
506
|
+
|
|
507
|
+
def validate!(index, middleware)
|
|
508
|
+
if index == 0 && @objects[0][0] == Rage::FiberWrapper
|
|
509
|
+
puts "WARNING: inserting the `#{middleware}` middleware before `Rage::FiberWrapper` may cause undefined behavior."
|
|
467
510
|
end
|
|
468
511
|
end
|
|
469
512
|
end
|
|
@@ -730,6 +773,48 @@ class Rage::Configuration
|
|
|
730
773
|
@backpressure = Backpressure.new(high_water_mark, low_water_mark, timeout)
|
|
731
774
|
end
|
|
732
775
|
|
|
776
|
+
# Allows configuring middleware used by `Rage::Deferred`. See {MiddlewareRegistry} for details on available methods.
|
|
777
|
+
# @example
|
|
778
|
+
# Rage.configure do
|
|
779
|
+
# config.deferred.enqueue_middleware.use MyEnqueueMiddleware
|
|
780
|
+
# config.deferred.enqueue_middleware.insert_before MyEnqueueMiddleware, MyLoggingMiddleware
|
|
781
|
+
# end
|
|
782
|
+
class Middleware < Rage::Configuration::MiddlewareRegistry
|
|
783
|
+
private
|
|
784
|
+
|
|
785
|
+
def validate!(_, middleware)
|
|
786
|
+
unless middleware.is_a?(Class)
|
|
787
|
+
raise ArgumentError, "Deferred middleware has to be a class"
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
unless middleware.method_defined?(:call)
|
|
791
|
+
raise ArgumentError, "Deferred middleware has to implement the `#call` method"
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
# Configure enqueue middleware used by `Rage::Deferred`.
|
|
797
|
+
# See {EnqueueMiddlewareInterface} for details on the arguments passed to the middleware.
|
|
798
|
+
# @return [Rage::Configuration::Deferred::Middleware]
|
|
799
|
+
# @example
|
|
800
|
+
# Rage.configure do
|
|
801
|
+
# config.deferred.enqueue_middleware.use MyCustomMiddleware
|
|
802
|
+
# end
|
|
803
|
+
def enqueue_middleware
|
|
804
|
+
@enqueue_middleware ||= Middleware.new
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
# Configure perform middleware used by `Rage::Deferred`.
|
|
808
|
+
# See {PerformMiddlewareInterface} for details on the arguments passed to the middleware.
|
|
809
|
+
# @return [Rage::Configuration::Deferred::Middleware]
|
|
810
|
+
# @example
|
|
811
|
+
# Rage.configure do
|
|
812
|
+
# config.deferred.perform_middleware.use MyCustomMiddleware
|
|
813
|
+
# end
|
|
814
|
+
def perform_middleware
|
|
815
|
+
@perform_middleware ||= Middleware.new
|
|
816
|
+
end
|
|
817
|
+
|
|
733
818
|
# @private
|
|
734
819
|
def default_disk_storage_path
|
|
735
820
|
Pathname.new("storage")
|
|
@@ -781,6 +866,65 @@ class Rage::Configuration
|
|
|
781
866
|
end
|
|
782
867
|
end
|
|
783
868
|
|
|
869
|
+
# The class allows configuring telemetry handlers. See {MiddlewareRegistry} for details on available methods.
|
|
870
|
+
# @example
|
|
871
|
+
# Rage.configure do
|
|
872
|
+
# config.telemetry.use MyTelemetryHandler.new
|
|
873
|
+
# end
|
|
874
|
+
# @see Rage::Configuration::MiddlewareRegistry
|
|
875
|
+
# @see Rage::Telemetry
|
|
876
|
+
class Telemetry < MiddlewareRegistry
|
|
877
|
+
# @private
|
|
878
|
+
# @return [Hash{String => Array<Rage::Telemetry::HandlerRef>}] a map of span IDs to handler references
|
|
879
|
+
def handlers_map
|
|
880
|
+
@objects.map(&:first).each_with_object({}) do |handler, memo|
|
|
881
|
+
handlers_map = handler.is_a?(Class) ? handler.handlers_map : handler.class.handlers_map
|
|
882
|
+
|
|
883
|
+
handlers_map.each do |span_id, handler_methods|
|
|
884
|
+
handler_refs = handler_methods.map do |handler_method|
|
|
885
|
+
Rage::Telemetry::HandlerRef[handler, handler_method]
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
if memo[span_id]
|
|
889
|
+
memo[span_id] += handler_refs
|
|
890
|
+
else
|
|
891
|
+
memo[span_id] = handler_refs
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
private
|
|
898
|
+
|
|
899
|
+
def validate!(_, handler)
|
|
900
|
+
is_handler = if handler.is_a?(Class)
|
|
901
|
+
handler.ancestors.include?(Rage::Telemetry::Handler)
|
|
902
|
+
else
|
|
903
|
+
handler.is_a?(Rage::Telemetry::Handler)
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
unless is_handler
|
|
907
|
+
raise ArgumentError, "Cannot add `#{handler}` as a telemetry handler; should inherit `Rage::Telemetry::Handler`"
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
handlers_map = if handler.is_a?(Class)
|
|
911
|
+
handler.handlers_map
|
|
912
|
+
else
|
|
913
|
+
handler.class.handlers_map
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
unless handlers_map&.any?
|
|
917
|
+
raise ArgumentError, "Telemetry handler `#{handler}` does not define any handlers"
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
handlers_map.values.reduce(&:+).each do |handler_method|
|
|
921
|
+
unless handler.respond_to?(handler_method)
|
|
922
|
+
raise ArgumentError, "Telemetry handler `#{handler}` does not implement the `#{handler_method}` handler method"
|
|
923
|
+
end
|
|
924
|
+
end
|
|
925
|
+
end
|
|
926
|
+
end
|
|
927
|
+
|
|
784
928
|
class Session
|
|
785
929
|
# @!attribute key
|
|
786
930
|
# Specify the name of the session cookie.
|
|
@@ -847,6 +991,13 @@ class Rage::Configuration
|
|
|
847
991
|
Rage.__log_processor.add_custom_tags(@log_tags.objects)
|
|
848
992
|
@logger.dynamic_tags = Rage.__log_processor.dynamic_tags
|
|
849
993
|
end
|
|
994
|
+
|
|
995
|
+
if defined?(::Rack::Events) && middleware.include?(::Rack::Events)
|
|
996
|
+
middleware.delete(Rage::BodyFinalizer)
|
|
997
|
+
middleware.insert_before(::Rack::Events, Rage::BodyFinalizer)
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
Rage::Telemetry.__setup if @telemetry
|
|
850
1001
|
end
|
|
851
1002
|
end
|
|
852
1003
|
|
|
@@ -882,3 +1033,66 @@ end
|
|
|
882
1033
|
# def call(severity:, tags:, context:, message:, request_info:)
|
|
883
1034
|
# end
|
|
884
1035
|
# end
|
|
1036
|
+
|
|
1037
|
+
# @!parse [ruby]
|
|
1038
|
+
# # @note This class does not exist at runtime and is used for documentation purposes only. Do not inherit your middleware classes from it.
|
|
1039
|
+
# class EnqueueMiddlewareInterface
|
|
1040
|
+
# # Called whenever a deferred task is enqueued.
|
|
1041
|
+
# #
|
|
1042
|
+
# # The middleware is expected to call `yield` to pass control to the next middleware in the stack. If the middleware does not call `yield`, the task will not be enqueued.
|
|
1043
|
+
# #
|
|
1044
|
+
# # Rage automatically detects which parameters your middleware's `#call` method accepts, and only passes those parameters. You can omit any of the described parameters in your implementation.
|
|
1045
|
+
# #
|
|
1046
|
+
# # @param task_class [Class] the deferred task class
|
|
1047
|
+
# # @param delay [Integer, nil] the delay in seconds before the task is executed
|
|
1048
|
+
# # @param delay_until [Time, Integer, nil] the time at which the task should be executed
|
|
1049
|
+
# # @param phase [:enqueue] the middleware phase. Useful for middlewares that are shared between enqueue and perform phases
|
|
1050
|
+
# # @param args [Array] the positional arguments passed to the task
|
|
1051
|
+
# # @param kwargs [Hash] the keyword arguments passed to the task
|
|
1052
|
+
# # @param context [Hash] the context is serialized together with the task and allows passing data between middlewares without exposing it to the task itself
|
|
1053
|
+
# # @example
|
|
1054
|
+
# # class EncryptArgumentsMiddleware
|
|
1055
|
+
# # def call(args:, kwargs:)
|
|
1056
|
+
# # args.map! { |arg| MyEncryptionSDK.encrypt(arg) }
|
|
1057
|
+
# # kwargs.transform_values! { |value| MyEncryptionSDK.encrypt(value) }
|
|
1058
|
+
# #
|
|
1059
|
+
# # yield
|
|
1060
|
+
# # end
|
|
1061
|
+
# # end
|
|
1062
|
+
# def call(task_class:, delay:, delay_until:, phase:, args:, kwargs:, context:)
|
|
1063
|
+
# end
|
|
1064
|
+
# end
|
|
1065
|
+
|
|
1066
|
+
# @!parse [ruby]
|
|
1067
|
+
# # @note This class does not exist at runtime and is used for documentation purposes only. Do not inherit your middleware classes from it.
|
|
1068
|
+
# class PerformMiddlewareInterface
|
|
1069
|
+
# # Called whenever a deferred task is performed.
|
|
1070
|
+
# #
|
|
1071
|
+
# # The middleware is expected to call `yield` to pass control to the next middleware in the stack. If the middleware does not call `yield`, the task will not be performed.
|
|
1072
|
+
# #
|
|
1073
|
+
# # Rage automatically detects which parameters your middleware's `#call` method accepts, and only passes those parameters. You can omit any of the described parameters in your implementation.
|
|
1074
|
+
# #
|
|
1075
|
+
# # @param task_class [Class] the deferred task class
|
|
1076
|
+
# # @param task [Rage::Deferred::Task] the deferred task instance
|
|
1077
|
+
# # @param phase [:perform] the middleware phase. Useful for middlewares that are shared between enqueue and perform phases
|
|
1078
|
+
# # @param args [Array] the positional arguments passed to the task
|
|
1079
|
+
# # @param kwargs [Hash] the keyword arguments passed to the task
|
|
1080
|
+
# # @param context [Hash] the context is serialized together with the task and allows passing data between middlewares without exposing it to the task itself
|
|
1081
|
+
# # @example
|
|
1082
|
+
# # class DecryptArgumentsMiddleware
|
|
1083
|
+
# # def call(args:, kwargs:)
|
|
1084
|
+
# # args.map! { |arg| MyEncryptionSDK.decrypt(arg) }
|
|
1085
|
+
# # kwargs.transform_values! { |value| MyEncryptionSDK.decrypt(value) }
|
|
1086
|
+
# #
|
|
1087
|
+
# # yield
|
|
1088
|
+
# #
|
|
1089
|
+
# # rescue
|
|
1090
|
+
# # # Re-encrypt the arguments in case of an error
|
|
1091
|
+
# # args.map! { |arg| MyEncryptionSDK.encrypt(arg) }
|
|
1092
|
+
# # kwargs.transform_values! { |value| MyEncryptionSDK.encrypt(value) }
|
|
1093
|
+
# # raise
|
|
1094
|
+
# # end
|
|
1095
|
+
# # end
|
|
1096
|
+
# def call(task_class:, task:, phase:, args:, kwargs:, context:)
|
|
1097
|
+
# end
|
|
1098
|
+
# end
|
data/lib/rage/controller/api.rb
CHANGED
|
@@ -112,56 +112,58 @@ class RageController::API
|
|
|
112
112
|
|
|
113
113
|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
114
114
|
def __run_#{action}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
Rage::Telemetry.tracer.span_controller_action_process(controller: self, params: @__params) do
|
|
116
|
+
#{if query_cache_enabled
|
|
117
|
+
<<~RUBY
|
|
118
|
+
ActiveRecord::Base.connection_pool.enable_query_cache!
|
|
119
|
+
RUBY
|
|
120
|
+
end}
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
#{wrap_parameters_chunk}
|
|
123
|
+
#{before_actions_chunk}
|
|
124
|
+
#{action} unless @__before_callback_rendered
|
|
125
|
+
#{around_actions_end_chunk}
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
#{if !after_actions_chunk.empty?
|
|
128
|
+
<<~RUBY
|
|
129
|
+
unless @__before_callback_rendered
|
|
130
|
+
@__rendered = true
|
|
131
|
+
#{after_actions_chunk}
|
|
132
|
+
end
|
|
133
|
+
RUBY
|
|
134
|
+
end}
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
[@__status, @__headers, @__body]
|
|
136
137
|
|
|
137
|
-
|
|
138
|
+
#{rescue_handlers_chunk}
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
ensure
|
|
141
|
+
#{if query_cache_enabled
|
|
142
|
+
<<~RUBY
|
|
143
|
+
ActiveRecord::Base.connection_pool.disable_query_cache!
|
|
144
|
+
RUBY
|
|
145
|
+
end}
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
#{if should_release_connections
|
|
148
|
+
<<~RUBY
|
|
149
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
|
150
|
+
RUBY
|
|
151
|
+
end}
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
153
|
+
#{if method_defined?(:append_info_to_payload) || private_method_defined?(:append_info_to_payload)
|
|
154
|
+
<<~RUBY
|
|
155
|
+
context = {}
|
|
156
|
+
append_info_to_payload(context)
|
|
157
|
+
|
|
158
|
+
log_context = Thread.current[:rage_logger][:context]
|
|
159
|
+
if log_context.empty?
|
|
160
|
+
Thread.current[:rage_logger][:context] = context
|
|
161
|
+
else
|
|
162
|
+
Thread.current[:rage_logger][:context] = log_context.merge(context)
|
|
163
|
+
end
|
|
164
|
+
RUBY
|
|
165
|
+
end}
|
|
166
|
+
end
|
|
165
167
|
end
|
|
166
168
|
RUBY
|
|
167
169
|
end
|
|
@@ -451,6 +453,9 @@ class RageController::API
|
|
|
451
453
|
@__rendered = false
|
|
452
454
|
end
|
|
453
455
|
|
|
456
|
+
# @private
|
|
457
|
+
attr_reader :__env, :__status, :__headers, :__body
|
|
458
|
+
|
|
454
459
|
# Get the request object. See {Rage::Request}.
|
|
455
460
|
# @return [Rage::Request]
|
|
456
461
|
def request
|
|
@@ -460,7 +465,7 @@ class RageController::API
|
|
|
460
465
|
# Get the response object. See {Rage::Response}.
|
|
461
466
|
# @return [Rage::Response]
|
|
462
467
|
def response
|
|
463
|
-
@response ||= Rage::Response.new(
|
|
468
|
+
@response ||= Rage::Response.new(self)
|
|
464
469
|
end
|
|
465
470
|
|
|
466
471
|
# Get the cookie object. See {Rage::Cookies}.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# The class encapsulates the context associated with a deferred task, and allows to store it without modifying the task instance.
|
|
6
6
|
#
|
|
7
7
|
class Rage::Deferred::Context
|
|
8
|
-
def self.build(task, args, kwargs
|
|
8
|
+
def self.build(task, args, kwargs)
|
|
9
9
|
logger = Thread.current[:rage_logger]
|
|
10
10
|
|
|
11
11
|
[
|
|
@@ -14,35 +14,63 @@ class Rage::Deferred::Context
|
|
|
14
14
|
kwargs.empty? ? nil : kwargs,
|
|
15
15
|
nil,
|
|
16
16
|
logger&.dig(:tags),
|
|
17
|
-
logger&.dig(:context)
|
|
17
|
+
logger&.dig(:context),
|
|
18
|
+
nil
|
|
18
19
|
]
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
# @return [Class] the task class
|
|
21
23
|
def self.get_task(context)
|
|
22
24
|
context[0]
|
|
23
25
|
end
|
|
24
26
|
|
|
27
|
+
# @return [Array, nil] arguments the task was enqueued with
|
|
25
28
|
def self.get_args(context)
|
|
26
29
|
context[1]
|
|
27
30
|
end
|
|
28
31
|
|
|
32
|
+
# @return [Array] arguments the task was enqueued with, creating it if it does not exist
|
|
33
|
+
def self.get_or_create_args(context)
|
|
34
|
+
context[1] ||= []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Hash, nil] keyword arguments the task was enqueued with
|
|
29
38
|
def self.get_kwargs(context)
|
|
30
39
|
context[2]
|
|
31
40
|
end
|
|
32
41
|
|
|
42
|
+
# @return [Hash] keyword arguments the task was enqueued with, creating it if it does not exist
|
|
43
|
+
def self.get_or_create_kwargs(context)
|
|
44
|
+
context[2] ||= {}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Integer, nil] number of attempts made to process the task
|
|
33
48
|
def self.get_attempts(context)
|
|
34
49
|
context[3]
|
|
35
50
|
end
|
|
36
51
|
|
|
52
|
+
# Increments the number of attempts made to process the task
|
|
37
53
|
def self.inc_attempts(context)
|
|
38
54
|
context[3] = context[3].to_i + 1
|
|
39
55
|
end
|
|
40
56
|
|
|
57
|
+
# @return [Array, nil] log tags associated with the task
|
|
41
58
|
def self.get_log_tags(context)
|
|
42
59
|
context[4]
|
|
43
60
|
end
|
|
44
61
|
|
|
62
|
+
# @return [Hash, nil] log context associated with the task
|
|
45
63
|
def self.get_log_context(context)
|
|
46
64
|
context[5]
|
|
47
65
|
end
|
|
66
|
+
|
|
67
|
+
# @return [Hash, nil] user context associated with the task
|
|
68
|
+
def self.get_user_context(context)
|
|
69
|
+
context[6]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Hash] user context associated with the task, creating it if it does not exist
|
|
73
|
+
def self.get_or_create_user_context(context)
|
|
74
|
+
context[6] ||= {}
|
|
75
|
+
end
|
|
48
76
|
end
|
|
@@ -74,14 +74,24 @@ module Rage::Deferred
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
# @private
|
|
78
|
+
def self.__middleware_chain
|
|
79
|
+
@__middleware_chain ||= MiddlewareChain.new(
|
|
80
|
+
enqueue_middleware: Rage.config.deferred.enqueue_middleware.objects,
|
|
81
|
+
perform_middleware: Rage.config.deferred.perform_middleware.objects
|
|
82
|
+
)
|
|
78
83
|
end
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
# @private
|
|
86
|
+
def self.__initialize
|
|
87
|
+
__middleware_chain
|
|
88
|
+
__load_tasks
|
|
81
89
|
end
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
module Backends
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class PushTimeout < StandardError
|
|
85
95
|
end
|
|
86
96
|
end
|
|
87
97
|
|
|
@@ -89,11 +99,13 @@ require_relative "task"
|
|
|
89
99
|
require_relative "queue"
|
|
90
100
|
require_relative "proxy"
|
|
91
101
|
require_relative "context"
|
|
102
|
+
require_relative "metadata"
|
|
103
|
+
require_relative "middleware_chain"
|
|
92
104
|
require_relative "backends/disk"
|
|
93
105
|
require_relative "backends/nil"
|
|
94
106
|
|
|
95
107
|
if Iodine.running?
|
|
96
|
-
Rage::Deferred.
|
|
108
|
+
Rage::Deferred.__initialize
|
|
97
109
|
else
|
|
98
|
-
Iodine.on_state(:on_start) { Rage::Deferred.
|
|
110
|
+
Iodine.on_state(:on_start) { Rage::Deferred.__initialize }
|
|
99
111
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Provides metadata about the current deferred task execution.
|
|
5
|
+
#
|
|
6
|
+
class Rage::Deferred::Metadata
|
|
7
|
+
class << self
|
|
8
|
+
# Returns the current attempt number.
|
|
9
|
+
# @return [Integer] the current attempt number (1 for the first run)
|
|
10
|
+
def attempts
|
|
11
|
+
Rage::Deferred::Context.get_attempts(context).to_i + 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns the number of retries that have occurred for the current task.
|
|
15
|
+
# @return [Integer] the number of retries (0 on first run, 1+ on retries)
|
|
16
|
+
def retries
|
|
17
|
+
attempts - 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Checks whether this is a retry execution.
|
|
21
|
+
# @return [Boolean] `true` if this is a retry, `false` if this is the first run
|
|
22
|
+
def retrying?
|
|
23
|
+
attempts > 1
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Checks whether the task will be retried if the current execution fails.
|
|
27
|
+
# @return [Boolean] `true` if a failure will schedule another attempt, `false` otherwise
|
|
28
|
+
def will_retry?
|
|
29
|
+
task = Rage::Deferred::Context.get_task(context)
|
|
30
|
+
task.__should_retry?(attempts)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def context
|
|
36
|
+
Fiber[Rage::Deferred::Task::CONTEXT_KEY]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rage::Deferred::MiddlewareChain
|
|
4
|
+
def initialize(enqueue_middleware:, perform_middleware:)
|
|
5
|
+
@enqueue_middleware = enqueue_middleware
|
|
6
|
+
@perform_middleware = perform_middleware
|
|
7
|
+
|
|
8
|
+
build_enqueue_chain!
|
|
9
|
+
build_perform_chain!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def build_enqueue_chain!
|
|
15
|
+
raw_arguments = {
|
|
16
|
+
phase: ":enqueue",
|
|
17
|
+
args: "Rage::Deferred::Context.get_or_create_args(context)",
|
|
18
|
+
kwargs: "Rage::Deferred::Context.get_or_create_kwargs(context)",
|
|
19
|
+
context: "Rage::Deferred::Context.get_or_create_user_context(context)",
|
|
20
|
+
task_class: "Rage::Deferred::Context.get_task(context)",
|
|
21
|
+
delay: "delay",
|
|
22
|
+
delay_until: "delay_until"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
self.class.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
26
|
+
def with_enqueue_middleware(context, delay:, delay_until:)
|
|
27
|
+
#{build_middleware_chain(:@enqueue_middleware, raw_arguments)}
|
|
28
|
+
end
|
|
29
|
+
RUBY
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def build_perform_chain!
|
|
33
|
+
raw_arguments = {
|
|
34
|
+
phase: ":perform",
|
|
35
|
+
args: "Rage::Deferred::Context.get_or_create_args(context)",
|
|
36
|
+
kwargs: "Rage::Deferred::Context.get_or_create_kwargs(context)",
|
|
37
|
+
context: "Rage::Deferred::Context.get_or_create_user_context(context)",
|
|
38
|
+
task_class: "task.class",
|
|
39
|
+
task: "task"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
self.class.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
43
|
+
def with_perform_middleware(context, task:)
|
|
44
|
+
#{build_middleware_chain(:@perform_middleware, raw_arguments)}
|
|
45
|
+
end
|
|
46
|
+
RUBY
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def build_middleware_chain(middlewares_var, raw_arguments)
|
|
50
|
+
middlewares = instance_variable_get(middlewares_var)
|
|
51
|
+
i = middlewares.length
|
|
52
|
+
|
|
53
|
+
middlewares.reverse.inject("yield") do |memo, middleware_with_args|
|
|
54
|
+
middleware, _, _ = middleware_with_args
|
|
55
|
+
arguments = Rage::Internal.build_arguments(middleware.instance_method(:call), raw_arguments)
|
|
56
|
+
i -= 1
|
|
57
|
+
|
|
58
|
+
<<~RUBY
|
|
59
|
+
middleware, args, block = #{middlewares_var}[#{i}]
|
|
60
|
+
|
|
61
|
+
middleware.new(*args, &block).call(#{arguments}) do
|
|
62
|
+
#{memo}
|
|
63
|
+
end
|
|
64
|
+
RUBY
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|