wurk 1.6.0 → 1.7.0
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 +9 -7
- data/app/controllers/wurk/dashboard_controller.rb +1 -0
- data/lib/wurk/cli.rb +11 -4
- data/lib/wurk/command_builder.rb +49 -0
- data/lib/wurk/component.rb +2 -1
- data/lib/wurk/fetcher/reliable.rb +2 -1
- data/lib/wurk/fetcher/unit_of_work.rb +8 -1
- data/lib/wurk/job_logger.rb +6 -2
- data/lib/wurk/pid_cache.rb +43 -0
- data/lib/wurk/profiler.rb +5 -1
- data/lib/wurk/redis_pool.rb +5 -2
- data/lib/wurk/version.rb +1 -1
- data/lib/wurk/web/extension.rb +10 -2
- data/lib/wurk.rb +2 -0
- data/vendor/assets/dashboard/wurk-manifest.json +2 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ce7dc086fc21fd3dd300206e2c9b15b2e8720a59e8186ea1459b66067bf6148
|
|
4
|
+
data.tar.gz: 1a659643c694f6fadd7bd34313604df1093274c4e50597c00ff767db4376f3ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6dd4d7643349e6ed95496e5149f19f3ca832589f4475b5de8e03f438dc4e445a3871b1ccf6240754bb85708f78a8118962234a465f3afa150b9753368a6fe77
|
|
7
|
+
data.tar.gz: 4a31cfe2170ab1d726db165d354edc00763e14b118b0219f15bfb0bc0de909c03a795829196e8a6b6e56c7f5e013a3dde4b7d94d0ebf49f2aec0c75d2c6a267e
|
data/README.md
CHANGED
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
|
|
22
22
|
Wurk is wire-compatible with Sidekiq — same Redis keys, same job JSON, same Ruby DSL. Swap one line in your `Gemfile` and your existing jobs, batches, limiters, cron entries, and live Redis data keep working untouched. The Pro and Enterprise feature sets ship in the same free gem, with no license check and no tiers.
|
|
23
23
|
|
|
24
|
-
**
|
|
24
|
+
**In production:** Wurk runs the background work at [developerz.ai](https://developerz.ai) and at partner deployments — millions of jobs an hour, across many servers, on the fork-based swarm described below. It is not a preview.
|
|
25
|
+
|
|
26
|
+
**At scale:** Wurk is built for fleets, not just for one box. Kubernetes `/live` + `/ready` probes are a config line, not a sidecar; a bearer-scoped [HTTP API](docs/api-http.md) lets non-Ruby services enqueue and inspect; [OpenTelemetry](docs/telemetry.md) traces propagate client → server; per-queue [global concurrency caps](docs/rate-limiting.md) hold cluster-wide limits; and monitoring is the dashboard you already mount — live SSE, charts, per-job progress, no separate stack to run. See [Wurk extras](#wurk-extras).
|
|
27
|
+
|
|
28
|
+
**On Sidekiq:** Wurk implements Sidekiq's API because it is a genuinely good API. Sidekiq is human-maintained and funds that work through its paid tiers; Wurk is AI-maintained, which is what lets the same surface be free software. Wurk is independent and not affiliated with or endorsed by Sidekiq or its maintainers — see [Why Wurk exists](#why-wurk-exists).
|
|
25
29
|
|
|
26
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`.
|
|
27
31
|
|
|
@@ -205,18 +209,16 @@ Knobs: `health_check(port:, bind: "0.0.0.0", ready_window: 30)`. In swarm mode o
|
|
|
205
209
|
|
|
206
210
|
## Why Wurk exists
|
|
207
211
|
|
|
208
|
-
|
|
212
|
+
Infrastructure this basic should be free software. A Rails app shouldn't need a licence key to get reliable fetch, batches, rate limiting, or cron — those are table stakes, not a premium tier, and the free-software tradition is that the best tools belong to everyone who runs them.
|
|
209
213
|
|
|
210
|
-
What
|
|
214
|
+
What has made that hard is maintenance: someone has to be paid to do it. Sidekiq funds a decade of *human* maintenance through its paid tiers, which is an honest trade. Wurk makes a different one — it is maintained **AI-first**: implementation, parity suite, docs, and benchmarks are written and kept current by AI agents under human review. A fix, a doc update, or a version bump is no longer somebody's week, which is what makes it practical to:
|
|
211
215
|
|
|
212
216
|
- ship the entire Pro + Enterprise surface with no tier, no flag gate, and no license check;
|
|
213
217
|
- keep parity honest mechanically rather than by hand — Sidekiq's own tests run as an oracle suite, and third-party gems (sidekiq-cron, sidekiq-unique-jobs, sidekiq-scheduler, sidekiq-status, sidekiq-failures, sidekiq-throttled) run their upstream suites against Wurk on every push;
|
|
214
218
|
- keep adding surface Sidekiq doesn't have — the [Wurk extras](#wurk-extras) above landed as one release;
|
|
215
|
-
-
|
|
216
|
-
|
|
217
|
-
It also means Wurk holds itself to published numbers instead of adjectives: the benchmark suite runs against stock Sidekiq every release and the results ship [as measured](docs/benchmarks.md), including the unflattering ones.
|
|
219
|
+
- hold ourselves to published numbers instead of adjectives — the suite runs against stock Sidekiq every release and ships the results [as measured](docs/benchmarks.md), including the unflattering ones.
|
|
218
220
|
|
|
219
|
-
|
|
221
|
+
Wurk is MIT and stays that way. If what you need is a commercial support contract and a human on the other end of an email, buying that is a perfectly good answer.
|
|
220
222
|
|
|
221
223
|
## Migrating from Sidekiq
|
|
222
224
|
|
data/lib/wurk/cli.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require
|
|
6
|
-
|
|
3
|
+
# yaml, optparse and erb are required inside the two methods that use them
|
|
4
|
+
# (#parse_config and #option_parser) rather than here. Together they cost ~45ms
|
|
5
|
+
# of the ~290ms `require "wurk"` spends, and only `exe/wurk` ever reaches them —
|
|
6
|
+
# a Rails app that enqueues and processes jobs pays that on every boot for a
|
|
7
|
+
# command-line parser it never runs. `require` is idempotent, so the lazy call
|
|
8
|
+
# is a $LOADED_FEATURES hash lookup on every invocation after the first.
|
|
7
9
|
require_relative 'version'
|
|
8
10
|
require_relative 'configuration'
|
|
9
11
|
require_relative 'component'
|
|
@@ -410,6 +412,9 @@ module Wurk
|
|
|
410
412
|
def parse_config(path)
|
|
411
413
|
raise ArgumentError, "No such file #{path}" unless ::File.exist?(path)
|
|
412
414
|
|
|
415
|
+
require 'erb'
|
|
416
|
+
require 'yaml'
|
|
417
|
+
|
|
413
418
|
erb = ::ERB.new(::File.read(path), trim_mode: '-')
|
|
414
419
|
erb.filename = ::File.expand_path(path)
|
|
415
420
|
opts = ::YAML.safe_load(erb.result, permitted_classes: [Symbol], aliases: true) || {}
|
|
@@ -444,6 +449,8 @@ module Wurk
|
|
|
444
449
|
end
|
|
445
450
|
|
|
446
451
|
def option_parser(opts)
|
|
452
|
+
require 'optparse'
|
|
453
|
+
|
|
447
454
|
::OptionParser.new do |o|
|
|
448
455
|
o.banner = @command ? "wurk #{@command} [options]" : 'wurk [options]'
|
|
449
456
|
define_value_flags(o, opts, OPTION_FLAGS)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'redis-client'
|
|
4
|
+
|
|
5
|
+
module Wurk
|
|
6
|
+
# Fast path for redis-client's command normalization.
|
|
7
|
+
#
|
|
8
|
+
# `RedisClient::CommandBuilder.generate` splices Hash arguments with a
|
|
9
|
+
# `flat_map` and then stringifies Symbols/Integers/Floats with a `map!` —
|
|
10
|
+
# two array allocations and two per-element type dispatches for a command
|
|
11
|
+
# whose arguments are already Strings. Every command Wurk sends per job is
|
|
12
|
+
# exactly that shape (LMOVE, LREM, DEL, LPUSH, SADD), and the fetch pipeline
|
|
13
|
+
# sends three of them. Measured ~4x faster on those commands (2.2µs → 0.6µs
|
|
14
|
+
# each) with `bench/command_builder.rb`.
|
|
15
|
+
#
|
|
16
|
+
# Anything else — a Hash to splice, a Symbol or a number to stringify,
|
|
17
|
+
# keyword arguments, an empty command — falls straight through to
|
|
18
|
+
# redis-client's own builder. This is a shortcut, never a second
|
|
19
|
+
# implementation of the semantics, so a host app calling
|
|
20
|
+
# `Sidekiq.redis { |c| c.call("HSET", key, hash) }` is unaffected.
|
|
21
|
+
#
|
|
22
|
+
# The array is duped rather than handed back: `call_v` and the pipelined
|
|
23
|
+
# forms pass the caller's own array through, and redis-client's builder
|
|
24
|
+
# always returns a fresh one that middleware is free to mutate in place.
|
|
25
|
+
module CommandBuilder
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def generate(args, kwargs = nil)
|
|
29
|
+
return args.dup if fast?(args, kwargs)
|
|
30
|
+
|
|
31
|
+
::RedisClient::CommandBuilder.generate(args, kwargs)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Split out, and public, because the two branches produce byte-identical
|
|
35
|
+
# output by construction — which leaves allocation count as the only
|
|
36
|
+
# runtime difference between "the shortcut fired" and "it silently didn't",
|
|
37
|
+
# and that is not something a test can assert safely in a process running
|
|
38
|
+
# other suites. So the decision itself is what the suite asserts.
|
|
39
|
+
#
|
|
40
|
+
# `kwargs` is nil only from `call_v` and the scan helpers. Every `call`,
|
|
41
|
+
# `blocking_call` and pipelined `call` arrives with `**kwargs` already
|
|
42
|
+
# splatted into a Hash — EMPTY when the caller passed no keywords — so a
|
|
43
|
+
# guard that only tested `.nil?` sent the entire hot path down the slow
|
|
44
|
+
# branch while every direct-call test still passed.
|
|
45
|
+
def fast?(args, kwargs)
|
|
46
|
+
(kwargs.nil? || kwargs.empty?) && !args.empty? && args.all?(String)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/wurk/component.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'socket'
|
|
4
4
|
require 'securerandom'
|
|
5
|
+
require_relative 'pid_cache'
|
|
5
6
|
|
|
6
7
|
module Wurk
|
|
7
8
|
# Shared mixin for runtime components (Launcher, Manager, Processor, Fetcher,
|
|
@@ -55,7 +56,7 @@ module Wurk
|
|
|
55
56
|
def self.tid
|
|
56
57
|
thread = Thread.current
|
|
57
58
|
memo = thread.thread_variable_get(:wurk_tid)
|
|
58
|
-
pid =
|
|
59
|
+
pid = PidCache.pid
|
|
59
60
|
return memo[1] if memo && memo[0] == pid
|
|
60
61
|
|
|
61
62
|
id = (thread.object_id ^ pid).to_s(36).freeze
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'socket'
|
|
4
4
|
require_relative '../component'
|
|
5
|
+
require_relative '../pid_cache'
|
|
5
6
|
require_relative '../keys'
|
|
6
7
|
require_relative '../lua'
|
|
7
8
|
require_relative '../fetcher'
|
|
@@ -366,7 +367,7 @@ module Wurk
|
|
|
366
367
|
# fetcher and read this without a lock. A racing writer's entry can be
|
|
367
368
|
# lost, and the next fetch rebuilds it.
|
|
368
369
|
def queue_keys(public_q)
|
|
369
|
-
pid =
|
|
370
|
+
pid = PidCache.pid
|
|
370
371
|
if @queue_keys_pid != pid
|
|
371
372
|
@queue_keys = {}
|
|
372
373
|
@queue_keys_pid = pid
|
|
@@ -32,6 +32,13 @@ module Wurk
|
|
|
32
32
|
# admitted under, and are set only by Fetcher::Capped — nil is the whole
|
|
33
33
|
# uncapped path, which is why the release below is a field test rather
|
|
34
34
|
# than a question asked of Redis.
|
|
35
|
+
# LREM's count, pre-stringified. Every argument of every command on the
|
|
36
|
+
# ACK pipeline is then a String, which is what keeps it on
|
|
37
|
+
# {Wurk::CommandBuilder}'s allocation-free fast path; an Integer here
|
|
38
|
+
# would send the whole command back through redis-client's normalizer.
|
|
39
|
+
LREM_COUNT = '1'
|
|
40
|
+
private_constant :LREM_COUNT
|
|
41
|
+
|
|
35
42
|
UnitOfWork = Struct.new(:queue, :queue_name, :private_queue, :job, :config, :jid, :fetcher,
|
|
36
43
|
:slot_key, :slot_token, keyword_init: true) do
|
|
37
44
|
# Deferred, never skipped: the LREM goes back to the fetcher, which
|
|
@@ -55,7 +62,7 @@ module Wurk
|
|
|
55
62
|
# wire-frozen, so the reaper cannot flag it). Reading the counter to
|
|
56
63
|
# decide would spend the very round trip the DEL is riding for free.
|
|
57
64
|
def write_ack(pipe)
|
|
58
|
-
pipe.call('LREM', private_queue,
|
|
65
|
+
pipe.call('LREM', private_queue, LREM_COUNT, job)
|
|
59
66
|
job_jid = jid.to_s
|
|
60
67
|
Middleware::PoisonPill.clear_in(pipe, job_jid) unless job_jid.empty?
|
|
61
68
|
release_slot_in(pipe) if slot_key
|
data/lib/wurk/job_logger.rb
CHANGED
|
@@ -20,6 +20,10 @@ module Wurk
|
|
|
20
20
|
def initialize(config)
|
|
21
21
|
@config = config
|
|
22
22
|
@logger = @config.logger
|
|
23
|
+
# Read once: `config[]` is a Hash lookup through a delegating Capsule, and
|
|
24
|
+
# #context_hash runs it per job only to walk a list that cannot change
|
|
25
|
+
# after boot (Configuration freezes at launch).
|
|
26
|
+
@logged_attributes = Array(@config[:logged_job_attributes]).map { |attr| [attr, attr.to_sym] }.freeze
|
|
23
27
|
@skip = !!@config[:skip_default_job_logging]
|
|
24
28
|
end
|
|
25
29
|
|
|
@@ -68,8 +72,8 @@ module Wurk
|
|
|
68
72
|
class: job_hash['wrapped'] || job_hash['class']
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
@
|
|
72
|
-
h[
|
|
75
|
+
@logged_attributes.each do |attr, sym|
|
|
76
|
+
h[sym] = job_hash[attr] if job_hash.key?(attr)
|
|
73
77
|
end
|
|
74
78
|
h
|
|
75
79
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wurk
|
|
4
|
+
# `Process.pid` is a real syscall on Linux — glibc dropped its getpid cache in
|
|
5
|
+
# 2.25 — and the fetch+execute path reads it twice per job ({Component.tid}
|
|
6
|
+
# and the fetcher's per-queue key cache, both of which read it only to notice
|
|
7
|
+
# a fork). Measured at ~640ns a call against ~110ns for this memo, which is
|
|
8
|
+
# about 1% of a job's CPU spent asking the kernel a question whose answer
|
|
9
|
+
# changes at most once in a process's life.
|
|
10
|
+
#
|
|
11
|
+
# Refreshed in the child half of every fork, so a swarm child never answers
|
|
12
|
+
# with the pid it inherited — the whole reason those two call sites read it.
|
|
13
|
+
#
|
|
14
|
+
# Same shape as redis-client's PIDCache, deliberately not borrowed from it:
|
|
15
|
+
# that is a private constant of a dependency, and this one is on our hot path.
|
|
16
|
+
module PidCache
|
|
17
|
+
if ::Process.respond_to?(:fork)
|
|
18
|
+
class << self
|
|
19
|
+
attr_reader :pid
|
|
20
|
+
|
|
21
|
+
def update!
|
|
22
|
+
@pid = ::Process.pid
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
update!
|
|
26
|
+
|
|
27
|
+
# Prepended, not aliased: `Process._fork` is a documented hook (Ruby 3.1+,
|
|
28
|
+
# and Wurk requires 3.2) that several gems wrap, and prepending composes
|
|
29
|
+
# with theirs instead of replacing it.
|
|
30
|
+
module CoreExt
|
|
31
|
+
def _fork
|
|
32
|
+
child_pid = super
|
|
33
|
+
PidCache.update! if child_pid.zero?
|
|
34
|
+
child_pid
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
::Process.singleton_class.prepend(CoreExt)
|
|
38
|
+
else # JRuby / TruffleRuby / Windows — no fork, so nothing can invalidate it
|
|
39
|
+
@pid = ::Process.pid
|
|
40
|
+
singleton_class.attr_reader(:pid)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/wurk/profiler.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'securerandom'
|
|
4
4
|
require 'zlib'
|
|
5
5
|
require 'stringio'
|
|
6
|
-
require 'tempfile'
|
|
7
6
|
require_relative 'keys'
|
|
8
7
|
require_relative 'pool_checkout'
|
|
9
8
|
|
|
@@ -109,7 +108,12 @@ module Wurk
|
|
|
109
108
|
Wurk.configuration.handle_exception(e, context: 'Wurk::Profiler')
|
|
110
109
|
end
|
|
111
110
|
|
|
111
|
+
# `tempfile` (and the `tmpdir` it drags in) is ~19ms of `require "wurk"`,
|
|
112
|
+
# spent only by an install that has vernier loaded AND profiling switched
|
|
113
|
+
# on for a job. Everyone else was paying it at boot.
|
|
112
114
|
def profile_to_json(&)
|
|
115
|
+
require 'tempfile'
|
|
116
|
+
|
|
113
117
|
Tempfile.create(['wurk-profile', '.json']) do |file|
|
|
114
118
|
::Vernier.profile(out: file.path, &)
|
|
115
119
|
File.read(file.path)
|
data/lib/wurk/redis_pool.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'redis-client'
|
|
4
4
|
require 'connection_pool'
|
|
5
|
+
require_relative 'command_builder'
|
|
5
6
|
require_relative 'redis_client_adapter'
|
|
6
7
|
require_relative 'redis_options'
|
|
7
8
|
|
|
@@ -52,13 +53,15 @@ module Wurk
|
|
|
52
53
|
DEFAULT_WRITE_TIMEOUT = 2.5
|
|
53
54
|
DEFAULT_RECONNECT_ATTEMPTS = 1
|
|
54
55
|
|
|
55
|
-
# The floor every pool starts from; any key the host passed wins over it
|
|
56
|
+
# The floor every pool starts from; any key the host passed wins over it —
|
|
57
|
+
# including `command_builder`, so an app that has its own keeps it.
|
|
56
58
|
DEFAULT_CLIENT_CONFIG = {
|
|
57
59
|
url: DEFAULT_URL,
|
|
58
60
|
connect_timeout: DEFAULT_CONNECT_TIMEOUT,
|
|
59
61
|
read_timeout: DEFAULT_READ_TIMEOUT,
|
|
60
62
|
write_timeout: DEFAULT_WRITE_TIMEOUT,
|
|
61
|
-
reconnect_attempts: DEFAULT_RECONNECT_ATTEMPTS
|
|
63
|
+
reconnect_attempts: DEFAULT_RECONNECT_ATTEMPTS,
|
|
64
|
+
command_builder: CommandBuilder
|
|
62
65
|
}.freeze
|
|
63
66
|
|
|
64
67
|
# Server-side messages where the connection is closed and the block retried
|
data/lib/wurk/version.rb
CHANGED
data/lib/wurk/web/extension.rb
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# erb and cgi are required by #call, the single entry point every extension
|
|
4
|
+
# request comes through, rather than here: together with the `cgi` erb drags in
|
|
5
|
+
# they are ~40ms of the ~220ms `require "wurk"` costs, and they are reachable
|
|
6
|
+
# only from a host that has actually registered a Web extension. A worker
|
|
7
|
+
# process — which loads this file because `wurk.rb` loads the web layer — never
|
|
8
|
+
# renders anything. `require` is idempotent, so this is a $LOADED_FEATURES
|
|
9
|
+
# lookup per request after the first.
|
|
5
10
|
require 'securerandom'
|
|
6
11
|
|
|
7
12
|
module Wurk
|
|
@@ -230,6 +235,9 @@ module Wurk
|
|
|
230
235
|
ext = registered_extension(name)
|
|
231
236
|
return nil unless ext
|
|
232
237
|
|
|
238
|
+
require 'erb'
|
|
239
|
+
require 'cgi'
|
|
240
|
+
|
|
233
241
|
verb = method.to_s.upcase
|
|
234
242
|
route, block, route_params = match_route(ext, verb, subpath)
|
|
235
243
|
return [404, html_headers, "No #{verb} route #{subpath} in extension #{name}"] unless route
|
data/lib/wurk.rb
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
require_relative 'wurk/errors'
|
|
9
9
|
require_relative 'wurk/version'
|
|
10
10
|
require_relative 'wurk/keys'
|
|
11
|
+
require_relative 'wurk/pid_cache'
|
|
12
|
+
require_relative 'wurk/command_builder'
|
|
11
13
|
require_relative 'wurk/redis_pool'
|
|
12
14
|
require_relative 'wurk/pool_checkout'
|
|
13
15
|
require_relative 'wurk/redis_connection'
|
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.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- developerz.ai
|
|
@@ -224,6 +224,7 @@ files:
|
|
|
224
224
|
- lib/wurk/client.rb
|
|
225
225
|
- lib/wurk/client/buffered.rb
|
|
226
226
|
- lib/wurk/collapse.rb
|
|
227
|
+
- lib/wurk/command_builder.rb
|
|
227
228
|
- lib/wurk/compat.rb
|
|
228
229
|
- lib/wurk/component.rb
|
|
229
230
|
- lib/wurk/configuration.rb
|
|
@@ -317,6 +318,7 @@ files:
|
|
|
317
318
|
- lib/wurk/middleware/poison_pill.rb
|
|
318
319
|
- lib/wurk/middleware/status.rb
|
|
319
320
|
- lib/wurk/middleware/timeout.rb
|
|
321
|
+
- lib/wurk/pid_cache.rb
|
|
320
322
|
- lib/wurk/pool_checkout.rb
|
|
321
323
|
- lib/wurk/process_set.rb
|
|
322
324
|
- lib/wurk/processor.rb
|