wurk 1.0.1 → 1.0.3
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 +2 -0
- data/app/controllers/wurk/api_controller.rb +22 -1
- data/lib/wurk/capsule.rb +17 -3
- data/lib/wurk/launcher.rb +29 -4
- data/lib/wurk/railtie.rb +19 -1
- data/lib/wurk/version.rb +1 -1
- data/lib/wurk.rb +24 -2
- data/vendor/assets/dashboard/assets/geist-sans-latin-400-normal-BOaIZNA2.woff +0 -0
- data/vendor/assets/dashboard/assets/geist-sans-latin-400-normal-gapTbOY8.woff2 +0 -0
- data/vendor/assets/dashboard/assets/geist-sans-latin-500-normal-CN2lyvyL.woff +0 -0
- data/vendor/assets/dashboard/assets/geist-sans-latin-500-normal-uokXdC-Q.woff2 +0 -0
- data/vendor/assets/dashboard/assets/geist-sans-latin-600-normal-CA1yjETN.woff +0 -0
- data/vendor/assets/dashboard/assets/geist-sans-latin-600-normal-DFOURf8L.woff2 +0 -0
- data/vendor/assets/dashboard/assets/geist-sans-latin-700-normal-BmN9tIp5.woff2 +0 -0
- data/vendor/assets/dashboard/assets/geist-sans-latin-700-normal-CjScfYeH.woff +0 -0
- data/vendor/assets/dashboard/assets/index-CpoPAGXr.css +1 -0
- data/vendor/assets/dashboard/assets/index-Xstb8f_d.js +126 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- data/vendor/assets/dashboard/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- data/vendor/assets/dashboard/index.html +2 -2
- data/vendor/assets/dashboard/wurk-manifest.json +2 -2
- metadata +60 -3
- data/vendor/assets/dashboard/assets/index-9CFRWpfG.js +0 -77
- data/vendor/assets/dashboard/assets/index-CW8AFQIv.css +0 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 000bd71bc3b9148d1f4250d32ebe25e300beee9f384b013ed3efabf2f778b1ce
|
|
4
|
+
data.tar.gz: eec8ea9f45d0ef0ccc0b894515dd6711adf08054757e0475178b45ed8ec81c37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b173cdc536f04367d2eea9b5c549d71f6fe7cbfa27095af3916dc690196421e45356bd663f35a1777a7b340ad57fe6c5b6be24aec3eb57aea3d128de30944a9
|
|
7
|
+
data.tar.gz: f89b858d93982f20efa5a1d79b8b2230736cf53bdd8b3d95d54fcc4c444bb90dc70822124d8ae80060b2110a68aec8116f807759bccc87b2478cd61872fa890a
|
data/README.md
CHANGED
|
@@ -57,6 +57,8 @@ Plus Wurk extras: a worker topology DSL, a Kubernetes liveness/readiness listene
|
|
|
57
57
|
|
|
58
58
|
- **[Website](https://developerz-ai.github.io/wurk/)** · **[Wiki / full docs](https://github.com/developerz-ai/wurk/wiki)** — the pitch, install, and the complete guide.
|
|
59
59
|
- **[Getting started & architecture](https://github.com/developerz-ai/wurk/blob/main/docs/idea/01-overview.md)** — how the swarm, manager, fetcher, and processor fit together.
|
|
60
|
+
- **[Starting the worker](https://github.com/developerz-ai/wurk/blob/main/docs/running.md)** — Rails auto-start, the `wurk`/`wurkswarm` runners, and running standalone without Rails.
|
|
61
|
+
- **[Active Job adapter](https://github.com/developerz-ai/wurk/blob/main/docs/active-job.md)** — run `ActiveJob`/`deliver_later` on Wurk with `queue_adapter = :wurk`.
|
|
60
62
|
- **[Migrating from Sidekiq](#migrating-from-sidekiq)** — the one-line swap and what to expect.
|
|
61
63
|
- **API reference (parity specs):** [Sidekiq OSS](https://github.com/developerz-ai/wurk/blob/main/docs/target/sidekiq-free.md) · [Pro](https://github.com/developerz-ai/wurk/blob/main/docs/target/sidekiq-pro.md) · [Enterprise](https://github.com/developerz-ai/wurk/blob/main/docs/target/sidekiq-ent.md) — the authoritative surface Wurk matches exactly.
|
|
62
64
|
- **[Securing the dashboard](https://github.com/developerz-ai/wurk/blob/main/docs/dashboard.md)** · **[Metrics history](https://github.com/developerz-ai/wurk/blob/main/docs/metrics-history.md)**
|
|
@@ -44,7 +44,7 @@ module Wurk
|
|
|
44
44
|
def meta
|
|
45
45
|
config = ::Wurk::Web.config
|
|
46
46
|
render json: {
|
|
47
|
-
read_only: config.read_only
|
|
47
|
+
read_only: config.read_only? || !mutations_authorized?(config),
|
|
48
48
|
read_only_message: config.read_only_message,
|
|
49
49
|
custom_tabs: config.custom_tabs
|
|
50
50
|
}
|
|
@@ -294,6 +294,27 @@ module Wurk
|
|
|
294
294
|
|
|
295
295
|
private
|
|
296
296
|
|
|
297
|
+
# Engine-relative path probed as a representative mutation. Must be a real
|
|
298
|
+
# mutating route (POST /api/retries — bulk retry/delete/kill) so that a
|
|
299
|
+
# path-sensitive hook resolves it the same way the Authorization middleware
|
|
300
|
+
# will resolve the actual mutation. Probing `request.path_info` (which here
|
|
301
|
+
# is the GET /api/meta path) would let such a hook allow the probe while
|
|
302
|
+
# still 403ing real mutations, reviving the "button shows, then 403s" gap.
|
|
303
|
+
MUTATION_PROBE_PATH = '/api/retries'
|
|
304
|
+
|
|
305
|
+
# Per-request read-only signal for the SPA. When a registered authorization
|
|
306
|
+
# hook would reject a *mutating* request for this user (e.g. a viewer role
|
|
307
|
+
# that may GET but not retry/kill), report `read_only` so the SPA hides the
|
|
308
|
+
# destructive actions — the Authorization middleware already 403s the
|
|
309
|
+
# mutation itself, this just stops the buttons from showing. With no hook
|
|
310
|
+
# registered, `authorized?` is always true, so this is a no-op and the flag
|
|
311
|
+
# keeps reflecting the global read-only mode. Probes POST on a canonical
|
|
312
|
+
# mutating path (SAFE_METHODS are GET/HEAD/OPTIONS) so a path-sensitive hook
|
|
313
|
+
# answers for a real mutation, not the GET /api/meta request carrying it.
|
|
314
|
+
def mutations_authorized?(config)
|
|
315
|
+
config.authorized?(request.env, 'POST', MUTATION_PROBE_PATH)
|
|
316
|
+
end
|
|
317
|
+
|
|
297
318
|
# Resolves a single entry by "<score>|<jid>" key and applies a whitelisted
|
|
298
319
|
# action. 400 on an unknown action, 404 when the key matches nothing (e.g.
|
|
299
320
|
# the entry was already retried/deleted from another tab).
|
data/lib/wurk/capsule.rb
CHANGED
|
@@ -153,13 +153,27 @@ module Wurk
|
|
|
153
153
|
fetcher
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
# Accepts both the CLI/string form (`"name"` / `"name,weight"`) and the
|
|
157
|
+
# nested-array form Sidekiq's YAML produces for weighted queues
|
|
158
|
+
# (`:queues: - [critical, 2]` parses to `["critical", 2]`). Without the Array
|
|
159
|
+
# branch a real sidekiq.yml crashes at boot with `Integer(): " 2]"` (#241).
|
|
156
160
|
def parse_queue_entry(entry)
|
|
161
|
+
if entry.is_a?(::Array)
|
|
162
|
+
raise ArgumentError, "queue entry must be `[name]` or `[name, weight]`: `#{entry}`" if entry.size > 2
|
|
163
|
+
|
|
164
|
+
return parse_pair_queue_entry(entry[0], entry[1], entry)
|
|
165
|
+
end
|
|
166
|
+
|
|
157
167
|
qname, weight_str = entry.to_s.split(',', 2)
|
|
158
|
-
qname
|
|
168
|
+
parse_pair_queue_entry(qname, weight_str, entry)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def parse_pair_queue_entry(name, weight, entry)
|
|
172
|
+
qname = name.to_s.strip
|
|
159
173
|
raise ArgumentError, "queue name cannot be empty: `#{entry}`" if qname.empty?
|
|
160
|
-
return [qname, 0] if
|
|
174
|
+
return [qname, 0] if weight.nil?
|
|
161
175
|
|
|
162
|
-
weight = Integer(
|
|
176
|
+
weight = Integer(weight)
|
|
163
177
|
raise ArgumentError, "queue weight must be > 0: `#{entry}`" if weight <= 0
|
|
164
178
|
|
|
165
179
|
[qname, weight]
|
data/lib/wurk/launcher.rb
CHANGED
|
@@ -48,7 +48,12 @@ module Wurk
|
|
|
48
48
|
def initialize(config, embedded: false)
|
|
49
49
|
@config = config
|
|
50
50
|
@embedded = embedded
|
|
51
|
+
# Two separate flags, deliberately. @done = "quieted" (stop fetching, stay
|
|
52
|
+
# alive, report quiet=true). @stopped = "shutting down" (terminate the
|
|
53
|
+
# heartbeat loop). Quiet must NOT stop the heartbeat — otherwise a quieted
|
|
54
|
+
# process never publishes quiet=true and expires out of the live set (#236).
|
|
51
55
|
@done = false
|
|
56
|
+
@stopped = false
|
|
52
57
|
@managers = config.capsules.values.map { |cap| Manager.new(cap) }
|
|
53
58
|
@poller = build_poller
|
|
54
59
|
@cron_poller = build_cron_poller
|
|
@@ -127,6 +132,7 @@ module Wurk
|
|
|
127
132
|
# CAS-release the cluster lock now (planned shutdown) so a follower can
|
|
128
133
|
# take over immediately instead of waiting out the TTL.
|
|
129
134
|
@leader&.stop
|
|
135
|
+
stop_heartbeat
|
|
130
136
|
clear_heartbeat
|
|
131
137
|
fire_event(:exit, reverse: true)
|
|
132
138
|
end
|
|
@@ -217,11 +223,30 @@ module Wurk
|
|
|
217
223
|
@health_server&.stop
|
|
218
224
|
end
|
|
219
225
|
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
226
|
+
# Terminate the heartbeat loop and wait for it to exit before clear_heartbeat
|
|
227
|
+
# removes us from the `processes` SET — otherwise a final in-flight beat could
|
|
228
|
+
# SADD us back right after the SREM. Wakes the thread out of its BEAT_PAUSE
|
|
229
|
+
# sleep so shutdown isn't delayed up to a full interval.
|
|
230
|
+
def stop_heartbeat
|
|
231
|
+
@stopped = true
|
|
232
|
+
thread = @heartbeat_thread
|
|
233
|
+
return unless thread
|
|
234
|
+
|
|
235
|
+
begin
|
|
236
|
+
thread.wakeup
|
|
237
|
+
rescue ThreadError
|
|
238
|
+
nil
|
|
239
|
+
end
|
|
240
|
+
thread.join(BEAT_PAUSE)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Heartbeat thread loop. `safe_thread` already wraps exceptions. Loops on
|
|
244
|
+
# @stopped — NOT @done — so a *quieted* process keeps beating and publishes
|
|
245
|
+
# `quiet=true` instead of vanishing from the live set (#236). Only `#stop`
|
|
246
|
+
# flips @stopped; its `Thread#wakeup` breaks the sleep so the loop re-checks
|
|
247
|
+
# @stopped and exits without waiting out the interval.
|
|
223
248
|
def start_heartbeat
|
|
224
|
-
until @
|
|
249
|
+
until @stopped
|
|
225
250
|
heartbeat
|
|
226
251
|
sleep BEAT_PAUSE
|
|
227
252
|
end
|
data/lib/wurk/railtie.rb
CHANGED
|
@@ -36,7 +36,25 @@ module Wurk
|
|
|
36
36
|
# and the swarm boot. Console mode is detected reliably here — the console
|
|
37
37
|
# command file defines ::Rails::Console before initializers run.
|
|
38
38
|
def self.skip_boot?
|
|
39
|
-
ENV['WURK_DISABLED'] == '1' ||
|
|
39
|
+
ENV['WURK_DISABLED'] == '1' ||
|
|
40
|
+
building? ||
|
|
41
|
+
defined?(::Rails::Console) ||
|
|
42
|
+
::Rails.env.test?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# A build/precompile step must never fork the swarm (#247). The default
|
|
46
|
+
# Rails Dockerfile runs `SECRET_KEY_BASE_DUMMY=1 ./bin/rails
|
|
47
|
+
# assets:precompile`; that loads `:environment` → fires after_initialize,
|
|
48
|
+
# but there's no Redis during `docker build`, so a fork would hang/fail the
|
|
49
|
+
# build. Same for other env-loading rake tasks (db:prepare, db:migrate).
|
|
50
|
+
# The real server path is unaffected: `rails server` / `puma` boot through
|
|
51
|
+
# Rails::Command, not Rake, and don't set the dummy secret.
|
|
52
|
+
def self.building?
|
|
53
|
+
return true if ENV.key?('SECRET_KEY_BASE_DUMMY')
|
|
54
|
+
|
|
55
|
+
defined?(::Rake) && ::Rake.application.top_level_tasks.any?
|
|
56
|
+
rescue StandardError
|
|
57
|
+
false
|
|
40
58
|
end
|
|
41
59
|
end
|
|
42
60
|
end
|
data/lib/wurk/version.rb
CHANGED
data/lib/wurk.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Standalone entry point. Loading "wurk" must work without Rails.
|
|
4
|
-
# The engine and railtie live under "wurk/rails"
|
|
5
|
-
# when
|
|
4
|
+
# The engine and railtie live under "wurk/rails"; they're auto-loaded at the
|
|
5
|
+
# end of this file when Rails is present (drop-in parity, #246), and stay
|
|
6
|
+
# unloaded otherwise so a no-Rails boot pulls in zero Rails code.
|
|
6
7
|
|
|
7
8
|
require_relative 'wurk/version'
|
|
8
9
|
require_relative 'wurk/keys'
|
|
@@ -280,3 +281,24 @@ require_relative 'wurk/api/fast'
|
|
|
280
281
|
# fully defined first. compat.rb only redefines names, it does not gate
|
|
281
282
|
# behavior, so trailing the load order is safe.
|
|
282
283
|
require_relative 'wurk/compat'
|
|
284
|
+
|
|
285
|
+
# Drop-in parity (#246): stock Sidekiq auto-loads its Rails integration when
|
|
286
|
+
# Rails is present (`lib/sidekiq.rb`: `require "sidekiq/rails" if
|
|
287
|
+
# defined?(Rails::Engine)`). Without the equivalent here, a host that follows
|
|
288
|
+
# the README/migration guide with a plain `gem "wurk"` (no `require:`) boots
|
|
289
|
+
# with the engine/railtie unloaded, so the `:wurk` / `:sidekiq` ActiveJob
|
|
290
|
+
# adapter constant is never defined and `config.active_job.queue_adapter =
|
|
291
|
+
# :wurk` raises NameError during the railtie phase. Loading the engine here
|
|
292
|
+
# (idempotent with an explicit `require "wurk/rails"`) closes that gap while
|
|
293
|
+
# keeping standalone, no-Rails boot fully Rails-free.
|
|
294
|
+
#
|
|
295
|
+
# Also gate on ActionDispatch::Routing::RouteSet: the engine's class body calls
|
|
296
|
+
# `isolate_namespace Wurk`, which under railties >= 8.1 eager-reads
|
|
297
|
+
# `ActionDispatch::Routing::RouteSet` to set the engine's @route_set_class.
|
|
298
|
+
# Ecosystem test helpers (sidekiq-cron, …) load `rails/engine/railties` for the
|
|
299
|
+
# railtie API alone — Rails::Engine is defined but ActionDispatch isn't — so a
|
|
300
|
+
# bare `defined?(Rails::Engine)` check would crash with `uninitialized constant
|
|
301
|
+
# Rails::Engine::Configuration::ActionDispatch` mid-`require "sidekiq"`. In a
|
|
302
|
+
# real Rails host both are loaded by `rails/all` before Bundler.require, so the
|
|
303
|
+
# stricter gate is invisible there.
|
|
304
|
+
require_relative 'wurk/rails' if defined?(Rails::Engine) && defined?(::ActionDispatch::Routing::RouteSet)
|