patient_http-solid_queue 1.0.0 → 1.0.2
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/CHANGELOG.md +19 -0
- data/README.md +9 -1
- data/VERSION +1 -1
- data/lib/patient_http/solid_queue/callback_job.rb +15 -14
- data/lib/patient_http/solid_queue/request_job.rb +29 -14
- data/lib/patient_http/solid_queue/task_handler.rb +20 -0
- data/lib/patient_http/solid_queue/task_monitor.rb +26 -10
- data/lib/patient_http/solid_queue/task_monitor_thread.rb +24 -13
- data/lib/patient_http/solid_queue.rb +42 -22
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b17f1e1d5c477d8ca2dac603c0521dd7a842469d24db38dadb5f24858443dca
|
|
4
|
+
data.tar.gz: 25d38a530f6ec873e5b224462d0335fa99912e6c1c36cad46f2c072ff9a5162a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1d0021667606b1b527eb4b1013fb418c5e9593d89d8daba3102415305f3a337d9258fd9c4387474c0f913b421619ceeeead1d59495fdf5d814635e1df2760b0
|
|
7
|
+
data.tar.gz: b27c9cfa86a5265061b3510bd168f7780b61bab702efc621d2214dac72c4bc79efb58c3c814c01a6ad71b7a7d986b85697d73e608ccd3bdfa8834a692f9f4555
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 1.0.2
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Externally stored request payloads are no longer deleted as soon as the request is submitted to the processor. They are now retained until the request completes so that Active Job retries, shutdown re-enqueues, and crash recovery can still fetch them. Discarded `RequestJob` jobs clean up their stored payloads via an `after_discard` hook.
|
|
12
|
+
- `RequestJob` now declares `retry_on` for `PatientHttp::MaxCapacityError` and `PatientHttp::NotRunningError` with a polynomial backoff. Previously these errors (raised as normal backpressure when the processor is at max capacity) sent jobs straight to the failed jobs list because Active Job does not retry by default.
|
|
13
|
+
- `CallbackJob` no longer deletes externally stored payloads when the callback raises, so retries can fetch the payload and re-run the callback.
|
|
14
|
+
- Crash recovery no longer silently loses a request if the process crashes between removing the inflight record and re-enqueueing the job. The record is now claimed first and only deleted after the job has been enqueued, so a failed recovery attempt is retried by a later garbage collection pass.
|
|
15
|
+
- Fixed process registration and garbage collection on MySQL, where passing an explicit conflict target to `upsert`/`insert_all` raises an error. Crash recovery was silently disabled on MySQL.
|
|
16
|
+
- `PatientHttp::SolidQueue.configure` and `reset_configuration!` now reset the memoized external storage so it picks up the new configuration.
|
|
17
|
+
- Fixed race conditions that could create duplicate processors or task monitor threads from concurrent lifecycle calls; starting while the processor is draining no longer replaces it.
|
|
18
|
+
- The task monitor thread no longer holds a database connection from the pool while sleeping between heartbeats.
|
|
19
|
+
|
|
20
|
+
## 1.0.1
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Ensure `PatientHttp::SolidQueue` is automatically registered as the HTTP processor on a process not running the SolidQueue server. This gem will now auto register itself on a client process either by calling `PatientHttp::SolidQueue.configure` or `PatientHttp::SolidQueue.register_handler` directly.
|
|
25
|
+
|
|
7
26
|
## 1.0.0
|
|
8
27
|
|
|
9
28
|
### Added
|
data/README.md
CHANGED
|
@@ -94,6 +94,14 @@ The `response.callback_args` and `error.callback_args` provide access to the arg
|
|
|
94
94
|
|
|
95
95
|
> [!IMPORTANT]
|
|
96
96
|
> Do not re-raise errors in the `on_error` callback as a means to retry the request. That will just retry the error callback job. If you want to retry the original request, you can enqueue a new request from within `on_error`. Be careful with this approach, though, as it can lead to infinite retry loops if the error condition is not resolved.
|
|
97
|
+
|
|
98
|
+
Callback jobs are not retried by default. If you want failed callbacks to be retried before being discarded, configure `retry_on` in an initializer:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
PatientHttp::SolidQueue::CallbackJob.retry_on StandardError, wait: :polynomially_longer, attempts: 5
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Note that Active Job runs `after_discard` hooks for any unhandled exception, not just when configured retries are exhausted. Without `retry_on`, the first callback failure will trigger the `on_retries_exhausted` handler and delete any externally stored payload, even though the failed job can still be retried manually from Mission Control (such retries will fail if the payload was stored externally).
|
|
97
105
|
>
|
|
98
106
|
> Also note that the error callback is only called when an exception occurs during the HTTP request (timeout, connection failure, etc). HTTP error status codes (4xx, 5xx) do not trigger the error callback by default. Instead, they are treated as completed requests and passed to the `on_complete` callback. See the "Handling HTTP Error Responses" section below for how to treat HTTP errors as exceptions.
|
|
99
107
|
|
|
@@ -436,7 +444,7 @@ See the [Configuration](lib/patient_http/solid_queue/configuration.rb) class for
|
|
|
436
444
|
|
|
437
445
|
> [!IMPORTANT]
|
|
438
446
|
>
|
|
439
|
-
> One difference between using this gem and making synchronous HTTP requests from a Solid Queue job is that if `max_connections` is reached due to slow asynchronous requests, new requests will trigger an error on the Active Job. The
|
|
447
|
+
> One difference between using this gem and making synchronous HTTP requests from a Solid Queue job is that if `max_connections` is reached due to slow asynchronous requests, new requests will trigger an error on the Active Job. The job declares `retry_on` for this error with a polynomial backoff, so it will automatically be retried until the processor has capacity again.
|
|
440
448
|
>
|
|
441
449
|
> In contrast, slow synchronous HTTP requests will fill up the worker pool and block new jobs from being dequeued until a worker thread becomes free.
|
|
442
450
|
>
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.2
|
|
@@ -52,21 +52,22 @@ module PatientHttp
|
|
|
52
52
|
actual_data = ref_data ? PatientHttp::SolidQueue.external_storage.fetch(data) : data
|
|
53
53
|
actual_data = PatientHttp::SolidQueue.decrypt(actual_data)
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
raise ArgumentError, "Unknown result_type: #{result_type}"
|
|
66
|
-
end
|
|
67
|
-
ensure
|
|
68
|
-
PatientHttp::SolidQueue.external_storage.delete(ref_data) if ref_data
|
|
55
|
+
if result_type == "response"
|
|
56
|
+
response = PatientHttp::Response.load(actual_data)
|
|
57
|
+
PatientHttp::SolidQueue.invoke_completion_callbacks(response)
|
|
58
|
+
callback_service.on_complete(response)
|
|
59
|
+
elsif result_type == "error"
|
|
60
|
+
error = PatientHttp::Error.load(actual_data)
|
|
61
|
+
PatientHttp::SolidQueue.invoke_error_callbacks(error)
|
|
62
|
+
callback_service.on_error(error)
|
|
63
|
+
else
|
|
64
|
+
raise ArgumentError, "Unknown result_type: #{result_type}"
|
|
69
65
|
end
|
|
66
|
+
|
|
67
|
+
# Only delete the stored payload after the callback succeeds so that
|
|
68
|
+
# retries can still fetch it. Discarded jobs are cleaned up by the
|
|
69
|
+
# after_discard hook.
|
|
70
|
+
PatientHttp::SolidQueue.external_storage.delete(ref_data) if ref_data
|
|
70
71
|
end
|
|
71
72
|
end
|
|
72
73
|
end
|
|
@@ -10,36 +10,51 @@ module PatientHttp
|
|
|
10
10
|
#
|
|
11
11
|
# @api private
|
|
12
12
|
class RequestJob < ActiveJob::Base
|
|
13
|
+
# Rejection due to backpressure is part of normal operation: retry until the
|
|
14
|
+
# processor has capacity again. NotRunningError covers jobs that run during
|
|
15
|
+
# the narrow window when the processor is draining or stopping.
|
|
16
|
+
retry_on PatientHttp::MaxCapacityError, PatientHttp::NotRunningError, wait: :polynomially_longer, attempts: :unlimited
|
|
17
|
+
|
|
13
18
|
# Capture the Active Job serialized hash into Context so RequestExecutor can use it.
|
|
14
19
|
around_perform do |job, block|
|
|
15
20
|
PatientHttp::SolidQueue::Context.with_job(job.serialize) { block.call }
|
|
16
21
|
end
|
|
17
22
|
|
|
23
|
+
# Clean up the externally stored request payload when the job is discarded.
|
|
24
|
+
# The payload is normally deleted by TaskHandler when the request completes,
|
|
25
|
+
# so this only fires for requests that never made it that far.
|
|
26
|
+
after_discard do |job, _exception|
|
|
27
|
+
PatientHttp::SolidQueue.external_storage.delete(job.arguments[0])
|
|
28
|
+
rescue => e
|
|
29
|
+
PatientHttp::SolidQueue.configuration.logger&.warn(
|
|
30
|
+
"[PatientHttp::SolidQueue] Failed to delete stored payload for dead job: #{e.class.name} #{e.message}".strip
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
18
34
|
# @param data [Hash] Request data (possibly a storage reference)
|
|
19
35
|
# @param callback_service_name [String] Fully qualified callback service class name
|
|
20
36
|
# @param raise_error_responses [Boolean, nil] Whether to treat non-2xx responses as errors
|
|
21
37
|
# @param callback_args [Hash, nil] Arguments to pass to the callback
|
|
22
38
|
# @param request_id [String, nil] Unique request ID for tracking
|
|
23
39
|
def perform(data, callback_service_name, raise_error_responses, callback_args, request_id)
|
|
24
|
-
|
|
25
|
-
actual_data = ref_data ? PatientHttp::SolidQueue.external_storage.fetch(data) : data
|
|
40
|
+
actual_data = PatientHttp::ExternalStorage.storage_ref?(data) ? PatientHttp::SolidQueue.external_storage.fetch(data) : data
|
|
26
41
|
actual_data = PatientHttp::SolidQueue.decrypt(actual_data)
|
|
27
42
|
|
|
28
43
|
request = PatientHttp::Request.load(actual_data)
|
|
29
44
|
active_job_data = PatientHttp::SolidQueue::Context.current_job
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
# The stored payload must not be deleted here: this job data is re-enqueued
|
|
47
|
+
# for Active Job retries (e.g. MaxCapacityError), processor shutdown retries,
|
|
48
|
+
# and crash recovery, all of which need to fetch the payload again.
|
|
49
|
+
# TaskHandler deletes it when the request completes.
|
|
50
|
+
RequestExecutor.execute(
|
|
51
|
+
request,
|
|
52
|
+
callback: callback_service_name,
|
|
53
|
+
raise_error_responses: raise_error_responses,
|
|
54
|
+
callback_args: callback_args,
|
|
55
|
+
active_job_data: active_job_data,
|
|
56
|
+
request_id: request_id
|
|
57
|
+
)
|
|
43
58
|
end
|
|
44
59
|
end
|
|
45
60
|
end
|
|
@@ -18,11 +18,13 @@ module PatientHttp
|
|
|
18
18
|
def on_complete(response, callback)
|
|
19
19
|
data = store_if_needed(response.as_json)
|
|
20
20
|
CallbackJob.perform_later(data, "response", callback)
|
|
21
|
+
delete_stored_request_payload
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def on_error(error, callback)
|
|
24
25
|
data = store_if_needed(error.as_json)
|
|
25
26
|
CallbackJob.perform_later(data, "error", callback)
|
|
27
|
+
delete_stored_request_payload
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def retry
|
|
@@ -39,6 +41,24 @@ module PatientHttp
|
|
|
39
41
|
|
|
40
42
|
private
|
|
41
43
|
|
|
44
|
+
# Delete the externally stored request payload once the request has
|
|
45
|
+
# completed. Until then the payload must remain fetchable because the
|
|
46
|
+
# Active Job data referencing it can be re-enqueued by Active Job retries,
|
|
47
|
+
# processor shutdown retries, and crash recovery. Only applies to
|
|
48
|
+
# RequestJob jobs; other job types own their own arguments.
|
|
49
|
+
def delete_stored_request_payload
|
|
50
|
+
return unless @active_job_data["job_class"] == RequestJob.name
|
|
51
|
+
|
|
52
|
+
data = @active_job_data["arguments"]&.first
|
|
53
|
+
return unless PatientHttp::ExternalStorage.storage_ref?(data)
|
|
54
|
+
|
|
55
|
+
PatientHttp::SolidQueue.external_storage.delete(data)
|
|
56
|
+
rescue => e
|
|
57
|
+
PatientHttp::SolidQueue.configuration.logger&.warn(
|
|
58
|
+
"[PatientHttp::SolidQueue] Failed to delete stored request payload: #{e.class.name} #{e.message}".strip
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
42
62
|
def store_if_needed(data)
|
|
43
63
|
encrypted = PatientHttp::SolidQueue.encrypt(data)
|
|
44
64
|
external_storage = PatientHttp::SolidQueue.external_storage
|
|
@@ -79,7 +79,7 @@ module PatientHttp
|
|
|
79
79
|
def ping_process
|
|
80
80
|
ProcessRegistration.upsert(
|
|
81
81
|
{process_id: @lock_identifier, max_connections: @config.max_connections, last_seen_at: Time.current},
|
|
82
|
-
unique_by: :process_id
|
|
82
|
+
unique_by: upsert_unique_by(:process_id)
|
|
83
83
|
)
|
|
84
84
|
rescue => e
|
|
85
85
|
@config.logger&.error("[PatientHttp::SolidQueue] Failed to ping process: #{e.message}")
|
|
@@ -211,31 +211,47 @@ module PatientHttp
|
|
|
211
211
|
private
|
|
212
212
|
|
|
213
213
|
def ensure_gc_lock_row!
|
|
214
|
-
GcLock.insert_all([{lock_name: GC_LOCK_NAME}]
|
|
214
|
+
GcLock.insert_all([{lock_name: GC_LOCK_NAME}])
|
|
215
215
|
end
|
|
216
216
|
|
|
217
|
-
#
|
|
217
|
+
# MySQL does not support an explicit conflict target for upserts; it always
|
|
218
|
+
# resolves conflicts via the table's unique indexes (ON DUPLICATE KEY UPDATE).
|
|
219
|
+
# Adapters with conflict targets (PostgreSQL, SQLite) require one to be given.
|
|
218
220
|
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
|
|
221
|
+
# @param column [Symbol] the unique column to use as the conflict target
|
|
222
|
+
# @return [Symbol, nil] the column, or nil when the adapter forbids a target
|
|
223
|
+
def upsert_unique_by(column)
|
|
224
|
+
Record.connection.supports_insert_conflict_target? ? column : nil
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Re-enqueue a single orphaned record.
|
|
228
|
+
#
|
|
229
|
+
# Uses a claim-by-exact-heartbeat update to handle race conditions: if the
|
|
230
|
+
# heartbeat was updated between our read and the claim, the update returns
|
|
231
|
+
# 0 rows and we skip re-enqueueing. Claiming (refreshing the heartbeat)
|
|
232
|
+
# before enqueueing means a crash mid-recovery leaves the record behind to
|
|
233
|
+
# go stale and be retried by a later GC pass, instead of losing the request.
|
|
234
|
+
# The record is only deleted after the job has been enqueued.
|
|
222
235
|
#
|
|
223
236
|
# @param record [InflightRequest] the orphaned record
|
|
224
237
|
# @param threshold [Float] heartbeat threshold (only records below this are orphaned)
|
|
225
238
|
# @param logger [Logger] logger for output
|
|
226
239
|
# @return [Boolean] true if successfully re-enqueued
|
|
227
240
|
def reenqueue_orphaned_record(record, threshold, logger)
|
|
228
|
-
# Atomically
|
|
229
|
-
|
|
241
|
+
# Atomically claim only if still orphaned (heartbeat unchanged). The dead
|
|
242
|
+
# process_id is left in place so a failed recovery becomes orphaned again.
|
|
243
|
+
claimed = InflightRequest
|
|
230
244
|
.where(task_id: record.task_id, heartbeat_at: record.heartbeat_at)
|
|
231
245
|
.where("heartbeat_at < ?", threshold)
|
|
232
|
-
.
|
|
246
|
+
.update_all(heartbeat_at: Time.current)
|
|
233
247
|
|
|
234
|
-
return false if
|
|
248
|
+
return false if claimed == 0
|
|
235
249
|
|
|
236
250
|
job_data = JSON.parse(record.job_payload)
|
|
237
251
|
ActiveJob::Base.deserialize(job_data).tap { |j| j.executions = 0 }.enqueue
|
|
238
252
|
|
|
253
|
+
InflightRequest.where(task_id: record.task_id).delete_all
|
|
254
|
+
|
|
239
255
|
logger&.info(
|
|
240
256
|
"[PatientHttp::SolidQueue] Re-enqueued orphaned request #{record.task_id} to #{job_data["job_class"]}"
|
|
241
257
|
)
|
|
@@ -34,11 +34,10 @@ module PatientHttp
|
|
|
34
34
|
#
|
|
35
35
|
# @return [void]
|
|
36
36
|
def start
|
|
37
|
-
return
|
|
38
|
-
@running.make_true
|
|
37
|
+
return unless @running.make_true
|
|
39
38
|
@stop_signal.reset
|
|
40
39
|
|
|
41
|
-
@task_monitor.ping_process
|
|
40
|
+
with_connection { @task_monitor.ping_process }
|
|
42
41
|
|
|
43
42
|
@thread = Thread.new do
|
|
44
43
|
run
|
|
@@ -80,16 +79,24 @@ module PatientHttp
|
|
|
80
79
|
break unless @running.true?
|
|
81
80
|
|
|
82
81
|
current_time = monotonic_time
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
update_heartbeats_due = current_time - last_heartbeat_update >= @config.heartbeat_interval
|
|
83
|
+
gc_attempt_due = current_time - last_gc_attempt >= @config.heartbeat_interval
|
|
84
|
+
|
|
85
|
+
if update_heartbeats_due || gc_attempt_due
|
|
86
|
+
# Check out a database connection only for the duration of the work
|
|
87
|
+
# so the thread does not hold a slot from the pool while it sleeps.
|
|
88
|
+
with_connection do
|
|
89
|
+
if update_heartbeats_due
|
|
90
|
+
@task_monitor.ping_process
|
|
91
|
+
update_heartbeats
|
|
92
|
+
last_heartbeat_update = current_time
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if gc_attempt_due
|
|
96
|
+
attempt_garbage_collection
|
|
97
|
+
last_gc_attempt = current_time
|
|
98
|
+
end
|
|
99
|
+
end
|
|
93
100
|
end
|
|
94
101
|
|
|
95
102
|
wait_time = @config.heartbeat_interval / 2.0
|
|
@@ -100,6 +107,10 @@ module PatientHttp
|
|
|
100
107
|
@config.logger&.info("[PatientHttp::SolidQueue] Monitor thread stopped")
|
|
101
108
|
end
|
|
102
109
|
|
|
110
|
+
def with_connection(&block)
|
|
111
|
+
Record.connection_pool.with_connection(&block)
|
|
112
|
+
end
|
|
113
|
+
|
|
103
114
|
def update_heartbeats
|
|
104
115
|
request_ids = @inflight_ids_callback.call
|
|
105
116
|
return if request_ids.empty?
|
|
@@ -55,6 +55,7 @@ module PatientHttp
|
|
|
55
55
|
@after_error_callbacks = []
|
|
56
56
|
@external_storage = nil
|
|
57
57
|
@request_handler = nil
|
|
58
|
+
@lifecycle_mutex = Mutex.new
|
|
58
59
|
|
|
59
60
|
class << self
|
|
60
61
|
attr_writer :configuration
|
|
@@ -67,6 +68,9 @@ module PatientHttp
|
|
|
67
68
|
configuration = Configuration.new
|
|
68
69
|
yield(configuration) if block_given?
|
|
69
70
|
@configuration = configuration
|
|
71
|
+
@external_storage = nil
|
|
72
|
+
register_handler
|
|
73
|
+
configuration
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
# Return the current configuration, initializing with defaults if necessary.
|
|
@@ -81,6 +85,7 @@ module PatientHttp
|
|
|
81
85
|
# @return [Configuration]
|
|
82
86
|
def reset_configuration!
|
|
83
87
|
@configuration = nil
|
|
88
|
+
@external_storage = nil
|
|
84
89
|
configuration
|
|
85
90
|
end
|
|
86
91
|
|
|
@@ -167,31 +172,26 @@ module PatientHttp
|
|
|
167
172
|
#
|
|
168
173
|
# @return [void]
|
|
169
174
|
def start
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@processor = PatientHttp::Processor.new(configuration)
|
|
173
|
-
@processor.observe(ProcessorObserver.new(@processor))
|
|
174
|
-
@processor.start
|
|
175
|
+
@lifecycle_mutex.synchronize do
|
|
176
|
+
return if @processor && !@processor.stopped?
|
|
175
177
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
callback: callback,
|
|
180
|
-
raise_error_responses: raise_error_responses,
|
|
181
|
-
callback_args: callback_args
|
|
182
|
-
)
|
|
178
|
+
@processor = PatientHttp::Processor.new(configuration)
|
|
179
|
+
@processor.observe(ProcessorObserver.new(@processor))
|
|
180
|
+
@processor.start
|
|
183
181
|
end
|
|
184
182
|
|
|
185
|
-
|
|
183
|
+
register_handler
|
|
186
184
|
end
|
|
187
185
|
|
|
188
186
|
# Signal the processor to drain (stop accepting new requests).
|
|
189
187
|
#
|
|
190
188
|
# @return [void]
|
|
191
189
|
def quiet
|
|
192
|
-
|
|
190
|
+
@lifecycle_mutex.synchronize do
|
|
191
|
+
return unless running?
|
|
193
192
|
|
|
194
|
-
|
|
193
|
+
@processor.drain
|
|
194
|
+
end
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
# Stop the processor gracefully.
|
|
@@ -199,14 +199,16 @@ module PatientHttp
|
|
|
199
199
|
# @param timeout [Float, nil] maximum time to wait for in-flight requests to complete
|
|
200
200
|
# @return [void]
|
|
201
201
|
def stop(timeout: nil)
|
|
202
|
-
return unless @processor
|
|
203
|
-
|
|
204
202
|
if @request_handler
|
|
205
203
|
PatientHttp.unregister_handler(@request_handler)
|
|
206
204
|
end
|
|
207
205
|
|
|
208
|
-
@
|
|
209
|
-
|
|
206
|
+
@lifecycle_mutex.synchronize do
|
|
207
|
+
return unless @processor
|
|
208
|
+
|
|
209
|
+
@processor.stop(timeout: timeout)
|
|
210
|
+
@processor = nil
|
|
211
|
+
end
|
|
210
212
|
end
|
|
211
213
|
|
|
212
214
|
# Reset all state (useful for testing).
|
|
@@ -216,16 +218,34 @@ module PatientHttp
|
|
|
216
218
|
def reset!
|
|
217
219
|
if @request_handler
|
|
218
220
|
PatientHttp.unregister_handler(@request_handler)
|
|
219
|
-
@request_handler = nil
|
|
220
221
|
end
|
|
221
|
-
@
|
|
222
|
-
|
|
222
|
+
@lifecycle_mutex.synchronize do
|
|
223
|
+
@processor&.stop(timeout: 0)
|
|
224
|
+
@processor = nil
|
|
225
|
+
end
|
|
223
226
|
@configuration = nil
|
|
224
227
|
@external_storage = nil
|
|
225
228
|
@after_completion_callbacks = []
|
|
226
229
|
@after_error_callbacks = []
|
|
227
230
|
end
|
|
228
231
|
|
|
232
|
+
# Register SolidQueue as the request handler for processing HTTP requests. This is called
|
|
233
|
+
# automatically when the processor starts or you call PatientHttp::SolidQueue.configure.
|
|
234
|
+
#
|
|
235
|
+
# @return [void]
|
|
236
|
+
def register_handler
|
|
237
|
+
@request_handler ||= lambda do |request:, callback:, raise_error_responses:, callback_args:|
|
|
238
|
+
execute(
|
|
239
|
+
request,
|
|
240
|
+
callback: callback,
|
|
241
|
+
raise_error_responses: raise_error_responses,
|
|
242
|
+
callback_args: callback_args
|
|
243
|
+
)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
PatientHttp.register_handler(@request_handler)
|
|
247
|
+
end
|
|
248
|
+
|
|
229
249
|
# Invoke the registered completion callbacks.
|
|
230
250
|
#
|
|
231
251
|
# @param response [PatientHttp::Response] the HTTP response
|