patient_http-solid_queue 1.2.0 → 1.3.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/ARCHITECTURE.md +10 -8
- data/CHANGELOG.md +30 -0
- data/README.md +303 -234
- data/VERSION +1 -1
- data/lib/generators/patient_http/solid_queue/install_generator.rb +248 -0
- data/lib/generators/patient_http/solid_queue/templates/initializer.rb +102 -0
- data/lib/patient_http/solid_queue/callback_job.rb +14 -7
- data/lib/patient_http/solid_queue/configuration.rb +178 -76
- data/lib/patient_http/solid_queue/context.rb +10 -9
- data/lib/patient_http/solid_queue/engine.rb +3 -1
- data/lib/patient_http/solid_queue/gc_lock.rb +4 -4
- data/lib/patient_http/solid_queue/inflight_request.rb +4 -4
- data/lib/patient_http/solid_queue/lifecycle_hooks.rb +11 -1
- data/lib/patient_http/solid_queue/process_registration.rb +4 -4
- data/lib/patient_http/solid_queue/processor_observer.rb +43 -12
- data/lib/patient_http/solid_queue/record.rb +2 -1
- data/lib/patient_http/solid_queue/request_executor.rb +37 -20
- data/lib/patient_http/solid_queue/request_job.rb +21 -12
- data/lib/patient_http/solid_queue/task_handler.rb +44 -12
- data/lib/patient_http/solid_queue/task_monitor.rb +91 -73
- data/lib/patient_http/solid_queue/task_monitor_thread.rb +15 -13
- data/lib/patient_http/solid_queue.rb +274 -121
- data/patient_http-solid_queue.gemspec +2 -2
- metadata +7 -5
- /data/db/migrate/{20260216000000_create_solid_queue_async_http_tables.rb → 20260216000000_create_patient_http_solid_queue_tables.rb} +0 -0
|
@@ -3,40 +3,43 @@
|
|
|
3
3
|
require "patient_http"
|
|
4
4
|
require "solid_queue"
|
|
5
5
|
|
|
6
|
-
# Main module for the Solid Queue Async HTTP gem.
|
|
7
|
-
#
|
|
8
|
-
# This gem provides a mechanism to offload long-running HTTP requests from Solid Queue workers
|
|
9
|
-
# to a dedicated async I/O processor running in the same process, freeing worker threads
|
|
10
|
-
# immediately while HTTP requests are in flight.
|
|
11
|
-
#
|
|
12
|
-
# == Usage
|
|
13
|
-
#
|
|
14
|
-
# request = PatientHttp::Request.new(:get, "https://api.example.com/users/123")
|
|
15
|
-
# PatientHttp::SolidQueue.execute(
|
|
16
|
-
# request,
|
|
17
|
-
# callback: MyCallback,
|
|
18
|
-
# callback_args: {user_id: 123}
|
|
19
|
-
# )
|
|
20
|
-
#
|
|
21
|
-
# Define a callback service class with +on_complete+ and +on_error+ methods:
|
|
22
|
-
#
|
|
23
|
-
# class MyCallback
|
|
24
|
-
# def on_complete(response)
|
|
25
|
-
# user_id = response.callback_args[:user_id]
|
|
26
|
-
# User.find(user_id).update!(data: response.json)
|
|
27
|
-
# end
|
|
28
|
-
#
|
|
29
|
-
# def on_error(error)
|
|
30
|
-
# Rails.logger.error("Request failed: #{error.message}")
|
|
31
|
-
# end
|
|
32
|
-
# end
|
|
33
6
|
module PatientHttp
|
|
7
|
+
# Runs HTTP requests from Solid Queue jobs on an async I/O processor.
|
|
8
|
+
#
|
|
9
|
+
# The processor runs in the Solid Queue worker process. Worker threads hand
|
|
10
|
+
# off long-running HTTP requests to it and are free to run other jobs while
|
|
11
|
+
# the requests are in flight.
|
|
12
|
+
#
|
|
13
|
+
# This module manages the processors for the current process. It starts one
|
|
14
|
+
# processor for each configured processor profile when a Solid Queue worker
|
|
15
|
+
# starts, and stops them when the worker stops. All processors in a process
|
|
16
|
+
# share one crash-recovery monitor.
|
|
17
|
+
#
|
|
18
|
+
# @example Make a request
|
|
19
|
+
# PatientHttp.get(
|
|
20
|
+
# "https://api.example.com/users/123",
|
|
21
|
+
# callback: MyCallback,
|
|
22
|
+
# callback_args: {user_id: 123}
|
|
23
|
+
# )
|
|
24
|
+
#
|
|
25
|
+
# @example Define a callback service
|
|
26
|
+
# class MyCallback
|
|
27
|
+
# def on_complete(response)
|
|
28
|
+
# user_id = response.callback_args[:user_id]
|
|
29
|
+
# User.find(user_id).update!(data: response.json)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# def on_error(error)
|
|
33
|
+
# Rails.logger.error("Request failed: #{error.message}")
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
34
36
|
module SolidQueue
|
|
37
|
+
# The gem version.
|
|
35
38
|
VERSION = File.read(File.join(__dir__, "../../VERSION")).strip
|
|
36
39
|
|
|
37
|
-
# Raised when the crash
|
|
38
|
-
# written. The
|
|
39
|
-
# record, and the job retries.
|
|
40
|
+
# Raised when the crash recovery registry entry for a request can't be
|
|
41
|
+
# written. The processor rejects the request instead of accepting it
|
|
42
|
+
# without a durable record, and the job retries.
|
|
40
43
|
class RegistrationError < StandardError; end
|
|
41
44
|
|
|
42
45
|
autoload :CallbackJob, File.join(__dir__, "solid_queue/callback_job")
|
|
@@ -55,7 +58,6 @@ module PatientHttp
|
|
|
55
58
|
autoload :TaskMonitorThread, File.join(__dir__, "solid_queue/task_monitor_thread")
|
|
56
59
|
|
|
57
60
|
@processors = {}
|
|
58
|
-
@configuration = nil
|
|
59
61
|
@after_completion_callbacks = []
|
|
60
62
|
@after_error_callbacks = []
|
|
61
63
|
@external_storage = nil
|
|
@@ -65,105 +67,185 @@ module PatientHttp
|
|
|
65
67
|
@monitor_thread = nil
|
|
66
68
|
|
|
67
69
|
class << self
|
|
68
|
-
|
|
70
|
+
# Sets the configuration. Intended for tests.
|
|
71
|
+
#
|
|
72
|
+
# `PatientHttp` stores the configuration, so this method assigns it there.
|
|
73
|
+
#
|
|
74
|
+
# @param config [Configuration, nil] The configuration, or `nil` to build a
|
|
75
|
+
# new one on next use.
|
|
76
|
+
# @return [void]
|
|
77
|
+
def configuration=(config)
|
|
78
|
+
PatientHttp.default_configuration = config
|
|
79
|
+
end
|
|
69
80
|
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
81
|
+
# Yields the configuration to a block.
|
|
82
|
+
#
|
|
83
|
+
# Every call yields the same configuration object, so options accumulate.
|
|
84
|
+
# Several initializers can each set options without overwriting one
|
|
85
|
+
# another. `PatientHttp.configure` calls this method, so application code
|
|
86
|
+
# can use either one.
|
|
74
87
|
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
88
|
+
# Configure the gem before the processors start. Running processors use
|
|
89
|
+
# this same configuration object, so an option changed while they run
|
|
90
|
+
# takes effect partway through the requests they're handling, and a
|
|
91
|
+
# processor profile declared while they run isn't started until the
|
|
92
|
+
# next restart. Changing the configuration while processors run logs a
|
|
93
|
+
# warning.
|
|
94
|
+
#
|
|
95
|
+
# @example
|
|
96
|
+
# PatientHttp.configure do |config|
|
|
97
|
+
# config.max_connections = 512
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
100
|
+
# @yield [config] The block that sets configuration options.
|
|
101
|
+
# @yieldparam config [Configuration] The configuration.
|
|
102
|
+
# @return [Configuration] The configuration.
|
|
77
103
|
def configure
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
104
|
+
config = configuration
|
|
105
|
+
if block_given?
|
|
106
|
+
if running?
|
|
107
|
+
config.logger&.warn(
|
|
108
|
+
"[PatientHttp::SolidQueue] Configuration changed while processors are running; " \
|
|
109
|
+
"configure the gem before the Solid Queue worker starts."
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
yield(config)
|
|
113
|
+
end
|
|
114
|
+
config
|
|
85
115
|
end
|
|
86
116
|
|
|
87
|
-
#
|
|
117
|
+
# Returns the configuration for this process, and creates it on first use.
|
|
88
118
|
#
|
|
89
|
-
#
|
|
119
|
+
# `PatientHttp` stores the configuration, so this method and
|
|
120
|
+
# `PatientHttp.configuration` return the same object. As a result, secrets
|
|
121
|
+
# registered with `PatientHttp.register_secret` reach the configuration
|
|
122
|
+
# that the processors use, regardless of load order.
|
|
123
|
+
#
|
|
124
|
+
# @return [Configuration] The configuration.
|
|
90
125
|
def configuration
|
|
91
|
-
|
|
126
|
+
PatientHttp.configuration
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Builds a new configuration. `PatientHttp` calls this method when it
|
|
130
|
+
# creates the configuration for this process.
|
|
131
|
+
#
|
|
132
|
+
# @return [Configuration] The new configuration.
|
|
133
|
+
# @api private
|
|
134
|
+
def new_configuration
|
|
135
|
+
Configuration.new
|
|
92
136
|
end
|
|
93
137
|
|
|
94
|
-
#
|
|
138
|
+
# Resets the configuration to the defaults. Intended for tests.
|
|
95
139
|
#
|
|
96
|
-
# @return [Configuration]
|
|
140
|
+
# @return [Configuration] The new configuration.
|
|
97
141
|
def reset_configuration!
|
|
98
|
-
|
|
99
|
-
@external_storage = nil
|
|
142
|
+
PatientHttp.default_configuration = nil
|
|
100
143
|
configuration
|
|
101
144
|
end
|
|
102
145
|
|
|
103
|
-
#
|
|
146
|
+
# Registers a block to run after each request completes. Use it for
|
|
147
|
+
# monitoring. Blocks run in the order they're registered.
|
|
148
|
+
#
|
|
149
|
+
# @example
|
|
150
|
+
# PatientHttp::SolidQueue.after_completion do |response|
|
|
151
|
+
# StatsD.timing("patient_http.duration", response.duration * 1000)
|
|
152
|
+
# end
|
|
104
153
|
#
|
|
105
|
-
# @yield [response] block to
|
|
106
|
-
# @yieldparam response [PatientHttp::Response]
|
|
154
|
+
# @yield [response] The block to run.
|
|
155
|
+
# @yieldparam response [PatientHttp::Response] The HTTP response.
|
|
156
|
+
# @return [void]
|
|
107
157
|
def after_completion(&block)
|
|
108
158
|
@after_completion_callbacks << block
|
|
109
159
|
end
|
|
110
160
|
|
|
111
|
-
#
|
|
161
|
+
# Registers a block to run after each request error. Use it for
|
|
162
|
+
# monitoring. Blocks run in the order they're registered.
|
|
163
|
+
#
|
|
164
|
+
# @example
|
|
165
|
+
# PatientHttp::SolidQueue.after_error do |error|
|
|
166
|
+
# StatsD.increment("patient_http.error.#{error.error_type}")
|
|
167
|
+
# end
|
|
112
168
|
#
|
|
113
|
-
# @yield [error] block to
|
|
114
|
-
# @yieldparam error [PatientHttp::Error]
|
|
169
|
+
# @yield [error] The block to run.
|
|
170
|
+
# @yieldparam error [PatientHttp::Error] The error.
|
|
171
|
+
# @return [void]
|
|
115
172
|
def after_error(&block)
|
|
116
173
|
@after_error_callbacks << block
|
|
117
174
|
end
|
|
118
175
|
|
|
119
|
-
#
|
|
176
|
+
# Returns whether any processor is running.
|
|
120
177
|
#
|
|
121
|
-
# @return [Boolean]
|
|
178
|
+
# @return [Boolean] `true` if any processor is running.
|
|
122
179
|
def running?
|
|
123
180
|
@processors.values.any?(&:running?)
|
|
124
181
|
end
|
|
125
182
|
|
|
126
|
-
#
|
|
183
|
+
# Returns whether any processor is draining. A draining processor doesn't
|
|
184
|
+
# accept new requests but continues to run in-flight requests.
|
|
127
185
|
#
|
|
128
|
-
# @return [Boolean]
|
|
186
|
+
# @return [Boolean] `true` if any processor is draining.
|
|
129
187
|
def draining?
|
|
130
188
|
@processors.values.any?(&:draining?)
|
|
131
189
|
end
|
|
132
190
|
|
|
133
|
-
#
|
|
191
|
+
# Returns whether any processor is stopping.
|
|
134
192
|
#
|
|
135
|
-
# @return [Boolean]
|
|
193
|
+
# @return [Boolean] `true` if any processor is stopping.
|
|
136
194
|
def stopping?
|
|
137
195
|
@processors.values.any?(&:stopping?)
|
|
138
196
|
end
|
|
139
197
|
|
|
140
|
-
#
|
|
198
|
+
# Returns whether all processors are stopped.
|
|
141
199
|
#
|
|
142
|
-
# @return [Boolean]
|
|
200
|
+
# @return [Boolean] `true` if all processors are stopped or none has
|
|
201
|
+
# started.
|
|
143
202
|
def stopped?
|
|
144
203
|
@processors.values.all?(&:stopped?)
|
|
145
204
|
end
|
|
146
205
|
|
|
147
|
-
#
|
|
206
|
+
# Returns the external storage for request and result payloads. The
|
|
207
|
+
# storage is rebuilt when the configuration is replaced.
|
|
148
208
|
#
|
|
149
|
-
# @return [PatientHttp::ExternalStorage]
|
|
209
|
+
# @return [PatientHttp::ExternalStorage] The external storage.
|
|
150
210
|
# @api private
|
|
151
211
|
def external_storage
|
|
152
|
-
|
|
212
|
+
config = configuration
|
|
213
|
+
storage = @external_storage
|
|
214
|
+
unless storage&.config.equal?(config)
|
|
215
|
+
storage = PatientHttp::ExternalStorage.new(config)
|
|
216
|
+
@external_storage = storage
|
|
217
|
+
end
|
|
218
|
+
storage
|
|
153
219
|
end
|
|
154
220
|
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
# @
|
|
165
|
-
# @
|
|
166
|
-
|
|
221
|
+
# Runs an HTTP request asynchronously and calls the callback service with
|
|
222
|
+
# the result.
|
|
223
|
+
#
|
|
224
|
+
# Application code normally uses the `PatientHttp` module methods instead,
|
|
225
|
+
# such as `PatientHttp.get`, `PatientHttp.post`, or the
|
|
226
|
+
# PatientHttp::RequestHelper mixin. Those methods take the same options and
|
|
227
|
+
# keep application code independent of the job system. They call this
|
|
228
|
+
# method through the registered request handler.
|
|
229
|
+
#
|
|
230
|
+
# @param request [PatientHttp::Request] The HTTP request.
|
|
231
|
+
# @param callback [Class, String] The callback service class, or its fully
|
|
232
|
+
# qualified name. The class must define `on_complete` and `on_error`
|
|
233
|
+
# instance methods.
|
|
234
|
+
# @param callback_args [#to_h, nil] The arguments to pass to the callback.
|
|
235
|
+
# Values must be JSON-native types: `nil`, `true`, `false`, String,
|
|
236
|
+
# Integer, Float, Array, or Hash. Hash keys are converted to strings. The
|
|
237
|
+
# callback reads the arguments from `response.callback_args` or
|
|
238
|
+
# `error.callback_args` with symbol or string keys.
|
|
239
|
+
# @param raise_error_responses [Boolean, nil] Whether to treat non-2xx
|
|
240
|
+
# responses as errors and call `on_error` instead of `on_complete`. If
|
|
241
|
+
# `nil`, uses the `raise_error_responses` configuration option.
|
|
242
|
+
# @param processor [Symbol, String, nil] The name of the processor profile
|
|
243
|
+
# that runs the request. If `nil`, uses the processor set on the request,
|
|
244
|
+
# then `:default`.
|
|
245
|
+
# @return [String] The request ID.
|
|
246
|
+
# @raise [PatientHttp::UnknownProcessorError] If the processor profile
|
|
247
|
+
# isn't configured.
|
|
248
|
+
def execute(request, callback:, callback_args: nil, raise_error_responses: nil, processor: nil)
|
|
167
249
|
PatientHttp::CallbackValidator.validate!(callback)
|
|
168
250
|
callback_name = callback.is_a?(Class) ? callback.name : callback.to_s
|
|
169
251
|
callback_args = PatientHttp::CallbackValidator.validate_callback_args(callback_args)
|
|
@@ -173,14 +255,19 @@ module PatientHttp
|
|
|
173
255
|
# Catch a misspelled profile name at the call site. A job that names an
|
|
174
256
|
# unconfigured profile is retried instead, which covers rolling deploys
|
|
175
257
|
# where the executing process is older than the enqueueing one.
|
|
176
|
-
|
|
258
|
+
profile_config = processor_config_for(processor_name)
|
|
259
|
+
unless profile_config
|
|
177
260
|
raise PatientHttp::UnknownProcessorError.new("No processor profile configured for #{processor_name.inspect}")
|
|
178
261
|
end
|
|
179
262
|
|
|
263
|
+
# The PatientHttp module methods pass nil when the caller did not ask for a
|
|
264
|
+
# specific behavior, so fall back to the processor profile's setting.
|
|
265
|
+
raise_error_responses = profile_config.raise_error_responses if raise_error_responses.nil?
|
|
266
|
+
|
|
180
267
|
encrypted = encrypt(request.as_json)
|
|
181
268
|
|
|
182
269
|
data = if external_storage.enabled?
|
|
183
|
-
external_storage.store(encrypted, max_size:
|
|
270
|
+
external_storage.store(encrypted, max_size: profile_config.payload_store_threshold)
|
|
184
271
|
else
|
|
185
272
|
encrypted
|
|
186
273
|
end
|
|
@@ -190,8 +277,9 @@ module PatientHttp
|
|
|
190
277
|
request_id
|
|
191
278
|
end
|
|
192
279
|
|
|
193
|
-
#
|
|
194
|
-
# the
|
|
280
|
+
# Starts a processor for each configured processor profile. Also starts
|
|
281
|
+
# the crash-recovery monitor that the processors share. The Solid Queue
|
|
282
|
+
# lifecycle hooks call this method when a Solid Queue worker starts.
|
|
195
283
|
#
|
|
196
284
|
# @return [void]
|
|
197
285
|
def start
|
|
@@ -226,7 +314,8 @@ module PatientHttp
|
|
|
226
314
|
register_handler
|
|
227
315
|
end
|
|
228
316
|
|
|
229
|
-
#
|
|
317
|
+
# Drains all processors. A draining processor doesn't accept new requests
|
|
318
|
+
# but continues to run in-flight requests.
|
|
230
319
|
#
|
|
231
320
|
# @return [void]
|
|
232
321
|
def quiet
|
|
@@ -237,15 +326,14 @@ module PatientHttp
|
|
|
237
326
|
end
|
|
238
327
|
end
|
|
239
328
|
|
|
240
|
-
#
|
|
329
|
+
# Stops all processors and the services they share. The Solid Queue
|
|
330
|
+
# lifecycle hooks call this method when a Solid Queue worker stops.
|
|
241
331
|
#
|
|
242
|
-
# @param timeout [Float, nil] maximum
|
|
332
|
+
# @param timeout [Float, nil] The maximum number of seconds to wait for
|
|
333
|
+
# in-flight requests to finish. If `nil`, uses the `shutdown_timeout`
|
|
334
|
+
# configuration option.
|
|
243
335
|
# @return [void]
|
|
244
336
|
def stop(timeout: nil)
|
|
245
|
-
if @request_handler
|
|
246
|
-
PatientHttp.unregister_handler(@request_handler)
|
|
247
|
-
end
|
|
248
|
-
|
|
249
337
|
@lifecycle_mutex.synchronize do
|
|
250
338
|
return if @processors.empty?
|
|
251
339
|
|
|
@@ -255,27 +343,32 @@ module PatientHttp
|
|
|
255
343
|
end
|
|
256
344
|
end
|
|
257
345
|
|
|
258
|
-
#
|
|
346
|
+
# Stops all processors and resets all state. Intended for tests.
|
|
259
347
|
#
|
|
260
348
|
# @return [void]
|
|
261
349
|
# @api private
|
|
262
350
|
def reset!
|
|
263
|
-
if @request_handler
|
|
264
|
-
PatientHttp.unregister_handler(@request_handler)
|
|
265
|
-
end
|
|
266
351
|
@lifecycle_mutex.synchronize do
|
|
267
352
|
stop_processors(timeout: 0)
|
|
268
353
|
@processors = {}
|
|
269
354
|
shutdown_shared_services
|
|
270
355
|
end
|
|
271
|
-
@configuration = nil
|
|
272
356
|
@external_storage = nil
|
|
273
357
|
@after_completion_callbacks = []
|
|
274
358
|
@after_error_callbacks = []
|
|
359
|
+
PatientHttp.default_configuration = nil
|
|
360
|
+
# Restore the state a freshly loaded process is in: the handler is
|
|
361
|
+
# registered, the configuration is not built yet.
|
|
362
|
+
register_handler
|
|
275
363
|
end
|
|
276
364
|
|
|
277
|
-
#
|
|
278
|
-
#
|
|
365
|
+
# Registers this gem as the request handler for `PatientHttp`.
|
|
366
|
+
#
|
|
367
|
+
# The gem calls this method when it loads. As a result, the `PatientHttp`
|
|
368
|
+
# module methods work in every process that loads the gem, whether or not
|
|
369
|
+
# the process runs a processor. The handler stays registered for the life
|
|
370
|
+
# of the process. After the processors stop, requests are enqueued in the
|
|
371
|
+
# queue database for another process to run.
|
|
279
372
|
#
|
|
280
373
|
# @return [void]
|
|
281
374
|
def register_handler
|
|
@@ -291,9 +384,9 @@ module PatientHttp
|
|
|
291
384
|
PatientHttp.register_handler(@request_handler)
|
|
292
385
|
end
|
|
293
386
|
|
|
294
|
-
#
|
|
387
|
+
# Calls the blocks registered with {after_completion}.
|
|
295
388
|
#
|
|
296
|
-
# @param response [PatientHttp::Response]
|
|
389
|
+
# @param response [PatientHttp::Response] The HTTP response.
|
|
297
390
|
# @return [void]
|
|
298
391
|
# @api private
|
|
299
392
|
def invoke_completion_callbacks(response)
|
|
@@ -304,9 +397,9 @@ module PatientHttp
|
|
|
304
397
|
end
|
|
305
398
|
end
|
|
306
399
|
|
|
307
|
-
#
|
|
400
|
+
# Calls the blocks registered with {after_error}.
|
|
308
401
|
#
|
|
309
|
-
# @param error [PatientHttp::Error]
|
|
402
|
+
# @param error [PatientHttp::Error] The error.
|
|
310
403
|
# @return [void]
|
|
311
404
|
# @api private
|
|
312
405
|
def invoke_error_callbacks(error)
|
|
@@ -317,34 +410,41 @@ module PatientHttp
|
|
|
317
410
|
end
|
|
318
411
|
end
|
|
319
412
|
|
|
320
|
-
#
|
|
413
|
+
# Encrypts data with the configured encryptor.
|
|
321
414
|
#
|
|
322
|
-
# @param value [
|
|
323
|
-
# @return [
|
|
415
|
+
# @param value [Hash] The data to encrypt.
|
|
416
|
+
# @return [Hash] The encrypted data, or the original data if encryption
|
|
417
|
+
# isn't configured.
|
|
418
|
+
# @api private
|
|
324
419
|
def encrypt(value)
|
|
325
420
|
configuration.encryptor.encrypt(value)
|
|
326
421
|
end
|
|
327
422
|
|
|
328
|
-
#
|
|
423
|
+
# Decrypts data with the configured encryptor.
|
|
329
424
|
#
|
|
330
|
-
# @param value [
|
|
331
|
-
# @return [
|
|
425
|
+
# @param value [Hash] The data to decrypt.
|
|
426
|
+
# @return [Hash] The decrypted data, or the original data if it isn't
|
|
427
|
+
# encrypted.
|
|
428
|
+
# @api private
|
|
332
429
|
def decrypt(value)
|
|
333
430
|
configuration.encryptor.decrypt(value)
|
|
334
431
|
end
|
|
335
432
|
|
|
336
|
-
# Returns
|
|
433
|
+
# Returns the processor with the given name.
|
|
337
434
|
#
|
|
338
|
-
# @param name [Symbol, String]
|
|
339
|
-
# @return [PatientHttp::Processor, nil]
|
|
435
|
+
# @param name [Symbol, String] The processor name.
|
|
436
|
+
# @return [PatientHttp::Processor, nil] The processor, or `nil` if no
|
|
437
|
+
# processor has that name.
|
|
340
438
|
# @api private
|
|
341
439
|
def processor(name = :default)
|
|
342
440
|
@processors[name.to_sym]
|
|
343
441
|
end
|
|
344
442
|
|
|
345
|
-
#
|
|
443
|
+
# Sets the default processor. Intended for tests.
|
|
346
444
|
#
|
|
347
|
-
# @param value [PatientHttp::Processor, nil]
|
|
445
|
+
# @param value [PatientHttp::Processor, nil] The processor, or `nil` to
|
|
446
|
+
# remove it.
|
|
447
|
+
# @return [void]
|
|
348
448
|
# @api private
|
|
349
449
|
def processor=(value)
|
|
350
450
|
if value.nil?
|
|
@@ -354,24 +454,67 @@ module PatientHttp
|
|
|
354
454
|
end
|
|
355
455
|
end
|
|
356
456
|
|
|
457
|
+
# Returns the configuration for a processor profile. Uses the running
|
|
458
|
+
# processor's configuration if there is one.
|
|
459
|
+
#
|
|
460
|
+
# @param name [Symbol, String] The processor name.
|
|
461
|
+
# @return [PatientHttp::Configuration, nil] The configuration for the
|
|
462
|
+
# profile, or `nil` if no processor has that name and the profile isn't
|
|
463
|
+
# declared.
|
|
464
|
+
# @api private
|
|
465
|
+
def processor_config_for(name)
|
|
466
|
+
key = name.to_s
|
|
467
|
+
return nil if key.empty?
|
|
468
|
+
|
|
469
|
+
running = @processors[key.to_sym]
|
|
470
|
+
return running.config if running
|
|
471
|
+
|
|
472
|
+
config = configuration
|
|
473
|
+
config.processor_config(key) if config.processor_options(key)
|
|
474
|
+
end
|
|
475
|
+
|
|
357
476
|
private
|
|
358
477
|
|
|
359
|
-
#
|
|
360
|
-
#
|
|
478
|
+
# Stops every processor.
|
|
479
|
+
#
|
|
480
|
+
# Each processor waits up to the full timeout for its in-flight requests,
|
|
481
|
+
# so the processors stop in parallel. Stopping them one at a time would
|
|
482
|
+
# multiply the shutdown time by the number of processors. An error from
|
|
483
|
+
# one processor is logged so that the other processors and the shared
|
|
484
|
+
# services still shut down.
|
|
485
|
+
#
|
|
486
|
+
# @param timeout [Float, nil] The maximum number of seconds to wait for
|
|
487
|
+
# in-flight requests.
|
|
488
|
+
# @return [void]
|
|
361
489
|
def stop_processors(timeout:)
|
|
362
490
|
processors = @processors.values
|
|
363
491
|
return if processors.empty?
|
|
364
492
|
|
|
365
493
|
if processors.one?
|
|
366
|
-
processors.first
|
|
494
|
+
stop_processor(processors.first, timeout)
|
|
367
495
|
else
|
|
368
|
-
processors.map { |processor| Thread.new { processor
|
|
496
|
+
processors.map { |processor| Thread.new { stop_processor(processor, timeout) } }.each(&:join)
|
|
369
497
|
end
|
|
370
498
|
end
|
|
371
499
|
|
|
372
|
-
#
|
|
373
|
-
#
|
|
374
|
-
#
|
|
500
|
+
# Stops a processor and logs any error instead of raising it.
|
|
501
|
+
#
|
|
502
|
+
# @param processor [PatientHttp::Processor] The processor.
|
|
503
|
+
# @param timeout [Float, nil] The maximum number of seconds to wait for
|
|
504
|
+
# in-flight requests.
|
|
505
|
+
# @return [void]
|
|
506
|
+
def stop_processor(processor, timeout)
|
|
507
|
+
processor.stop(timeout: timeout)
|
|
508
|
+
rescue => e
|
|
509
|
+
configuration.logger&.error(
|
|
510
|
+
"[PatientHttp::SolidQueue] Failed to stop processor #{processor.name}: #{e.inspect}"
|
|
511
|
+
)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Stops the monitor thread and removes this process from the registry. The
|
|
515
|
+
# caller must hold the lifecycle mutex and must stop all processors first.
|
|
516
|
+
#
|
|
517
|
+
# @return [void]
|
|
375
518
|
def shutdown_shared_services
|
|
376
519
|
@monitor_thread&.stop
|
|
377
520
|
@monitor_thread = nil
|
|
@@ -390,4 +533,14 @@ if defined?(::Rails::Engine)
|
|
|
390
533
|
require_relative "solid_queue/engine"
|
|
391
534
|
end
|
|
392
535
|
|
|
536
|
+
# Wire the gem up as soon as it is loaded so that no setup step is required to
|
|
537
|
+
# start making requests:
|
|
538
|
+
#
|
|
539
|
+
# - the request handler is registered, so PatientHttp.get and friends work in
|
|
540
|
+
# every process that requires the gem, configured or not;
|
|
541
|
+
# - PatientHttp.configure and PatientHttp.configuration resolve to this gem's
|
|
542
|
+
# configuration, so applications never have to name the integration;
|
|
543
|
+
# - the Solid Queue lifecycle hooks start and stop the processor with the worker.
|
|
544
|
+
PatientHttp::SolidQueue.register_handler
|
|
545
|
+
PatientHttp.register_configuration_provider(PatientHttp::SolidQueue)
|
|
393
546
|
PatientHttp::SolidQueue::LifecycleHooks.register
|
|
@@ -35,8 +35,8 @@ Gem::Specification.new do |spec|
|
|
|
35
35
|
|
|
36
36
|
spec.require_paths = ["lib"]
|
|
37
37
|
|
|
38
|
-
spec.required_ruby_version = ">= 3.
|
|
38
|
+
spec.required_ruby_version = ">= 3.3"
|
|
39
39
|
|
|
40
|
-
spec.add_dependency "patient_http", ">= 1.
|
|
40
|
+
spec.add_dependency "patient_http", ">= 1.7.0"
|
|
41
41
|
spec.add_dependency "solid_queue", ">= 1.0.0"
|
|
42
42
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: patient_http-solid_queue
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 1.
|
|
18
|
+
version: 1.7.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 1.
|
|
25
|
+
version: 1.7.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: solid_queue
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -48,7 +48,9 @@ files:
|
|
|
48
48
|
- MIT-LICENSE
|
|
49
49
|
- README.md
|
|
50
50
|
- VERSION
|
|
51
|
-
- db/migrate/
|
|
51
|
+
- db/migrate/20260216000000_create_patient_http_solid_queue_tables.rb
|
|
52
|
+
- lib/generators/patient_http/solid_queue/install_generator.rb
|
|
53
|
+
- lib/generators/patient_http/solid_queue/templates/initializer.rb
|
|
52
54
|
- lib/patient_http-solid_queue.rb
|
|
53
55
|
- lib/patient_http/solid_queue.rb
|
|
54
56
|
- lib/patient_http/solid_queue/callback_job.rb
|
|
@@ -82,7 +84,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
82
84
|
requirements:
|
|
83
85
|
- - ">="
|
|
84
86
|
- !ruby/object:Gem::Version
|
|
85
|
-
version: '3.
|
|
87
|
+
version: '3.3'
|
|
86
88
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
89
|
requirements:
|
|
88
90
|
- - ">="
|