Class: Wurk::Middleware::Status

Inherits:
Object
  • Object
show all
Includes:
ServerMiddleware
Defined in:
lib/wurk/middleware/status.rb

Overview

Drives the status:<jid> lifecycle for a worker class that opted in with sidekiq_options track: true. Every other class — the default — costs one Hash lookup and a yield.

running ──┬─▶ complete     perform returned; its value is captured
        ├─▶ interrupted  cooperative stop (IterableJob)
        └─▶ failed ──┬─▶ retrying   a retry was scheduled
                     └─▶ dead       retries exhausted / discarded

(enqueued is written at push time by the client, not here.)

failed is written the moment the attempt raises, because that much is always true. Whether the job then retries or dies is decided one frame further out, in JobRetry — which is why retrying is written from there via Status.retrying and dead from DeathHandler, instead of being guessed at here. Guessing would mean a second implementation of retry policy (retry_for, sidekiq_retry_in returning :discard/:kill, dead: false, poison-pill kills), and the two would drift.

Registered last of the built-ins, inside Metrics::History: the stored result is perform's own return value, so none of them may wrap or replace it before this one sees it.

Defined Under Namespace

Modules: DeathHandler

Constant Summary collapse

MAX_RESULT_BYTES =

Hard cap on a stored result, in bytes. A job that returns an ActiveRecord relation must cost Redis 8 KB, not a table dump — past the cap the head is kept and result_truncated says the rest was dropped.

8 * 1024
MAX_ERROR_BYTES =

Same reasoning for the error text: a parser whose message quotes the whole offending document would otherwise store the document.

1024

Instance Attribute Summary

Attributes included from ServerMiddleware

#config

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ServerMiddleware

#logger, #redis, #redis_pool

Class Method Details

.dead(job) ⇒ Object



102
# File 'lib/wurk/middleware/status.rb', line 102

def dead(job) = record_outcome(job, 'dead')

.report(error, context) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



116
117
118
# File 'lib/wurk/middleware/status.rb', line 116

def report(error, context)
  Wurk.configuration.handle_exception(error, context: "Wurk::Middleware::Status: #{context}")
end

.retrying(job) ⇒ Object

Called by JobRetry#schedule_retry once the retry is on the ZSET — the first moment anyone knows this failure is a retry rather than a death.



100
# File 'lib/wurk/middleware/status.rb', line 100

def retrying(job) = record_outcome(job, 'retrying')

.truncate(text, limit) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

byteslice, not slice: the cap is a byte budget, and 8192 characters is 32 KB of UTF-8 in the worst case. Cutting on a byte boundary can split a multi-byte character, so the dangling tail is scrubbed away.



109
110
111
112
113
# File 'lib/wurk/middleware/status.rb', line 109

def truncate(text, limit)
  return text if text.bytesize <= limit

  text.byteslice(0, limit).scrub('')
end

Instance Method Details

#call(worker, job, queue) ⇒ Object

The lifecycle is inline rather than split across a helper so the untracked path is a Hash lookup and a bare yield: taking the block as &block, or handing it to another method, allocates a Proc for every job in the process, tracked or not.

Rescue taxonomy mirrors Batch::ServerMiddleware — a handled skip and a cooperative interruption are not failures. A job cut by its absolute deadline deliberately is one here, where the two metric emitters count it as processed-and-expired instead: those feed an aggregate an operator subtracts (executed = processed - failed - expired), while this row answers "what happened to this job" — it did not finish, and error_class/error_message say why. interrupted would promise a resume that never comes. Caveat inherited from the chain position it shares with Metrics::History: a Limiter::OverLimit raised inside the job body unwinds through here before Limiter (outer) converts it into a reschedule, so a rate-limited job books failed for an attempt it never ran. Pinned for History in test/unit/metrics_history_test.rb; fixing it needs the chain-order decision that test defers, and Status must not answer it differently from its two siblings in the meantime.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/wurk/middleware/status.rb', line 64

def call(worker, job, queue)
  jid = job['jid']
  return yield unless ::Wurk::Status.tracked?(job) && jid && !jid.empty?

  progress = attach_progress(worker, jid)
  started(jid, job, queue)
  begin
    result = yield
    complete(jid, job, progress, result)
    result
  rescue ::Wurk::Job::Interrupted
    interrupted(jid, progress)
    raise
  rescue ::Wurk::JobRetry::Handled
    # Neither success nor failure: the job has been put back on a queue
    # and will run again. Leave the row `running` and keep the progress it
    # reported, so the next attempt continues the same story.
    safely { progress.flush }
    raise
  rescue StandardError => e
    failed(jid, progress, e)
    raise
  end
end