patient_http-solid_queue 1.2.1 → 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 +24 -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 -122
- data/patient_http-solid_queue.gemspec +2 -2
- metadata +8 -6
- /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,14 +326,12 @@ 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
|
-
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
# process rather than raise. Only {.reset!} removes the handler.
|
|
246
|
-
#
|
|
247
|
-
# @param timeout [Float, nil] maximum time to wait for in-flight requests to complete
|
|
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.
|
|
248
335
|
# @return [void]
|
|
249
336
|
def stop(timeout: nil)
|
|
250
337
|
@lifecycle_mutex.synchronize do
|
|
@@ -256,27 +343,32 @@ module PatientHttp
|
|
|
256
343
|
end
|
|
257
344
|
end
|
|
258
345
|
|
|
259
|
-
#
|
|
346
|
+
# Stops all processors and resets all state. Intended for tests.
|
|
260
347
|
#
|
|
261
348
|
# @return [void]
|
|
262
349
|
# @api private
|
|
263
350
|
def reset!
|
|
264
|
-
if @request_handler
|
|
265
|
-
PatientHttp.unregister_handler(@request_handler)
|
|
266
|
-
end
|
|
267
351
|
@lifecycle_mutex.synchronize do
|
|
268
352
|
stop_processors(timeout: 0)
|
|
269
353
|
@processors = {}
|
|
270
354
|
shutdown_shared_services
|
|
271
355
|
end
|
|
272
|
-
@configuration = nil
|
|
273
356
|
@external_storage = nil
|
|
274
357
|
@after_completion_callbacks = []
|
|
275
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
|
|
276
363
|
end
|
|
277
364
|
|
|
278
|
-
#
|
|
279
|
-
#
|
|
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.
|
|
280
372
|
#
|
|
281
373
|
# @return [void]
|
|
282
374
|
def register_handler
|
|
@@ -292,9 +384,9 @@ module PatientHttp
|
|
|
292
384
|
PatientHttp.register_handler(@request_handler)
|
|
293
385
|
end
|
|
294
386
|
|
|
295
|
-
#
|
|
387
|
+
# Calls the blocks registered with {after_completion}.
|
|
296
388
|
#
|
|
297
|
-
# @param response [PatientHttp::Response]
|
|
389
|
+
# @param response [PatientHttp::Response] The HTTP response.
|
|
298
390
|
# @return [void]
|
|
299
391
|
# @api private
|
|
300
392
|
def invoke_completion_callbacks(response)
|
|
@@ -305,9 +397,9 @@ module PatientHttp
|
|
|
305
397
|
end
|
|
306
398
|
end
|
|
307
399
|
|
|
308
|
-
#
|
|
400
|
+
# Calls the blocks registered with {after_error}.
|
|
309
401
|
#
|
|
310
|
-
# @param error [PatientHttp::Error]
|
|
402
|
+
# @param error [PatientHttp::Error] The error.
|
|
311
403
|
# @return [void]
|
|
312
404
|
# @api private
|
|
313
405
|
def invoke_error_callbacks(error)
|
|
@@ -318,34 +410,41 @@ module PatientHttp
|
|
|
318
410
|
end
|
|
319
411
|
end
|
|
320
412
|
|
|
321
|
-
#
|
|
413
|
+
# Encrypts data with the configured encryptor.
|
|
322
414
|
#
|
|
323
|
-
# @param value [
|
|
324
|
-
# @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
|
|
325
419
|
def encrypt(value)
|
|
326
420
|
configuration.encryptor.encrypt(value)
|
|
327
421
|
end
|
|
328
422
|
|
|
329
|
-
#
|
|
423
|
+
# Decrypts data with the configured encryptor.
|
|
330
424
|
#
|
|
331
|
-
# @param value [
|
|
332
|
-
# @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
|
|
333
429
|
def decrypt(value)
|
|
334
430
|
configuration.encryptor.decrypt(value)
|
|
335
431
|
end
|
|
336
432
|
|
|
337
|
-
# Returns
|
|
433
|
+
# Returns the processor with the given name.
|
|
338
434
|
#
|
|
339
|
-
# @param name [Symbol, String]
|
|
340
|
-
# @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.
|
|
341
438
|
# @api private
|
|
342
439
|
def processor(name = :default)
|
|
343
440
|
@processors[name.to_sym]
|
|
344
441
|
end
|
|
345
442
|
|
|
346
|
-
#
|
|
443
|
+
# Sets the default processor. Intended for tests.
|
|
347
444
|
#
|
|
348
|
-
# @param value [PatientHttp::Processor, nil]
|
|
445
|
+
# @param value [PatientHttp::Processor, nil] The processor, or `nil` to
|
|
446
|
+
# remove it.
|
|
447
|
+
# @return [void]
|
|
349
448
|
# @api private
|
|
350
449
|
def processor=(value)
|
|
351
450
|
if value.nil?
|
|
@@ -355,24 +454,67 @@ module PatientHttp
|
|
|
355
454
|
end
|
|
356
455
|
end
|
|
357
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
|
+
|
|
358
476
|
private
|
|
359
477
|
|
|
360
|
-
#
|
|
361
|
-
#
|
|
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]
|
|
362
489
|
def stop_processors(timeout:)
|
|
363
490
|
processors = @processors.values
|
|
364
491
|
return if processors.empty?
|
|
365
492
|
|
|
366
493
|
if processors.one?
|
|
367
|
-
processors.first
|
|
494
|
+
stop_processor(processors.first, timeout)
|
|
368
495
|
else
|
|
369
|
-
processors.map { |processor| Thread.new { processor
|
|
496
|
+
processors.map { |processor| Thread.new { stop_processor(processor, timeout) } }.each(&:join)
|
|
370
497
|
end
|
|
371
498
|
end
|
|
372
499
|
|
|
373
|
-
#
|
|
374
|
-
#
|
|
375
|
-
#
|
|
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]
|
|
376
518
|
def shutdown_shared_services
|
|
377
519
|
@monitor_thread&.stop
|
|
378
520
|
@monitor_thread = nil
|
|
@@ -391,4 +533,14 @@ if defined?(::Rails::Engine)
|
|
|
391
533
|
require_relative "solid_queue/engine"
|
|
392
534
|
end
|
|
393
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)
|
|
394
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,14 +84,14 @@ 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
|
- - ">="
|
|
89
91
|
- !ruby/object:Gem::Version
|
|
90
92
|
version: '0'
|
|
91
93
|
requirements: []
|
|
92
|
-
rubygems_version:
|
|
94
|
+
rubygems_version: 4.0.3
|
|
93
95
|
specification_version: 4
|
|
94
96
|
summary: Offload async HTTP requests from Solid Queue workers to a dedicated async
|
|
95
97
|
I/O processor
|