wurk 1.7.3 → 1.7.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de5a24063d924950bf14f06ba530215018d35acc33a069a9a6698f48602833fa
4
- data.tar.gz: bcdf163c1de721563e707958f3487e2518f175a811143f84e48b070b55e4cacb
3
+ metadata.gz: ac61be234c0359ccb0761df401c351021317cf2a5ae162e2329dcd94d7b8b4af
4
+ data.tar.gz: 00e7d69e0a3d440f1bf335ebbeeceb80b7e93ec1f82a6f48fcc189f54c4737ab
5
5
  SHA512:
6
- metadata.gz: cc074e442e5987d123b86d9e02c8349840cd7c0b5e90d0cc5866b8e928f136e8d8ac5c861ffcf1c8a9243c0bb5e053f79a9b9477654d28b7728aadf7129957f3
7
- data.tar.gz: 8c1ee815585ca13aef64a68d62b98e50288aeb989a98471177b4ddc94dfa8baddd33a74b42ff75eb92133b54e2da1c7f8ddbcb3403182ccda55112df33e2c6bd
6
+ metadata.gz: bd52c4819554b5cad428d2514379005027aaa21cddf679ae89974edd12568075711e5dbc41b86da656f7df4a6b28ccde88cd421952002cd9966ec0e72b7841cd
7
+ data.tar.gz: 796f59f291295b5681a920cb5c9d04baa70fb416afc3246db55d1ebbd009f5a6f348d785b57c80b1dd9a07dc249771b67b3bb9203927e41becf0484cf27a6c74
data/README.md CHANGED
@@ -29,6 +29,8 @@ Wurk is wire-compatible with Sidekiq — same Redis keys, same job JSON, same Ru
29
29
 
30
30
  **On speed:** Wurk is not currently faster than stock Sidekiq — it runs at roughly 0.87×–1.02× depending on workload shape, with parity on CPU and I/O but still behind on framework overhead (noop) and boot time. Numbers, method, and the reproduction command are in [docs/benchmarks.md](docs/benchmarks.md); run them yourself with `rake bench:vs_sidekiq`.
31
31
 
32
+ **Also from us:** [Ultimate](https://github.com/developerz-ai/ultimate) — the AI-first full-stack framework, for a weekend pet project or the system a company runs on. What a whole stack looks like when the one writing the code is an agent, not a human: [`bunx create-ultimate myapp`](#also-from-developerzai-ultimate).
33
+
32
34
  ## Install
33
35
 
34
36
  ```ruby
@@ -299,6 +301,18 @@ Wurk is MIT and stays that way. If what you need is a commercial support contrac
299
301
 
300
302
  Full walkthrough — config side-by-side, the Redis key/`sidekiq_options` mapping, known incompatibilities, and a one-page cutover checklist: **[docs/migrate-from-sidekiq.md](https://github.com/developerz-ai/wurk/blob/main/docs/migrate-from-sidekiq.md)**.
301
303
 
304
+ ## Also from developerz.ai: Ultimate
305
+
306
+ **[Ultimate](https://github.com/developerz-ai/ultimate) — the AI-first full-stack framework.** Rails' opinions, Bun's speed, one command that means shippable. Wurk is what one AI-maintained *library* looks like; Ultimate is the whole stack built on the same bet — the best framework you can hand a coding agent, whether what you are building is a weekend pet project or the system a company runs on.
307
+
308
+ ```bash
309
+ bunx create-ultimate myapp && cd myapp && x dev
310
+ ```
311
+
312
+ Every framework of the last fifteen years optimised for a human typing the code. Ultimate assumes an agent writes it, and that rewrites the defaults: **one way to do each thing**, so there is nothing to choose between; conventions that are **build errors**, so the compiler corrects the agent instead of a reviewer; errors carrying a stable code, a cause, and the exact command that fixes it, so a failure costs one round-trip; docs shipped in `node_modules` and `--json` on every command, so nothing reaches for a wiki. `x verify` is the whole contract — green means shippable.
313
+
314
+ TypeScript end to end, MIT, and as measured about its own claims as this file is: what is proven, what is not, and what it refuses to claim yet are all in [its README](https://github.com/developerz-ai/ultimate#readme).
315
+
302
316
  ## Contributing
303
317
 
304
318
  Issues and pull requests are welcome — see **[CONTRIBUTING.md](https://github.com/developerz-ai/wurk/blob/main/CONTRIBUTING.md)** for the dev setup, test layers, and conventions, and **[SECURITY.md](https://github.com/developerz-ai/wurk/blob/main/SECURITY.md)** to report a vulnerability.
@@ -94,13 +94,12 @@ module Wurk
94
94
  # one without reopening this file. Baked into the literal rather than
95
95
  # appended at load, so the parallel test runner never observes a
96
96
  # half-built list.
97
- # rubocop:disable Style/MutableConstant
97
+ # rubocop:disable-next Style/MutableConstant
98
98
  JOB_KEYS = %w[
99
99
  class args queue at retry jid backtrace tags dead
100
100
  retry_for retry_queue expires_in track timeout deadline
101
101
  encrypt unique_for unique_until collapse log_level locale wrapped
102
102
  ]
103
- # rubocop:enable Style/MutableConstant
104
103
 
105
104
  # `push_bulk`'s envelope keys, on top of the job keys it shares.
106
105
  BULK_KEYS = %w[batch_size spread_interval].freeze
data/lib/wurk/stats.rb CHANGED
@@ -116,9 +116,9 @@ module Wurk
116
116
  # Single pipeline for the cheap counters. Eagerly invoked at initialize
117
117
  # so callers can read many fields without paying per-method round trips.
118
118
  FAST_QUERIES = [
119
- ['GET', 'stat:processed'],
120
- ['GET', 'stat:failed'],
121
- ['GET', Keys::STAT_EXPIRED],
119
+ ['GET', 'stat:processed'],
120
+ ['GET', 'stat:failed'],
121
+ ['GET', Keys::STAT_EXPIRED],
122
122
  ['ZCARD', Keys::SCHEDULE],
123
123
  ['ZCARD', Keys::RETRY],
124
124
  ['ZCARD', Keys::DEAD],
data/lib/wurk/swarm.rb CHANGED
@@ -65,6 +65,25 @@ module Wurk
65
65
  # them mid-tail, so its own wait always extends past theirs by this much.
66
66
  SHUTDOWN_GRACE = 5
67
67
 
68
+ # How often the drain re-relays TERM to whatever is still in the child
69
+ # table. A child forked in the instant before the drain can miss the first
70
+ # one outright: from `Process.fork` returning in the child until ChildBoot
71
+ # resets the inherited traps, the PARENT's handler is what a TERM finds
72
+ # there, and it writes the signal into the parent's self-pipe (or, once
73
+ # `fork_child` has dropped that pipe, into nothing) instead of stopping the
74
+ # child. The child then boots clean, drains nothing, and `wait_for_children`
75
+ # burns its whole budget before `hard_kill_stragglers` SIGKILLs it — taking
76
+ # its in-flight jobs with it. The window is the `_fork` hook chain
77
+ # (ActiveSupport's ForkTracker and friends all run child-side before
78
+ # `Process.fork` returns), so it widens with load: measured 10.5s drains
79
+ # against a 2.6s baseline once that chain is slowed. No child-side fix can
80
+ # close it — the window precedes any code the child controls — so the
81
+ # supervisor repeats itself instead. Re-sending is safe: by the second pass
82
+ # the child owns its handler, TERM to a child already draining is a no-op,
83
+ # and the pids come from the child table, where an exited child is a zombie
84
+ # until reaped and so can never be a recycled pid.
85
+ RETERM_INTERVAL = 1.0
86
+
68
87
  # USR2 is relayed (log reopen) — without a trap, a logrotate config that
69
88
  # signals the master pid would hit USR2's default disposition and kill the
70
89
  # whole swarm.
@@ -341,7 +360,20 @@ module Wurk
341
360
  # stall signal delivery if the pipe fills. `exception: false` returns
342
361
  # :wait_writable instead of raising when full (drop the coalescible
343
362
  # duplicate); a closed pipe during shutdown is ignored too.
363
+ #
364
+ # The owner check is what keeps ONE child's signal from draining the whole
365
+ # fleet. A forked child inherits both this trap and the parent's live pipe,
366
+ # and `fork_child` cannot drop them until `Process.fork` RETURNS — the
367
+ # `_fork` hook chain runs child-side before that, so there is a window
368
+ # where an operator's `kill <child_pid>` writes TERM into the PARENT's
369
+ # supervise loop and drains every sibling. Measured: with that chain slowed
370
+ # the way a loaded box does, TERMing a single child killed the supervisor
371
+ # and all of it. Comparing pids instead closes it with no window at all —
372
+ # the inherited trap is inert in the child from its first instruction,
373
+ # because `@owner_pid` was stamped before the fork.
344
374
  def emit_signal(sig)
375
+ return unless owner?
376
+
345
377
  @signal_write&.write_nonblock("#{sig}\n", exception: false)
346
378
  rescue ::IOError, ::Errno::EPIPE, ::Errno::EBADF
347
379
  nil
@@ -503,8 +535,13 @@ module Wurk
503
535
 
504
536
  def wait_for_children(timeout)
505
537
  deadline = monotonic + timeout
538
+ reterm_at = monotonic + RETERM_INTERVAL
506
539
  while monotonic < deadline && any_children?
507
540
  reap_children
541
+ if monotonic >= reterm_at
542
+ relay_signal('TERM')
543
+ reterm_at = monotonic + RETERM_INTERVAL
544
+ end
508
545
  sleep 0.1
509
546
  end
510
547
  end
data/lib/wurk/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wurk
4
- VERSION = '1.7.3'
4
+ VERSION = '1.7.5'
5
5
  end
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.7.3",
3
- "timestamp": "2026-08-18T15:19:58.150Z"
2
+ "version": "1.7.5",
3
+ "timestamp": "2026-08-27T21:26:34.924Z"
4
4
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wurk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.3
4
+ version: 1.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - developerz.ai