wurk 1.7.6 → 1.8.1
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/README.md +26 -5
- data/app/controllers/concerns/wurk/sse_streaming.rb +2 -1
- data/app/controllers/concerns/wurk/stream_concurrency_guard.rb +33 -9
- data/app/controllers/wurk/api/pagination.rb +25 -7
- data/app/controllers/wurk/api_controller.rb +49 -25
- data/app/controllers/wurk/extensions_controller.rb +8 -2
- data/app/controllers/wurk/profiles_controller.rb +45 -9
- data/config/routes.rb +31 -10
- data/lib/generators/wurk/install/templates/wurk.rb +1 -1
- data/lib/sidekiq/capsule.rb +4 -0
- data/lib/sidekiq/component.rb +4 -0
- data/lib/sidekiq/config.rb +4 -0
- data/lib/sidekiq/deploy.rb +4 -0
- data/lib/sidekiq/embedded.rb +4 -0
- data/lib/sidekiq/fetch.rb +4 -0
- data/lib/sidekiq/iterable_job.rb +4 -0
- data/lib/sidekiq/job/interrupt_handler.rb +4 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +4 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +4 -0
- data/lib/sidekiq/job/iterable.rb +4 -0
- data/lib/sidekiq/job_logger.rb +4 -0
- data/lib/sidekiq/job_util.rb +4 -0
- data/lib/sidekiq/loader.rb +4 -0
- data/lib/sidekiq/logger.rb +4 -0
- data/lib/sidekiq/metrics/query.rb +4 -0
- data/lib/sidekiq/middleware/current_attributes.rb +7 -0
- data/lib/sidekiq/middleware/i18n.rb +6 -0
- data/lib/sidekiq/middleware/modules.rb +4 -0
- data/lib/sidekiq/pro/web.rb +5 -0
- data/lib/sidekiq/profiler.rb +4 -0
- data/lib/sidekiq/redis_client_adapter.rb +4 -0
- data/lib/sidekiq/test_api.rb +5 -0
- data/lib/sidekiq/testing/inline.rb +8 -0
- data/lib/sidekiq/transaction_aware_client.rb +4 -0
- data/lib/sidekiq/worker_compatibility_alias.rb +4 -0
- data/lib/sidekiq-ent/periodic/testing.rb +5 -0
- data/lib/sidekiq-ent/web.rb +5 -0
- data/lib/sidekiq-ent.rb +7 -0
- data/lib/sidekiq-pro.rb +7 -0
- data/lib/wurk/api/fast.rb +8 -33
- data/lib/wurk/api/idempotency.rb +28 -5
- data/lib/wurk/api/swarm.rb +1 -1
- data/lib/wurk/batch/callbacks.rb +143 -92
- data/lib/wurk/batch/death_handler.rb +19 -8
- data/lib/wurk/batch/server_middleware.rb +37 -24
- data/lib/wurk/batch/status.rb +49 -15
- data/lib/wurk/batch.rb +164 -65
- data/lib/wurk/capsule.rb +0 -2
- data/lib/wurk/client/buffered.rb +227 -106
- data/lib/wurk/client.rb +33 -13
- data/lib/wurk/compat.rb +13 -0
- data/lib/wurk/configuration.rb +155 -24
- data/lib/wurk/cpu_count.rb +97 -0
- data/lib/wurk/cron.rb +97 -34
- data/lib/wurk/dead_set.rb +6 -7
- data/lib/wurk/encryption.rb +46 -13
- data/lib/wurk/fetcher/many_queues.rb +65 -0
- data/lib/wurk/fetcher/orphan_grace.rb +67 -0
- data/lib/wurk/fetcher/private_list_key.rb +76 -0
- data/lib/wurk/fetcher/reaper.rb +55 -74
- data/lib/wurk/fetcher/reliable.rb +23 -17
- data/lib/wurk/fetcher/unit_of_work.rb +19 -1
- data/lib/wurk/fetcher/unparseable_keys.rb +56 -0
- data/lib/wurk/flow/completion.rb +28 -3
- data/lib/wurk/flow/creation.rb +24 -4
- data/lib/wurk/flow.rb +24 -2
- data/lib/wurk/health.rb +182 -40
- data/lib/wurk/heartbeat.rb +37 -14
- data/lib/wurk/history.rb +7 -1
- data/lib/wurk/import/sidekiq_cron.rb +248 -0
- data/lib/wurk/job/options.rb +22 -3
- data/lib/wurk/job_logger.rb +13 -3
- data/lib/wurk/job_record.rb +68 -19
- data/lib/wurk/job_retry.rb +12 -6
- data/lib/wurk/job_set.rb +99 -51
- data/lib/wurk/job_util.rb +27 -8
- data/lib/wurk/launcher.rb +84 -40
- data/lib/wurk/leader.rb +53 -28
- data/lib/wurk/limiter/base.rb +11 -30
- data/lib/wurk/limiter/bucket.rb +14 -5
- data/lib/wurk/limiter/server_middleware.rb +2 -1
- data/lib/wurk/limiter.rb +1 -1
- data/lib/wurk/loader.rb +48 -0
- data/lib/wurk/lua/batch_ack_complete.lua +28 -0
- data/lib/wurk/lua/batch_ack_failed.lua +18 -0
- data/lib/wurk/lua/batch_ack_success.lua +35 -0
- data/lib/wurk/lua/batch_append_callback.lua +66 -0
- data/lib/wurk/lua/batch_delete.lua +31 -0
- data/lib/wurk/lua/batch_hold.lua +17 -0
- data/lib/wurk/lua/batch_invalidate.lua +14 -0
- data/lib/wurk/lua/batch_kid_done.lua +14 -0
- data/lib/wurk/lua/batch_push.lua +47 -0
- data/lib/wurk/lua/batch_remove_jobs.lua +29 -0
- data/lib/wurk/lua/batch_schedule.lua +27 -0
- data/lib/wurk/lua/fetch_first.lua +27 -0
- data/lib/wurk/lua/flow_abandon.lua +16 -21
- data/lib/wurk/lua/flow_advance.lua +41 -26
- data/lib/wurk/lua/flow_create.lua +24 -9
- data/lib/wurk/lua/flow_fail.lua +3 -3
- data/lib/wurk/lua/leader_campaign.lua +19 -0
- data/lib/wurk/lua/limiter_bucket_acquire.lua +3 -2
- data/lib/wurk/lua/limiter_list_sweep.lua +8 -9
- data/lib/wurk/lua/loader.rb +36 -23
- data/lib/wurk/lua/throttle_slot.lua +13 -26
- data/lib/wurk/lua.rb +31 -244
- data/lib/wurk/manager.rb +25 -18
- data/lib/wurk/metrics/accumulator.rb +31 -10
- data/lib/wurk/metrics/dashboard_series.rb +163 -0
- data/lib/wurk/metrics/histogram.rb +46 -0
- data/lib/wurk/metrics/history.rb +49 -57
- data/lib/wurk/metrics/prometheus.rb +232 -0
- data/lib/wurk/metrics/query.rb +127 -155
- data/lib/wurk/metrics/rollup.rb +2 -2
- data/lib/wurk/metrics/statsd.rb +1 -1
- data/lib/wurk/middleware/interrupt_handler.rb +6 -2
- data/lib/wurk/middleware/poison_pill.rb +5 -4
- data/lib/wurk/middleware/timeout.rb +8 -8
- data/lib/wurk/process_set.rb +5 -5
- data/lib/wurk/processor.rb +69 -31
- data/lib/wurk/profile_set.rb +19 -24
- data/lib/wurk/profiler.rb +48 -50
- data/lib/wurk/queue.rb +36 -19
- data/lib/wurk/rails_boot.rb +43 -1
- data/lib/wurk/railtie.rb +2 -0
- data/lib/wurk/rake_tasks.rb +18 -0
- data/lib/wurk/redact.rb +39 -0
- data/lib/wurk/redis_connection.rb +6 -3
- data/lib/wurk/redis_options.rb +21 -4
- data/lib/wurk/redis_pool.rb +119 -21
- data/lib/wurk/scheduled.rb +44 -11
- data/lib/wurk/sentry/error_handler.rb +53 -7
- data/lib/wurk/sentry/middleware.rb +7 -8
- data/lib/wurk/shutdown_gate.rb +24 -1
- data/lib/wurk/sorted_entry.rb +16 -5
- data/lib/wurk/stats.rb +9 -4
- data/lib/wurk/status.rb +2 -2
- data/lib/wurk/swarm/child_boot.rb +48 -38
- data/lib/wurk/swarm/liveness.rb +153 -0
- data/lib/wurk/swarm/restart.rb +24 -3
- data/lib/wurk/swarm.rb +115 -24
- data/lib/wurk/telemetry/server_middleware.rb +2 -5
- data/lib/wurk/testing.rb +15 -5
- data/lib/wurk/throttle.rb +16 -1
- data/lib/wurk/topology.rb +1 -5
- data/lib/wurk/unique.rb +1 -1
- data/lib/wurk/version.rb +1 -1
- data/lib/wurk/watchdog.rb +2 -2
- data/lib/wurk/web/config.rb +56 -20
- data/lib/wurk/web/enterprise.rb +21 -18
- data/lib/wurk/web/extension.rb +98 -22
- data/lib/wurk/web/rack_app.rb +12 -11
- data/lib/wurk/web/search.rb +18 -8
- data/lib/wurk/worker/setter.rb +17 -14
- data/lib/wurk/worker.rb +6 -67
- data/lib/wurk.rb +2 -3
- data/vendor/assets/dashboard/assets/ArgsValue-_MYkd_vA.js +1 -0
- data/vendor/assets/dashboard/assets/BatchDetail-DPmg-hle.js +1 -0
- data/vendor/assets/dashboard/assets/Batches-NTgqSlvD.js +1 -0
- data/vendor/assets/dashboard/assets/Busy-2rxcLJMq.js +1 -0
- data/vendor/assets/dashboard/assets/Cron-BFpJgKYb.js +1 -0
- data/vendor/assets/dashboard/assets/Dashboard-C8VOE2S7.js +1 -0
- data/vendor/assets/dashboard/assets/Dead-Dse2YEbZ.js +1 -0
- data/vendor/assets/dashboard/assets/Extension-BIc2c1zG.js +1 -0
- data/vendor/assets/dashboard/assets/FilterBox-BfZm8ZzH.js +1 -0
- data/vendor/assets/dashboard/assets/FlowDetail-BWddOtgf.js +1 -0
- data/vendor/assets/dashboard/assets/FlowState-6lGsojxE.js +1 -0
- data/vendor/assets/dashboard/assets/Flows-BgKTlYwI.js +1 -0
- data/vendor/assets/dashboard/assets/JobDetailModal-BrjmO98c.js +2 -0
- data/vendor/assets/dashboard/assets/Limiters-DeUkfbcg.js +1 -0
- data/vendor/assets/dashboard/assets/Metrics-DzmAgBFG.js +1 -0
- data/vendor/assets/dashboard/assets/NotFound-CQLLXIfa.js +1 -0
- data/vendor/assets/dashboard/assets/PageHeader-CeSaqysY.js +1 -0
- data/vendor/assets/dashboard/assets/Profiles-CI2i2tl8.js +1 -0
- data/vendor/assets/dashboard/assets/Queues-DUCQ_3St.js +1 -0
- data/vendor/assets/dashboard/assets/Retries-Dc2RFuTf.js +1 -0
- data/vendor/assets/dashboard/assets/Scheduled-B8zOKCLC.js +1 -0
- data/vendor/assets/dashboard/assets/Search-x0eS-V-U.js +1 -0
- data/vendor/assets/dashboard/assets/charts-VggSMFGc.js +1 -0
- data/vendor/assets/dashboard/assets/i18n-B6AbnmJn.js +1 -0
- data/vendor/assets/dashboard/assets/index-CTWV8VL4.js +141 -0
- data/vendor/assets/dashboard/assets/index-WpZjU076.css +1 -0
- data/vendor/assets/dashboard/assets/useResetPageOnEmpty-BsYWeStI.js +1 -0
- data/vendor/assets/dashboard/assets/useSort-BWWfpnZH.js +1 -0
- data/vendor/assets/dashboard/assets/utils-DnzJXljj.js +1 -0
- data/vendor/assets/dashboard/index.html +3 -3
- data/vendor/assets/dashboard/wurk-manifest.json +2 -2
- metadata +90 -34
- data/config/locales/en.yml +0 -15
- data/vendor/assets/dashboard/assets/ArgsValue-D-x_ifLY.js +0 -1
- data/vendor/assets/dashboard/assets/BatchDetail-C39NJuew.js +0 -1
- data/vendor/assets/dashboard/assets/Batches-CSwo7Asa.js +0 -1
- data/vendor/assets/dashboard/assets/Busy-BOFMu-sq.js +0 -1
- data/vendor/assets/dashboard/assets/Cron-Dy8RQzDI.js +0 -1
- data/vendor/assets/dashboard/assets/Dashboard-BuTHI-O1.js +0 -1
- data/vendor/assets/dashboard/assets/Dead-B9KRvQ0N.js +0 -1
- data/vendor/assets/dashboard/assets/Extension-BnBVHfux.js +0 -1
- data/vendor/assets/dashboard/assets/FilterBox-DC24zite.js +0 -1
- data/vendor/assets/dashboard/assets/FlowDetail-DyLuzUvt.js +0 -1
- data/vendor/assets/dashboard/assets/FlowState-DAPKUahm.js +0 -1
- data/vendor/assets/dashboard/assets/Flows-Fr3rjZM_.js +0 -1
- data/vendor/assets/dashboard/assets/JobDetailModal-N6kiJXq3.js +0 -2
- data/vendor/assets/dashboard/assets/Limiters-kbFA7uS1.js +0 -1
- data/vendor/assets/dashboard/assets/Metrics-Dj2uoZ3o.js +0 -1
- data/vendor/assets/dashboard/assets/PageHeader-B_F94azl.js +0 -1
- data/vendor/assets/dashboard/assets/Profiles-D_DjEezN.js +0 -1
- data/vendor/assets/dashboard/assets/Queues-CO4V9hAz.js +0 -1
- data/vendor/assets/dashboard/assets/Retries-DCWnzeLa.js +0 -1
- data/vendor/assets/dashboard/assets/Scheduled-BebDUjLU.js +0 -1
- data/vendor/assets/dashboard/assets/Search-Cvr5fy4Y.js +0 -1
- data/vendor/assets/dashboard/assets/Skeleton-Bu3Ke6rV.js +0 -1
- data/vendor/assets/dashboard/assets/charts-BCs9bQKz.js +0 -1
- data/vendor/assets/dashboard/assets/index-BIwyOC5Q.js +0 -141
- data/vendor/assets/dashboard/assets/index-DBQN6Jk8.css +0 -1
- data/vendor/assets/dashboard/assets/useResetPageOnEmpty-Bzh-BJyL.js +0 -1
- data/vendor/assets/dashboard/assets/useSort-COA3fVJ5.js +0 -1
- data/vendor/assets/dashboard/assets/utils-BIrvZ1hi.js +0 -1
data/lib/wurk/batch/callbacks.rb
CHANGED
|
@@ -1,45 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require_relative '../lua'
|
|
4
5
|
|
|
5
6
|
module Wurk
|
|
6
7
|
class Batch
|
|
8
|
+
# Raised by `Callbacks.enqueue_callbacks` once every callback for the event
|
|
9
|
+
# has been attempted and at least one could not be enqueued, so the caller
|
|
10
|
+
# leaves the event's dedup marker unwritten and the next fire retries it.
|
|
11
|
+
class CallbackEnqueueError < StandardError; end
|
|
12
|
+
|
|
7
13
|
# Fires batch callbacks (`:success`, `:complete`, `:death`) by enqueuing
|
|
8
|
-
# them as ordinary jobs on the batch's `callback_queue`.
|
|
9
|
-
# b-<bid>-notify so the same callback can't be enqueued twice even
|
|
10
|
-
# if multiple workers race to ack the final job.
|
|
14
|
+
# them as ordinary jobs on the batch's `callback_queue`.
|
|
11
15
|
#
|
|
12
16
|
# Callback wrapper job: Wurk::Batch::CallbackJob — given a target spec
|
|
13
17
|
# ("Klass" or "Klass#method") and options hash, it instantiates and
|
|
14
18
|
# invokes on_<event> (or the named method) with the Status snapshot.
|
|
19
|
+
#
|
|
20
|
+
# A child batch's `:complete` and `:success` callback jobs are enqueued
|
|
21
|
+
# *into its parent batch* (their payload carries the parent's `bid`), so
|
|
22
|
+
# they are live jids of the parent: the parent cannot drain until they
|
|
23
|
+
# have run. That is what makes the spec §2.9 step workflow work —
|
|
24
|
+
# `step1_done` reopens the parent and adds `step2` before the parent can
|
|
25
|
+
# fire. A callback job that fails counts against the parent like any of
|
|
26
|
+
# its jobs (a death suppresses the parent's `:success`). `:death`
|
|
27
|
+
# callbacks stay outside: they fire while the child is still running, and
|
|
28
|
+
# a notification must not hold or poison the parent.
|
|
15
29
|
module Callbacks
|
|
16
30
|
module_function
|
|
17
31
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
32
|
+
# In-process re-drive for the fire and ack paths. Nothing else re-drives
|
|
33
|
+
# a fire once the job that drained the batch has acked, so a transient
|
|
34
|
+
# Redis error there would otherwise strand the callbacks for good.
|
|
35
|
+
REDRIVE_ATTEMPTS = 3
|
|
36
|
+
REDRIVE_BACKOFF = 0.05
|
|
37
|
+
|
|
38
|
+
# Runs on the post-state of a batch script that moved a member out of the
|
|
39
|
+
# live jids or pending-children set (see the gate note heading
|
|
40
|
+
# lua/batch_ack_success.lua). Fires `:complete` once both sets are empty, and
|
|
41
|
+
# `:success` when pending is also 0 and nothing in the subtree is dead;
|
|
42
|
+
# then hands the drain to the parent.
|
|
22
43
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# parent's
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# re-invocation, so at most one of the racing paths reaches a fire and
|
|
30
|
-
# the callback markers absorb the rest (see `fire_complete` for the one
|
|
31
|
-
# window that can still duplicate).
|
|
32
|
-
def maybe_fire(bid, pending:, live:)
|
|
33
|
-
return unless live.zero?
|
|
34
|
-
return unless kids_finished?(bid)
|
|
44
|
+
# Spec §2.4: child `:complete`/`:success` fire before the parent's, so a
|
|
45
|
+
# parent whose own jobs are done while a child batch is still running
|
|
46
|
+
# waits here (kids > 0); the child's `propagate_to_parent` re-enters with
|
|
47
|
+
# the parent's fresh state when it finishes.
|
|
48
|
+
def maybe_fire(bid, pending:, live:, kids:)
|
|
49
|
+
return unless live.zero? && kids.zero?
|
|
35
50
|
|
|
36
51
|
fire_complete(bid)
|
|
37
52
|
fire_success(bid) if pending.zero? && !subtree_dead?(bid)
|
|
38
53
|
propagate_to_parent(bid)
|
|
39
54
|
end
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
# Yields the attempt number (0-based) and retries a StandardError up to
|
|
57
|
+
# REDRIVE_ATTEMPTS times in total, re-raising the last one. Every block
|
|
58
|
+
# handed to it is idempotent: the ack scripts are SREM/SADD-guarded and
|
|
59
|
+
# the fires are marker-guarded.
|
|
60
|
+
def retrying(attempts: REDRIVE_ATTEMPTS)
|
|
61
|
+
attempt = 0
|
|
62
|
+
begin
|
|
63
|
+
yield attempt
|
|
64
|
+
rescue StandardError
|
|
65
|
+
attempt += 1
|
|
66
|
+
raise if attempt >= attempts
|
|
67
|
+
|
|
68
|
+
sleep(REDRIVE_BACKOFF * attempt)
|
|
69
|
+
retry
|
|
70
|
+
end
|
|
43
71
|
end
|
|
44
72
|
|
|
45
73
|
# Fired from Wurk::Batch::DeathHandler whenever a death makes the died
|
|
@@ -58,13 +86,20 @@ module Wurk
|
|
|
58
86
|
# dashboard and `subtree_dead?` all still see a dead batch. `:complete`
|
|
59
87
|
# and `:success` have no such fallback — the callback is their whole
|
|
60
88
|
# signal — and this claim additionally gates `cascade_death`, which
|
|
61
|
-
# would otherwise re-walk the ancestor chain on every re-invocation.
|
|
89
|
+
# would otherwise re-walk the ancestor chain on every re-invocation. An
|
|
90
|
+
# enqueue that *raises* is different from a crash: the claim is handed
|
|
91
|
+
# back so the caller's re-drive fires `:death` again.
|
|
62
92
|
def fire_death(bid)
|
|
63
93
|
record_event(bid, 'death_at')
|
|
64
94
|
index_dead(bid)
|
|
65
95
|
return unless dedup_set(bid, 'death')
|
|
66
96
|
|
|
67
|
-
|
|
97
|
+
begin
|
|
98
|
+
enqueue_callbacks(bid, 'death')
|
|
99
|
+
rescue CallbackEnqueueError
|
|
100
|
+
Wurk.redis { |conn| conn.call('DEL', "b-#{bid}-death") }
|
|
101
|
+
raise
|
|
102
|
+
end
|
|
68
103
|
cascade_death(bid)
|
|
69
104
|
end
|
|
70
105
|
|
|
@@ -95,22 +130,19 @@ module Wurk
|
|
|
95
130
|
end
|
|
96
131
|
|
|
97
132
|
# `:complete` and `:success` mark their dedup key *after* the enqueue,
|
|
98
|
-
# never before (F16)
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
133
|
+
# never before (F16): a claim-then-enqueue ordering turns a crash in
|
|
134
|
+
# between into callbacks nobody ever enqueues. Enqueuing first makes the
|
|
135
|
+
# durable side effect happen before the marker that suppresses it, and an
|
|
136
|
+
# enqueue that raised leaves the marker unwritten so the re-drive (the
|
|
137
|
+
# caller's `retrying`, or a reclaimed re-run of the draining job) fires
|
|
138
|
+
# the event again.
|
|
104
139
|
#
|
|
105
140
|
# The accepted direction is a duplicate over a lost callback: callback
|
|
106
141
|
# jobs retry like any other job and must already be idempotent (spec
|
|
107
142
|
# §2.4, §12 "Callback retries"), so firing one twice is a cost the app
|
|
108
143
|
# is required to absorb, while losing one silently strands the batch.
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
# reclaimed child re-running propagate_to_parent, a second DeathHandler
|
|
112
|
-
# pass — so the duplicate window is only two genuinely concurrent acks
|
|
113
|
-
# interleaving between each other's check and mark.
|
|
144
|
+
# Concurrent duplicates are confined to re-drives: the fire gate admits
|
|
145
|
+
# exactly one caller per drain transition.
|
|
114
146
|
#
|
|
115
147
|
# `record_event` stays ahead of the enqueue: the callback job reads a
|
|
116
148
|
# Status snapshot and must see `complete_at`/`success_at` already set.
|
|
@@ -140,11 +172,8 @@ module Wurk
|
|
|
140
172
|
# full success. `created_at` shares the CLOCK_REALTIME epoch we record it
|
|
141
173
|
# with. No-op without a dogstatsd client.
|
|
142
174
|
#
|
|
143
|
-
# Strictly best-effort:
|
|
144
|
-
# enqueue
|
|
145
|
-
# Redis hiccup on the HGET) would abort `fire_success` with nothing left
|
|
146
|
-
# to re-drive it — the success callbacks and linger would be stranded for
|
|
147
|
-
# good. Swallow and log instead.
|
|
175
|
+
# Strictly best-effort: a raise here would abort `fire_success` ahead of
|
|
176
|
+
# the enqueue for the sake of a metric. Swallow and log instead.
|
|
148
177
|
def emit_duration_metric(bid)
|
|
149
178
|
created = Wurk.redis { |conn| conn.call('HGET', "b-#{bid}", 'created_at') }
|
|
150
179
|
return if created.nil? || created.to_s.empty?
|
|
@@ -163,14 +192,13 @@ module Wurk
|
|
|
163
192
|
raw = Wurk.redis { |conn| conn.call('HGET', "b-#{bid}", 'linger') }
|
|
164
193
|
seconds = raw.nil? || raw.to_s.empty? ? Batch::POST_SUCCESS_EXPIRY_SECONDS : raw.to_i
|
|
165
194
|
Wurk.redis do |conn|
|
|
166
|
-
Batch.keys_for(bid).each { |key|
|
|
195
|
+
conn.pipelined { |pipe| Batch.keys_for(bid).each { |key| pipe.call('EXPIRE', key, seconds) } }
|
|
167
196
|
end
|
|
168
197
|
end
|
|
169
198
|
|
|
170
199
|
# True once `b-<bid>-<event>` exists, i.e. an enqueue pass for `event`
|
|
171
200
|
# has completed. The read-side half of the enqueue-then-mark ordering in
|
|
172
|
-
# `fire_complete`/`fire_success
|
|
173
|
-
# because its `dedup_set` still doubles as the claim.
|
|
201
|
+
# `fire_complete`/`fire_success`.
|
|
174
202
|
def dedup_marked?(bid, event)
|
|
175
203
|
Wurk.redis { |conn| conn.call('EXISTS', "b-#{bid}-#{event}") }.to_i == 1
|
|
176
204
|
end
|
|
@@ -190,17 +218,17 @@ module Wurk
|
|
|
190
218
|
end
|
|
191
219
|
end
|
|
192
220
|
|
|
193
|
-
# The
|
|
194
|
-
# already expired
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
# post-success `linger` window, untouched.
|
|
221
|
+
# The HSET resurrects the hash when a callback fires for a batch whose keys
|
|
222
|
+
# already expired, so the write is followed by an NX stamp — without it the
|
|
223
|
+
# resurrected hash would have no clock at all. NX leaves a live batch's
|
|
224
|
+
# expiry, and the shorter post-success `linger` window, untouched.
|
|
198
225
|
def record_event(bid, field)
|
|
199
226
|
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME)
|
|
200
227
|
Wurk.redis do |conn|
|
|
201
|
-
conn.
|
|
202
|
-
|
|
203
|
-
|
|
228
|
+
conn.pipelined do |pipe|
|
|
229
|
+
pipe.call('HSET', "b-#{bid}", field, now.to_s, field.delete_suffix('_at'), '1')
|
|
230
|
+
pipe.call('EXPIRE', "b-#{bid}", Batch::DEFAULT_EXPIRY_SECONDS, 'NX')
|
|
231
|
+
end
|
|
204
232
|
end
|
|
205
233
|
end
|
|
206
234
|
|
|
@@ -233,13 +261,13 @@ module Wurk
|
|
|
233
261
|
# last dead job is manually retried back to success, the descendant
|
|
234
262
|
# clears its OWN death mark (#212, in BATCH_PUSH) — but every ancestor
|
|
235
263
|
# was marked by the death *cascade*, not by a jid in its own died set,
|
|
236
|
-
# so nothing
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
264
|
+
# so nothing there ever clears them. Re-evaluated whenever a child
|
|
265
|
+
# drains (propagate_to_parent): drop the durable death mark and
|
|
266
|
+
# `dead-batches` membership once the batch's own died set is empty AND
|
|
267
|
+
# no child still carries a death mark. The `b-<bid>-death` notify dedup
|
|
268
|
+
# key is deliberately left intact, so a later re-death re-marks the batch
|
|
269
|
+
# (fire_death restores the flag before its own dedup guard) without ever
|
|
270
|
+
# re-enqueuing `:death`.
|
|
243
271
|
def clear_death_on_recovery(bid)
|
|
244
272
|
return unless death_fired?(bid)
|
|
245
273
|
return if own_died_remaining?(bid)
|
|
@@ -261,26 +289,46 @@ module Wurk
|
|
|
261
289
|
end
|
|
262
290
|
|
|
263
291
|
# Per-callback rescue: one bad spec or a transient enqueue failure must
|
|
264
|
-
# not
|
|
265
|
-
#
|
|
292
|
+
# not keep the remaining callbacks for this event from being attempted.
|
|
293
|
+
# Each failure reaches the error handlers; once all were attempted, any
|
|
294
|
+
# failure raises so the caller does not write the event's marker.
|
|
266
295
|
def enqueue_callbacks(bid, event)
|
|
267
|
-
|
|
296
|
+
failed = callback_items(bid, event).count { |item| !push_callback(bid, event, item) }
|
|
297
|
+
return if failed.zero?
|
|
298
|
+
|
|
299
|
+
raise CallbackEnqueueError, "batch #{bid}: #{failed} #{event} callback(s) failed to enqueue"
|
|
300
|
+
end
|
|
268
301
|
|
|
269
|
-
|
|
302
|
+
def callback_items(bid, event)
|
|
303
|
+
callbacks, queue, parent_bid = callback_specs_for(bid)
|
|
304
|
+
parent_bid = nil if event == 'death'
|
|
305
|
+
callbacks.filter_map do |(cb_event, target, options)|
|
|
270
306
|
next unless cb_event == event
|
|
271
307
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
308
|
+
item = callback_item(bid, target, event, options, queue)
|
|
309
|
+
item['bid'] = parent_bid if parent_bid
|
|
310
|
+
item
|
|
275
311
|
end
|
|
276
312
|
end
|
|
277
313
|
|
|
314
|
+
# True when the callback job was enqueued.
|
|
315
|
+
def push_callback(bid, event, item)
|
|
316
|
+
Batch.with_thread_batch(nil, nil) { Wurk::Client.push(item) }
|
|
317
|
+
true
|
|
318
|
+
rescue StandardError => e
|
|
319
|
+
Wurk.configuration.handle_exception(
|
|
320
|
+
e, { context: "batch #{bid}: #{event} callback #{item['args'][1].inspect} enqueue failed", bid: bid }
|
|
321
|
+
)
|
|
322
|
+
false
|
|
323
|
+
end
|
|
324
|
+
|
|
278
325
|
def callback_specs_for(bid)
|
|
279
|
-
|
|
280
|
-
|
|
326
|
+
callbacks_json, queue, parent_bid = Wurk.redis do |conn|
|
|
327
|
+
conn.call('HMGET', "b-#{bid}", 'callbacks', 'callback_queue', 'parent_bid')
|
|
328
|
+
end
|
|
281
329
|
queue = 'default' if queue.nil? || queue.empty?
|
|
282
|
-
|
|
283
|
-
[
|
|
330
|
+
parent_bid = nil if parent_bid.nil? || parent_bid.empty?
|
|
331
|
+
[parse_callbacks(callbacks_json), queue, parent_bid]
|
|
284
332
|
end
|
|
285
333
|
|
|
286
334
|
def parse_callbacks(raw)
|
|
@@ -291,46 +339,49 @@ module Wurk
|
|
|
291
339
|
[]
|
|
292
340
|
end
|
|
293
341
|
|
|
294
|
-
|
|
295
|
-
|
|
342
|
+
# Pushed (by push_callback) with the batch thread-locals cleared: a
|
|
343
|
+
# fire can run on a thread that is inside some other batch's `#jobs`
|
|
344
|
+
# block (a hold released at the end of a nested block), and the client
|
|
345
|
+
# middleware would otherwise stamp that batch's bid over the parent bid
|
|
346
|
+
# — or the buffer would swallow the push until that block exits.
|
|
347
|
+
def callback_item(bid, target, event, options, queue)
|
|
348
|
+
{
|
|
296
349
|
'class' => 'Wurk::Batch::CallbackJob',
|
|
297
350
|
'args' => [bid, target, event, options],
|
|
298
351
|
'queue' => queue,
|
|
299
352
|
'retry' => true
|
|
300
|
-
|
|
353
|
+
}
|
|
301
354
|
end
|
|
302
355
|
|
|
303
|
-
# When a child batch
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
#
|
|
307
|
-
#
|
|
356
|
+
# When a child batch drains (by success or death), drop it from the
|
|
357
|
+
# parent's pending children and re-evaluate the parent with the state
|
|
358
|
+
# BATCH_KID_DONE read atomically alongside the SREM. A re-run of the
|
|
359
|
+
# child (a reclaimed job, a re-drive) removes nothing but still reports
|
|
360
|
+
# the parent's state, so a parent fire lost to a crash is re-driven too.
|
|
361
|
+
#
|
|
362
|
+
# A recovered child may have lifted the last death from the parent's
|
|
363
|
+
# subtree — clear the parent's cascaded mark first, so the parent's
|
|
364
|
+
# `:success` can fire whenever it next drains (here, or when this
|
|
365
|
+
# child's callback jobs ack into it). Harmless on the death path: the
|
|
366
|
+
# dying child still carries its mark, so any_child_dead? keeps the
|
|
367
|
+
# parent dead.
|
|
308
368
|
def propagate_to_parent(bid)
|
|
309
369
|
parent_bid = parent_bid_for(bid)
|
|
310
370
|
return if parent_bid.nil? || parent_bid.empty?
|
|
311
|
-
return unless pkids_drained?(parent_bid, bid)
|
|
312
371
|
|
|
313
|
-
# A recovered child may have lifted the last death from the parent's
|
|
314
|
-
# subtree — clear the parent's cascaded mark before its gate runs, so
|
|
315
|
-
# `:success` can fire. Harmless on the death path: the dying child
|
|
316
|
-
# still carries its mark, so any_child_dead? keeps the parent dead.
|
|
317
372
|
clear_death_on_recovery(parent_bid)
|
|
318
|
-
|
|
373
|
+
pending, live, kids = Wurk.redis do |conn|
|
|
374
|
+
Wurk::Lua::Loader.eval_cached(
|
|
375
|
+
conn, :batch_kid_done,
|
|
376
|
+
keys: ["b-#{parent_bid}", "b-#{parent_bid}-jids", "b-#{parent_bid}-pkids"], argv: [bid]
|
|
377
|
+
)
|
|
378
|
+
end
|
|
379
|
+
maybe_fire(parent_bid, pending: pending.to_i, live: live.to_i, kids: kids.to_i)
|
|
319
380
|
end
|
|
320
381
|
|
|
321
382
|
def parent_bid_for(bid)
|
|
322
383
|
Wurk.redis { |conn| conn.call('HGET', "b-#{bid}", 'parent_bid') }
|
|
323
384
|
end
|
|
324
|
-
|
|
325
|
-
def pkids_drained?(parent_bid, child_bid)
|
|
326
|
-
Wurk.redis do |conn|
|
|
327
|
-
conn.call('SREM', "b-#{parent_bid}-pkids", child_bid)
|
|
328
|
-
conn.call('SCARD', "b-#{parent_bid}-pkids").to_i.zero?
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
def pending_for(bid) = Wurk.redis { |conn| conn.call('HGET', "b-#{bid}", 'pending') }.to_i
|
|
333
|
-
def live_for(bid) = Wurk.redis { |conn| conn.call('SCARD', "b-#{bid}-jids") }.to_i
|
|
334
385
|
end
|
|
335
386
|
end
|
|
336
387
|
end
|
|
@@ -10,32 +10,43 @@ module Wurk
|
|
|
10
10
|
# carries a `bid`, we BATCH_ACK_COMPLETE → record the death → fire
|
|
11
11
|
# `:death` callback exactly once per batch (first death only).
|
|
12
12
|
#
|
|
13
|
+
# The retry layer runs this after the job is already in the morgue and
|
|
14
|
+
# acked off its private list, and swallows whatever it raises — nothing
|
|
15
|
+
# would ever deliver this death to the batch again. So the whole pass is
|
|
16
|
+
# re-driven in-process (`Callbacks.retrying`): the ack script is
|
|
17
|
+
# SREM/SADD-guarded and the fires are marker-guarded, so a replay is safe.
|
|
18
|
+
# A replay can't trust `first_death` (the lost attempt may already have
|
|
19
|
+
# moved the jid into the died set), so it falls back to the `:death`
|
|
20
|
+
# claim marker to decide whether `:death` still has to fire.
|
|
21
|
+
#
|
|
13
22
|
# Spec: docs/target/sidekiq-pro.md §2.4 (`:death`).
|
|
14
23
|
class DeathHandler
|
|
15
24
|
def self.call(job, _exception)
|
|
16
25
|
bid = job['bid']
|
|
17
26
|
return unless bid
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
Callbacks.retrying { |attempt| record(bid, job['jid'], replay: attempt.positive?) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.record(bid, jid, replay:)
|
|
32
|
+
live, _died, first_death, kids, pending = Wurk.redis do |conn|
|
|
20
33
|
Wurk::Lua::Loader.eval_cached(
|
|
21
34
|
conn,
|
|
22
35
|
:batch_ack_complete,
|
|
23
|
-
keys: ["b-#{bid}", "b-#{bid}-jids", "b-#{bid}-died", "b-#{bid}-failed"],
|
|
24
|
-
argv: [
|
|
36
|
+
keys: ["b-#{bid}", "b-#{bid}-jids", "b-#{bid}-died", "b-#{bid}-failed", "b-#{bid}-pkids"],
|
|
37
|
+
argv: [jid, Batch::DEFAULT_EXPIRY_SECONDS]
|
|
25
38
|
)
|
|
26
|
-
end
|
|
27
|
-
live, _died, first_death = Array(result).map(&:to_i)
|
|
39
|
+
end.map(&:to_i)
|
|
28
40
|
|
|
29
41
|
restamp_ttls(bid)
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
return unless live.zero?
|
|
43
|
+
Callbacks.fire_death(bid) if first_death == 1 || (replay && !Callbacks.dedup_marked?(bid, 'death'))
|
|
33
44
|
|
|
34
45
|
# Through the gated maybe_fire, not a direct fire_complete: this batch
|
|
35
46
|
# may still have running child batches, and spec §2.4 ordering says
|
|
36
47
|
# its `:complete` must wait for theirs (#209). `:success` stays
|
|
37
48
|
# suppressed regardless — the death above set the durable death flag.
|
|
38
|
-
|
|
49
|
+
Callbacks.maybe_fire(bid, pending: pending, live: live, kids: kids)
|
|
39
50
|
end
|
|
40
51
|
|
|
41
52
|
# BATCH_ACK_COMPLETE stamps the two keys it can itself resurrect; this
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'json'
|
|
4
3
|
require_relative '../middleware'
|
|
5
4
|
require_relative '../lua'
|
|
6
5
|
require_relative '../job'
|
|
7
6
|
require_relative '../job_retry'
|
|
7
|
+
require_relative 'callbacks'
|
|
8
8
|
|
|
9
9
|
module Wurk
|
|
10
10
|
class Batch
|
|
11
11
|
# Server middleware. Runs around `perform` for any job carrying a `bid`.
|
|
12
|
-
# On success → BATCH_ACK_SUCCESS →
|
|
13
|
-
#
|
|
14
|
-
# `:complete` callback jobs.
|
|
12
|
+
# On success → BATCH_ACK_SUCCESS → when that drains the batch, fire its
|
|
13
|
+
# callbacks (Callbacks.maybe_fire).
|
|
15
14
|
#
|
|
16
15
|
# On a job raising (and thus heading to retry), records a transient
|
|
17
16
|
# failure → BATCH_ACK_FAILED → the jid joins `b-<bid>-failed` and
|
|
@@ -21,8 +20,18 @@ module Wurk
|
|
|
21
20
|
# Limiter::Rescheduled re-enqueue — and cooperative IterableJob
|
|
22
21
|
# interruption) are re-raised as neither success nor failure.
|
|
23
22
|
#
|
|
23
|
+
# The success ack runs outside the job's rescue: once `perform` returned,
|
|
24
|
+
# nothing the batch bookkeeping raises may be mistaken for the job failing,
|
|
25
|
+
# or a job that already did its work is retried and runs twice. The ack
|
|
26
|
+
# itself is retried in-process and only raises (sending the job to retry,
|
|
27
|
+
# at-least-once) when Redis stays unreachable — a jid left in the live set
|
|
28
|
+
# would hold the batch open forever. The fire after it never raises: it is
|
|
29
|
+
# re-driven in-process and then reported to the error handlers.
|
|
30
|
+
#
|
|
24
31
|
# Invalidated batches short-circuit: the job is skipped without
|
|
25
32
|
# raising — counts as a "success" for batch purposes per spec §12.
|
|
33
|
+
# A callback job is never skipped: it rides in its *parent* batch (see
|
|
34
|
+
# Callbacks), and a cancelled parent must not swallow a child's callback.
|
|
26
35
|
#
|
|
27
36
|
# Death handling lives in Wurk::Batch::DeathHandler (registered as a
|
|
28
37
|
# config death_handler) because death is signalled from the retry layer,
|
|
@@ -30,16 +39,15 @@ module Wurk
|
|
|
30
39
|
class ServerMiddleware
|
|
31
40
|
include Wurk::Middleware::ServerMiddleware
|
|
32
41
|
|
|
42
|
+
CALLBACK_JOB = 'Wurk::Batch::CallbackJob'
|
|
43
|
+
|
|
33
44
|
def call(_worker, job, _queue, &)
|
|
34
45
|
bid = job['bid']
|
|
35
46
|
return yield unless bid
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
run_and_ack(bid, job['jid'], &)
|
|
48
|
+
jid = job['jid']
|
|
49
|
+
run(bid, jid, &) if job['class'] == CALLBACK_JOB || !invalidated?(bid)
|
|
50
|
+
ack_success(bid, jid)
|
|
43
51
|
end
|
|
44
52
|
|
|
45
53
|
private
|
|
@@ -50,9 +58,8 @@ module Wurk
|
|
|
50
58
|
# failure: re-raise it untouched, acking neither. Any other exception
|
|
51
59
|
# means the job failed and will retry (or eventually die): record it
|
|
52
60
|
# before re-raising.
|
|
53
|
-
def
|
|
61
|
+
def run(bid, jid)
|
|
54
62
|
yield
|
|
55
|
-
ack_success(bid, jid)
|
|
56
63
|
rescue Wurk::JobRetry::Handled, Wurk::Job::Interrupted
|
|
57
64
|
raise
|
|
58
65
|
rescue StandardError
|
|
@@ -64,19 +71,27 @@ module Wurk
|
|
|
64
71
|
redis_pool.with { |conn| conn.call('HGET', "b-#{bid}", 'invalidated') } == '1'
|
|
65
72
|
end
|
|
66
73
|
|
|
74
|
+
# A jid that was no longer live (`removed == 0`) is a re-run — most
|
|
75
|
+
# often a job reclaimed after a SIGKILL that landed between this ack and
|
|
76
|
+
# the fire. It still goes through the gate with the batch's current
|
|
77
|
+
# state: if the batch is drained, the dead run's fire may never have
|
|
78
|
+
# happened, and the callback markers absorb it if it did.
|
|
67
79
|
def ack_success(bid, jid)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
conn,
|
|
71
|
-
:batch_ack_success,
|
|
72
|
-
keys: ["b-#{bid}", "b-#{bid}-jids", "b-#{bid}-failed"],
|
|
73
|
-
argv: [jid]
|
|
74
|
-
)
|
|
80
|
+
_removed, pending, live, kids = Callbacks.retrying do
|
|
81
|
+
redis_pool.with { |conn| Batch.ack_success(conn, bid, jid) }
|
|
75
82
|
end
|
|
76
|
-
pending, live
|
|
77
|
-
|
|
83
|
+
fire(bid, pending, live, kids)
|
|
84
|
+
end
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
def fire(bid, pending, live, kids)
|
|
87
|
+
Callbacks.retrying { Callbacks.maybe_fire(bid, pending: pending, live: live, kids: kids) }
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
error_config.handle_exception(e, { context: "batch #{bid}: firing callbacks after ack", bid: bid })
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# `config` is a Configuration or a Capsule; only the former reports.
|
|
93
|
+
def error_config
|
|
94
|
+
config.respond_to?(:handle_exception) ? config : config.config
|
|
80
95
|
end
|
|
81
96
|
|
|
82
97
|
def ack_failed(bid, jid)
|
|
@@ -93,6 +108,4 @@ module Wurk
|
|
|
93
108
|
end
|
|
94
109
|
end
|
|
95
110
|
|
|
96
|
-
require_relative 'callbacks'
|
|
97
|
-
|
|
98
111
|
Wurk.configuration.server_middleware.add(Wurk::Batch::ServerMiddleware)
|
data/lib/wurk/batch/status.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require_relative '../lua'
|
|
5
|
+
require_relative 'callbacks'
|
|
4
6
|
|
|
5
7
|
module Wurk
|
|
6
8
|
class Batch
|
|
@@ -10,7 +12,7 @@ module Wurk
|
|
|
10
12
|
#
|
|
11
13
|
# `#data` returns the JSON-friendly hash served by the polling endpoint.
|
|
12
14
|
# `#join` blocks the current thread until `complete?` — test/util only.
|
|
13
|
-
# `#delete`
|
|
15
|
+
# `#delete` removes the batch and every reference to it.
|
|
14
16
|
class Status
|
|
15
17
|
JOIN_POLL_INTERVAL = 0.5
|
|
16
18
|
|
|
@@ -47,7 +49,7 @@ module Wurk
|
|
|
47
49
|
def complete?
|
|
48
50
|
return true if @data['complete'] == '1'
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
complete_with?(live_jids_count)
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
def failed_jids
|
|
@@ -81,8 +83,10 @@ module Wurk
|
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
# JSON-serializable snapshot used by the polling middleware / web UI.
|
|
84
|
-
# Field names are wire-compat with Sidekiq Pro's BatchStatus.
|
|
86
|
+
# Field names are wire-compat with Sidekiq Pro's BatchStatus. The four
|
|
87
|
+
# set reads it needs ride one pipeline.
|
|
85
88
|
def data
|
|
89
|
+
failed, dead, kids, live = set_snapshot
|
|
86
90
|
{
|
|
87
91
|
'bid' => @bid,
|
|
88
92
|
'total' => total,
|
|
@@ -92,14 +96,14 @@ module Wurk
|
|
|
92
96
|
'complete_at' => complete_at,
|
|
93
97
|
'success_at' => success_at,
|
|
94
98
|
'death_at' => death_at,
|
|
95
|
-
'complete' =>
|
|
99
|
+
'complete' => complete_with?(live.to_i),
|
|
96
100
|
'invalidated' => invalidated?,
|
|
97
101
|
'description' => description,
|
|
98
102
|
'parent_bid' => parent_bid,
|
|
99
103
|
'tags' => tags,
|
|
100
|
-
'failed_jids' =>
|
|
101
|
-
'dead_jids' =>
|
|
102
|
-
'child_count' =>
|
|
104
|
+
'failed_jids' => failed,
|
|
105
|
+
'dead_jids' => dead,
|
|
106
|
+
'child_count' => kids.to_i
|
|
103
107
|
}
|
|
104
108
|
end
|
|
105
109
|
|
|
@@ -115,15 +119,22 @@ module Wurk
|
|
|
115
119
|
end
|
|
116
120
|
end
|
|
117
121
|
|
|
118
|
-
# Nukes every key for this batch
|
|
119
|
-
#
|
|
120
|
-
#
|
|
122
|
+
# Nukes every key for this batch, its index entries and tag indexes,
|
|
123
|
+
# and its membership in the parent's `-kids`/`-pkids` — one atomic
|
|
124
|
+
# script (BATCH_DELETE). Dangerous if jobs are still in flight: they'll
|
|
125
|
+
# succeed/fail without a batch to ack against and this batch's callbacks
|
|
126
|
+
# won't fire. The *parent* is not left waiting on a child that no longer
|
|
127
|
+
# exists: when this was the last thing it waited on, its callbacks fire
|
|
128
|
+
# here.
|
|
121
129
|
def delete
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
reload!
|
|
131
|
+
parent = parent_bid.to_s
|
|
132
|
+
own = Batch.keys_for(@bid)
|
|
133
|
+
pending, live, kids = Wurk.redis do |conn|
|
|
134
|
+
Wurk::Lua::Loader.eval_cached(conn, :batch_delete, keys: delete_keys(own, parent),
|
|
135
|
+
argv: [@bid, own.size, parent.empty? ? '0' : '1'])
|
|
136
|
+
end.map(&:to_i)
|
|
137
|
+
Callbacks.maybe_fire(parent, pending: pending, live: live, kids: kids) unless parent.empty?
|
|
127
138
|
nil
|
|
128
139
|
end
|
|
129
140
|
|
|
@@ -135,6 +146,29 @@ module Wurk
|
|
|
135
146
|
|
|
136
147
|
private
|
|
137
148
|
|
|
149
|
+
# [failed jids, dead jids, child count, live count] in one round trip.
|
|
150
|
+
def set_snapshot
|
|
151
|
+
Wurk.redis do |conn|
|
|
152
|
+
conn.pipelined do |pipe|
|
|
153
|
+
pipe.call('SMEMBERS', "b-#{@bid}-failed")
|
|
154
|
+
pipe.call('SMEMBERS', "b-#{@bid}-died")
|
|
155
|
+
pipe.call('SCARD', "b-#{@bid}-kids")
|
|
156
|
+
pipe.call('SCARD', "b-#{@bid}-jids")
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# KEYS layout BATCH_DELETE expects.
|
|
162
|
+
def delete_keys(own, parent)
|
|
163
|
+
keys = [*own, 'batches', 'dead-batches']
|
|
164
|
+
keys.push("b-#{parent}", "b-#{parent}-jids", "b-#{parent}-pkids", "b-#{parent}-kids") unless parent.empty?
|
|
165
|
+
keys.concat(tags.map { |t| "tags:#{t}" })
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def complete_with?(live)
|
|
169
|
+
@data['complete'] == '1' || (total.positive? && live.zero?)
|
|
170
|
+
end
|
|
171
|
+
|
|
138
172
|
def live_jids_count
|
|
139
173
|
Wurk.redis { |conn| conn.call('SCARD', "b-#{@bid}-jids") }.to_i
|
|
140
174
|
end
|