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.
Files changed (217) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -5
  3. data/app/controllers/concerns/wurk/sse_streaming.rb +2 -1
  4. data/app/controllers/concerns/wurk/stream_concurrency_guard.rb +33 -9
  5. data/app/controllers/wurk/api/pagination.rb +25 -7
  6. data/app/controllers/wurk/api_controller.rb +49 -25
  7. data/app/controllers/wurk/extensions_controller.rb +8 -2
  8. data/app/controllers/wurk/profiles_controller.rb +45 -9
  9. data/config/routes.rb +31 -10
  10. data/lib/generators/wurk/install/templates/wurk.rb +1 -1
  11. data/lib/sidekiq/capsule.rb +4 -0
  12. data/lib/sidekiq/component.rb +4 -0
  13. data/lib/sidekiq/config.rb +4 -0
  14. data/lib/sidekiq/deploy.rb +4 -0
  15. data/lib/sidekiq/embedded.rb +4 -0
  16. data/lib/sidekiq/fetch.rb +4 -0
  17. data/lib/sidekiq/iterable_job.rb +4 -0
  18. data/lib/sidekiq/job/interrupt_handler.rb +4 -0
  19. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +4 -0
  20. data/lib/sidekiq/job/iterable/csv_enumerator.rb +4 -0
  21. data/lib/sidekiq/job/iterable.rb +4 -0
  22. data/lib/sidekiq/job_logger.rb +4 -0
  23. data/lib/sidekiq/job_util.rb +4 -0
  24. data/lib/sidekiq/loader.rb +4 -0
  25. data/lib/sidekiq/logger.rb +4 -0
  26. data/lib/sidekiq/metrics/query.rb +4 -0
  27. data/lib/sidekiq/middleware/current_attributes.rb +7 -0
  28. data/lib/sidekiq/middleware/i18n.rb +6 -0
  29. data/lib/sidekiq/middleware/modules.rb +4 -0
  30. data/lib/sidekiq/pro/web.rb +5 -0
  31. data/lib/sidekiq/profiler.rb +4 -0
  32. data/lib/sidekiq/redis_client_adapter.rb +4 -0
  33. data/lib/sidekiq/test_api.rb +5 -0
  34. data/lib/sidekiq/testing/inline.rb +8 -0
  35. data/lib/sidekiq/transaction_aware_client.rb +4 -0
  36. data/lib/sidekiq/worker_compatibility_alias.rb +4 -0
  37. data/lib/sidekiq-ent/periodic/testing.rb +5 -0
  38. data/lib/sidekiq-ent/web.rb +5 -0
  39. data/lib/sidekiq-ent.rb +7 -0
  40. data/lib/sidekiq-pro.rb +7 -0
  41. data/lib/wurk/api/fast.rb +8 -33
  42. data/lib/wurk/api/idempotency.rb +28 -5
  43. data/lib/wurk/api/swarm.rb +1 -1
  44. data/lib/wurk/batch/callbacks.rb +143 -92
  45. data/lib/wurk/batch/death_handler.rb +19 -8
  46. data/lib/wurk/batch/server_middleware.rb +37 -24
  47. data/lib/wurk/batch/status.rb +49 -15
  48. data/lib/wurk/batch.rb +164 -65
  49. data/lib/wurk/capsule.rb +0 -2
  50. data/lib/wurk/client/buffered.rb +227 -106
  51. data/lib/wurk/client.rb +33 -13
  52. data/lib/wurk/compat.rb +13 -0
  53. data/lib/wurk/configuration.rb +155 -24
  54. data/lib/wurk/cpu_count.rb +97 -0
  55. data/lib/wurk/cron.rb +97 -34
  56. data/lib/wurk/dead_set.rb +6 -7
  57. data/lib/wurk/encryption.rb +46 -13
  58. data/lib/wurk/fetcher/many_queues.rb +65 -0
  59. data/lib/wurk/fetcher/orphan_grace.rb +67 -0
  60. data/lib/wurk/fetcher/private_list_key.rb +76 -0
  61. data/lib/wurk/fetcher/reaper.rb +55 -74
  62. data/lib/wurk/fetcher/reliable.rb +23 -17
  63. data/lib/wurk/fetcher/unit_of_work.rb +19 -1
  64. data/lib/wurk/fetcher/unparseable_keys.rb +56 -0
  65. data/lib/wurk/flow/completion.rb +28 -3
  66. data/lib/wurk/flow/creation.rb +24 -4
  67. data/lib/wurk/flow.rb +24 -2
  68. data/lib/wurk/health.rb +182 -40
  69. data/lib/wurk/heartbeat.rb +37 -14
  70. data/lib/wurk/history.rb +7 -1
  71. data/lib/wurk/import/sidekiq_cron.rb +248 -0
  72. data/lib/wurk/job/options.rb +22 -3
  73. data/lib/wurk/job_logger.rb +13 -3
  74. data/lib/wurk/job_record.rb +68 -19
  75. data/lib/wurk/job_retry.rb +12 -6
  76. data/lib/wurk/job_set.rb +99 -51
  77. data/lib/wurk/job_util.rb +27 -8
  78. data/lib/wurk/launcher.rb +84 -40
  79. data/lib/wurk/leader.rb +53 -28
  80. data/lib/wurk/limiter/base.rb +11 -30
  81. data/lib/wurk/limiter/bucket.rb +14 -5
  82. data/lib/wurk/limiter/server_middleware.rb +2 -1
  83. data/lib/wurk/limiter.rb +1 -1
  84. data/lib/wurk/loader.rb +48 -0
  85. data/lib/wurk/lua/batch_ack_complete.lua +28 -0
  86. data/lib/wurk/lua/batch_ack_failed.lua +18 -0
  87. data/lib/wurk/lua/batch_ack_success.lua +35 -0
  88. data/lib/wurk/lua/batch_append_callback.lua +66 -0
  89. data/lib/wurk/lua/batch_delete.lua +31 -0
  90. data/lib/wurk/lua/batch_hold.lua +17 -0
  91. data/lib/wurk/lua/batch_invalidate.lua +14 -0
  92. data/lib/wurk/lua/batch_kid_done.lua +14 -0
  93. data/lib/wurk/lua/batch_push.lua +47 -0
  94. data/lib/wurk/lua/batch_remove_jobs.lua +29 -0
  95. data/lib/wurk/lua/batch_schedule.lua +27 -0
  96. data/lib/wurk/lua/fetch_first.lua +27 -0
  97. data/lib/wurk/lua/flow_abandon.lua +16 -21
  98. data/lib/wurk/lua/flow_advance.lua +41 -26
  99. data/lib/wurk/lua/flow_create.lua +24 -9
  100. data/lib/wurk/lua/flow_fail.lua +3 -3
  101. data/lib/wurk/lua/leader_campaign.lua +19 -0
  102. data/lib/wurk/lua/limiter_bucket_acquire.lua +3 -2
  103. data/lib/wurk/lua/limiter_list_sweep.lua +8 -9
  104. data/lib/wurk/lua/loader.rb +36 -23
  105. data/lib/wurk/lua/throttle_slot.lua +13 -26
  106. data/lib/wurk/lua.rb +31 -244
  107. data/lib/wurk/manager.rb +25 -18
  108. data/lib/wurk/metrics/accumulator.rb +31 -10
  109. data/lib/wurk/metrics/dashboard_series.rb +163 -0
  110. data/lib/wurk/metrics/histogram.rb +46 -0
  111. data/lib/wurk/metrics/history.rb +49 -57
  112. data/lib/wurk/metrics/prometheus.rb +232 -0
  113. data/lib/wurk/metrics/query.rb +127 -155
  114. data/lib/wurk/metrics/rollup.rb +2 -2
  115. data/lib/wurk/metrics/statsd.rb +1 -1
  116. data/lib/wurk/middleware/interrupt_handler.rb +6 -2
  117. data/lib/wurk/middleware/poison_pill.rb +5 -4
  118. data/lib/wurk/middleware/timeout.rb +8 -8
  119. data/lib/wurk/process_set.rb +5 -5
  120. data/lib/wurk/processor.rb +69 -31
  121. data/lib/wurk/profile_set.rb +19 -24
  122. data/lib/wurk/profiler.rb +48 -50
  123. data/lib/wurk/queue.rb +36 -19
  124. data/lib/wurk/rails_boot.rb +43 -1
  125. data/lib/wurk/railtie.rb +2 -0
  126. data/lib/wurk/rake_tasks.rb +18 -0
  127. data/lib/wurk/redact.rb +39 -0
  128. data/lib/wurk/redis_connection.rb +6 -3
  129. data/lib/wurk/redis_options.rb +21 -4
  130. data/lib/wurk/redis_pool.rb +119 -21
  131. data/lib/wurk/scheduled.rb +44 -11
  132. data/lib/wurk/sentry/error_handler.rb +53 -7
  133. data/lib/wurk/sentry/middleware.rb +7 -8
  134. data/lib/wurk/shutdown_gate.rb +24 -1
  135. data/lib/wurk/sorted_entry.rb +16 -5
  136. data/lib/wurk/stats.rb +9 -4
  137. data/lib/wurk/status.rb +2 -2
  138. data/lib/wurk/swarm/child_boot.rb +48 -38
  139. data/lib/wurk/swarm/liveness.rb +153 -0
  140. data/lib/wurk/swarm/restart.rb +24 -3
  141. data/lib/wurk/swarm.rb +115 -24
  142. data/lib/wurk/telemetry/server_middleware.rb +2 -5
  143. data/lib/wurk/testing.rb +15 -5
  144. data/lib/wurk/throttle.rb +16 -1
  145. data/lib/wurk/topology.rb +1 -5
  146. data/lib/wurk/unique.rb +1 -1
  147. data/lib/wurk/version.rb +1 -1
  148. data/lib/wurk/watchdog.rb +2 -2
  149. data/lib/wurk/web/config.rb +56 -20
  150. data/lib/wurk/web/enterprise.rb +21 -18
  151. data/lib/wurk/web/extension.rb +98 -22
  152. data/lib/wurk/web/rack_app.rb +12 -11
  153. data/lib/wurk/web/search.rb +18 -8
  154. data/lib/wurk/worker/setter.rb +17 -14
  155. data/lib/wurk/worker.rb +6 -67
  156. data/lib/wurk.rb +2 -3
  157. data/vendor/assets/dashboard/assets/ArgsValue-_MYkd_vA.js +1 -0
  158. data/vendor/assets/dashboard/assets/BatchDetail-DPmg-hle.js +1 -0
  159. data/vendor/assets/dashboard/assets/Batches-NTgqSlvD.js +1 -0
  160. data/vendor/assets/dashboard/assets/Busy-2rxcLJMq.js +1 -0
  161. data/vendor/assets/dashboard/assets/Cron-BFpJgKYb.js +1 -0
  162. data/vendor/assets/dashboard/assets/Dashboard-C8VOE2S7.js +1 -0
  163. data/vendor/assets/dashboard/assets/Dead-Dse2YEbZ.js +1 -0
  164. data/vendor/assets/dashboard/assets/Extension-BIc2c1zG.js +1 -0
  165. data/vendor/assets/dashboard/assets/FilterBox-BfZm8ZzH.js +1 -0
  166. data/vendor/assets/dashboard/assets/FlowDetail-BWddOtgf.js +1 -0
  167. data/vendor/assets/dashboard/assets/FlowState-6lGsojxE.js +1 -0
  168. data/vendor/assets/dashboard/assets/Flows-BgKTlYwI.js +1 -0
  169. data/vendor/assets/dashboard/assets/JobDetailModal-BrjmO98c.js +2 -0
  170. data/vendor/assets/dashboard/assets/Limiters-DeUkfbcg.js +1 -0
  171. data/vendor/assets/dashboard/assets/Metrics-DzmAgBFG.js +1 -0
  172. data/vendor/assets/dashboard/assets/NotFound-CQLLXIfa.js +1 -0
  173. data/vendor/assets/dashboard/assets/PageHeader-CeSaqysY.js +1 -0
  174. data/vendor/assets/dashboard/assets/Profiles-CI2i2tl8.js +1 -0
  175. data/vendor/assets/dashboard/assets/Queues-DUCQ_3St.js +1 -0
  176. data/vendor/assets/dashboard/assets/Retries-Dc2RFuTf.js +1 -0
  177. data/vendor/assets/dashboard/assets/Scheduled-B8zOKCLC.js +1 -0
  178. data/vendor/assets/dashboard/assets/Search-x0eS-V-U.js +1 -0
  179. data/vendor/assets/dashboard/assets/charts-VggSMFGc.js +1 -0
  180. data/vendor/assets/dashboard/assets/i18n-B6AbnmJn.js +1 -0
  181. data/vendor/assets/dashboard/assets/index-CTWV8VL4.js +141 -0
  182. data/vendor/assets/dashboard/assets/index-WpZjU076.css +1 -0
  183. data/vendor/assets/dashboard/assets/useResetPageOnEmpty-BsYWeStI.js +1 -0
  184. data/vendor/assets/dashboard/assets/useSort-BWWfpnZH.js +1 -0
  185. data/vendor/assets/dashboard/assets/utils-DnzJXljj.js +1 -0
  186. data/vendor/assets/dashboard/index.html +3 -3
  187. data/vendor/assets/dashboard/wurk-manifest.json +2 -2
  188. metadata +90 -34
  189. data/config/locales/en.yml +0 -15
  190. data/vendor/assets/dashboard/assets/ArgsValue-D-x_ifLY.js +0 -1
  191. data/vendor/assets/dashboard/assets/BatchDetail-C39NJuew.js +0 -1
  192. data/vendor/assets/dashboard/assets/Batches-CSwo7Asa.js +0 -1
  193. data/vendor/assets/dashboard/assets/Busy-BOFMu-sq.js +0 -1
  194. data/vendor/assets/dashboard/assets/Cron-Dy8RQzDI.js +0 -1
  195. data/vendor/assets/dashboard/assets/Dashboard-BuTHI-O1.js +0 -1
  196. data/vendor/assets/dashboard/assets/Dead-B9KRvQ0N.js +0 -1
  197. data/vendor/assets/dashboard/assets/Extension-BnBVHfux.js +0 -1
  198. data/vendor/assets/dashboard/assets/FilterBox-DC24zite.js +0 -1
  199. data/vendor/assets/dashboard/assets/FlowDetail-DyLuzUvt.js +0 -1
  200. data/vendor/assets/dashboard/assets/FlowState-DAPKUahm.js +0 -1
  201. data/vendor/assets/dashboard/assets/Flows-Fr3rjZM_.js +0 -1
  202. data/vendor/assets/dashboard/assets/JobDetailModal-N6kiJXq3.js +0 -2
  203. data/vendor/assets/dashboard/assets/Limiters-kbFA7uS1.js +0 -1
  204. data/vendor/assets/dashboard/assets/Metrics-Dj2uoZ3o.js +0 -1
  205. data/vendor/assets/dashboard/assets/PageHeader-B_F94azl.js +0 -1
  206. data/vendor/assets/dashboard/assets/Profiles-D_DjEezN.js +0 -1
  207. data/vendor/assets/dashboard/assets/Queues-CO4V9hAz.js +0 -1
  208. data/vendor/assets/dashboard/assets/Retries-DCWnzeLa.js +0 -1
  209. data/vendor/assets/dashboard/assets/Scheduled-BebDUjLU.js +0 -1
  210. data/vendor/assets/dashboard/assets/Search-Cvr5fy4Y.js +0 -1
  211. data/vendor/assets/dashboard/assets/Skeleton-Bu3Ke6rV.js +0 -1
  212. data/vendor/assets/dashboard/assets/charts-BCs9bQKz.js +0 -1
  213. data/vendor/assets/dashboard/assets/index-BIwyOC5Q.js +0 -141
  214. data/vendor/assets/dashboard/assets/index-DBQN6Jk8.css +0 -1
  215. data/vendor/assets/dashboard/assets/useResetPageOnEmpty-Bzh-BJyL.js +0 -1
  216. data/vendor/assets/dashboard/assets/useSort-COA3fVJ5.js +0 -1
  217. data/vendor/assets/dashboard/assets/utils-BIrvZ1hi.js +0 -1
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb): defines Sidekiq::CurrentAttributes
4
+ # (`persist`, `Save`, `Load`). Nothing is registered until `persist` is called,
5
+ # as upstream.
6
+ require 'wurk'
7
+ require 'wurk/middleware/current_attributes'
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb): opts into the locale-propagating
4
+ # Sidekiq::Middleware::I18n pair, exactly as upstream's file does on require.
5
+ require 'wurk'
6
+ require 'wurk/middleware/i18n'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (sidekiq-pro.md §10): Sidekiq::Pro::Web is the same
4
+ # dashboard as Sidekiq::Web, which Wurk loads whole.
5
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb): Sidekiq 8.1's side-effect-free
4
+ # testing API. Unlike sidekiq/testing it leaves the test mode alone.
5
+ require 'wurk'
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path, deprecated upstream exactly like sidekiq/testing.
4
+ require 'wurk'
5
+
6
+ Wurk.testing!(:inline)
7
+ warn('⛔️ `require "sidekiq/testing/inline"` is deprecated and will be removed in Sidekiq 9.0. ' \
8
+ 'Use `Sidekiq.testing!(:inline)` instead.')
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (see lib/sidekiq.rb). Wurk loads this surface whole.
4
+ require 'wurk'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (sidekiq-ent.md §2.5): Sidekiq::Periodic::ConfigTester
4
+ # ships with Wurk's cron.
5
+ require 'wurk'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop-in require path (sidekiq-ent.md §9): the Limits, Periodic and History
4
+ # tabs are part of Wurk's one dashboard, loaded whole.
5
+ require 'wurk'
@@ -0,0 +1,7 @@
1
+ # rubocop:disable Naming/FileName -- the gem's own require name, hyphen included
2
+ # frozen_string_literal: true
3
+
4
+ # Drop-in require path (sidekiq-ent.md, top): Wurk ships every Enterprise
5
+ # feature in the one gem, so this only has to load it.
6
+ require 'wurk'
7
+ # rubocop:enable Naming/FileName
@@ -0,0 +1,7 @@
1
+ # rubocop:disable Naming/FileName -- the gem's own require name, hyphen included
2
+ # frozen_string_literal: true
3
+
4
+ # Drop-in require path (sidekiq-pro.md, top): Wurk ships every Pro feature in
5
+ # the one gem, so this only has to load it.
6
+ require 'wurk'
7
+ # rubocop:enable Naming/FileName
data/lib/wurk/api/fast.rb CHANGED
@@ -1,23 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../lua'
4
- require_relative '../job_record'
5
4
 
6
5
  module Wurk
7
6
  module API
8
7
  # Pro parity (§11): Lua-backed O(1)-round-trip replacements for the
9
- # ruby-side LRANGE / ZSCAN loops in Queue and SortedSet. Mixed into the
10
- # existing data API classes so the surface is `Sidekiq::Queue#delete_job(jid)`,
11
- # `Sidekiq::DeadSet#scan(pattern) { |JobRecord| … }` etc. — wire-compat
12
- # with Pro consumer code that drops in on a one-line require swap.
8
+ # ruby-side LRANGE loops in Queue. Mixed into the existing data API class
9
+ # so the surface is `Sidekiq::Queue#delete_job(jid)` — wire-compat with Pro
10
+ # consumer code that drops in on a one-line require swap.
13
11
  #
14
12
  # We don't reimplement `Queue#size` (already LLEN, unchanged per spec).
15
13
  module Fast
16
14
  # Extension to Wurk::Queue. Pure server-side delete by jid / class — no
17
- # network round-trips per match. Returns the count of payloads removed.
15
+ # network round-trips per match.
18
16
  module QueueExt
19
- # @return [Integer] payloads removed (0 when jid absent; >0 only in
20
- # the corner case of duplicate-jid corruption).
17
+ # @return [String, nil] the deleted job's JSON, or nil when no job in
18
+ # the queue has that jid — Pro's contract, so `if q.delete_job(jid)`
19
+ # branches on whether anything was removed.
21
20
  def delete_job(jid)
22
21
  raise ArgumentError, 'jid required' if jid.nil? || jid.to_s.empty?
23
22
 
@@ -28,7 +27,7 @@ module Wurk
28
27
  keys: [Keys.queue(name)],
29
28
  argv: [jid.to_s]
30
29
  )
31
- end.to_i
30
+ end
32
31
  end
33
32
 
34
33
  # @param klass [Class, String, Symbol]
@@ -47,32 +46,8 @@ module Wurk
47
46
  end.to_i
48
47
  end
49
48
  end
50
-
51
- # Extension to Wurk::SortedSet / JobSet. The base `scan(match, count)`
52
- # already yields `(value, score)` pairs via ZSCAN — Pro promotes the
53
- # surface to yield `JobRecord` directly so callers can `entry.delete`
54
- # / `entry.retry` without re-parsing JSON.
55
- #
56
- # The new behavior is enabled by *block arity*: a 1-arg block (or
57
- # block.lambda? + a 1-param signature) receives `JobRecord`; legacy
58
- # 2-arg blocks (`|value, score|`) keep the raw shape. This preserves
59
- # the existing two-arg contract used by JobSet#find_job internally.
60
- module SortedSetExt
61
- def scan(match, count = 100, &block)
62
- return enum_for(:scan, match, count) unless block
63
-
64
- if block.arity == 1
65
- super do |value, score|
66
- block.call(Wurk::SortedEntry.new(self, score, value))
67
- end
68
- else
69
- super
70
- end
71
- end
72
- end
73
49
  end
74
50
  end
75
51
  end
76
52
 
77
53
  Wurk::Queue.include(Wurk::API::Fast::QueueExt)
78
- Wurk::SortedSet.prepend(Wurk::API::Fast::SortedSetExt)
@@ -95,17 +95,40 @@ module Wurk
95
95
  reserved ? nil : existing.to_s
96
96
  end
97
97
 
98
+ # Errors that prove the write they interrupted never applied: refused
99
+ # while dialing, a starved checkout, or a reply in which Redis itself
100
+ # rejected the command.
101
+ PRE_APPLY_ERRORS = [
102
+ ::RedisClient::CannotConnectError, ::RedisClient::FailoverError,
103
+ ::RedisClient::CommandError, ::ConnectionPool::TimeoutError
104
+ ].freeze
105
+
98
106
  # Only a success is worth replaying. A rejected body is a request the
99
- # client should be able to correct and send again under the same key, and
100
- # a raise is a request whose outcome nobody knows — both release the key
101
- # rather than pinning an answer to it.
107
+ # client should be able to correct and send again under the same key, so
108
+ # it releases. A raise releases only when nothing can have reached Redis
109
+ # — before any write went out, or on an error that proves the one in
110
+ # flight never applied. Anything else (a lost reply, a bulk request that
111
+ # died after an earlier slice landed) is an outcome nobody knows: the key
112
+ # stays pending until its TTL, so the client's retry is told "in flight"
113
+ # instead of enqueueing a second copy.
102
114
  def settle(slot, fingerprint)
115
+ Thread.current[::Wurk::Client::WRITE_STATE_KEY] = :clean
103
116
  response = yield
104
117
  response[0] < 300 ? record(slot, fingerprint, response) : release(slot)
105
118
  response
106
- rescue StandardError
107
- release(slot)
119
+ rescue StandardError => e
120
+ release(slot) unless outcome_unknown?(e)
108
121
  raise
122
+ ensure
123
+ Thread.current[::Wurk::Client::WRITE_STATE_KEY] = nil
124
+ end
125
+
126
+ def outcome_unknown?(error)
127
+ case Thread.current[::Wurk::Client::WRITE_STATE_KEY]
128
+ when :applied then true
129
+ when :attempted then PRE_APPLY_ERRORS.none? { |klass| error.is_a?(klass) }
130
+ else false
131
+ end
109
132
  end
110
133
 
111
134
  # KEEPTTL so the window is the first request's, not this write's, and XX
@@ -134,7 +134,7 @@ module Wurk
134
134
  def limiters(request)
135
135
  query = Page.query!(request)
136
136
  window = Page.window(query)
137
- names = ::Wurk::Web::Enterprise::Limits.list(filter: filter!(query))
137
+ names = ::Wurk::Web::Enterprise::Limits.list(filter: filter!(query), sweep: !ReadOnly.enabled?(request))
138
138
  rows = Page.slice(names, window) { |name| limiter_row(name) }
139
139
  Response.json(200, total: names.size, page: window.page, count: window.count, limiters: rows)
140
140
  end