Class: Wurk::Middleware::Chain
- Inherits:
-
Object
- Object
- Wurk::Middleware::Chain
- Includes:
- Enumerable
- Defined in:
- lib/wurk/middleware/chain.rb
Overview
Ordered list of middleware bound to a config/capsule. Sidekiq contract:
add appends (removing any existing entry for the same klass), prepend
pushes to index 0, insert_before/insert_after anchor relative to an
existing entry, invoke walks the chain handing the inner block to each.
add has one opt-in exception, declared per middleware class: a class
may name the classes it must stay OUTSIDE of in a class-level outer_of,
and add then places it before the first of those anchors instead of at
the tail. A class that declares no anchor keeps the plain append.
Entries store klass + ctor args + the config they were registered with,
not the live object: every job gets a
fresh instance per entry (invoke builds each as it reaches it, retrieve
builds them all up front). This keeps middleware thread-safe by
construction and matches Sidekiq's lifecycle.
Spec: docs/target/sidekiq-free.md §10.1.
Defined Under Namespace
Classes: Entry
Instance Attribute Summary collapse
-
#config ⇒ Object
Returns the value of attribute config.
-
#entries ⇒ Object
readonly
Returns the value of attribute entries.
Instance Method Summary collapse
-
#add(klass) ⇒ Object
Appends the middleware — the documented Sidekiq behaviour (spec §10.1): any existing entry for
klassis removed and the new one lands at the tail (innermost). - #clear ⇒ Object
-
#copy_for(capsule) ⇒ Object
Bind a duplicate of this chain to
capsule. - #each ⇒ Object
- #empty? ⇒ Boolean
- #exists?(klass) ⇒ Boolean (also: #include?)
-
#initialize(config = nil, on_registration: nil) {|_self| ... } ⇒ Chain
constructor
A new instance of Chain.
- #insert_after(oldklass, newklass) ⇒ Object
- #insert_before(oldklass, newklass) ⇒ Object
-
#invoke(*args, &block) ⇒ Object
Walks the chain inside-out.
- #prepend(klass) ⇒ Object
- #remove(klass) ⇒ Object
- #retrieve ⇒ Object
Constructor Details
#initialize(config = nil, on_registration: nil) {|_self| ... } ⇒ Chain
Returns a new instance of Chain.
28 29 30 31 32 33 |
# File 'lib/wurk/middleware/chain.rb', line 28 def initialize(config = nil, on_registration: nil) @config = config @on_registration = on_registration @entries = [] yield self if block_given? end |
Instance Attribute Details
#config ⇒ Object
Returns the value of attribute config.
26 27 28 |
# File 'lib/wurk/middleware/chain.rb', line 26 def config @config end |
#entries ⇒ Object (readonly)
Returns the value of attribute entries.
25 26 27 |
# File 'lib/wurk/middleware/chain.rb', line 25 def entries @entries end |
Instance Method Details
#add(klass) ⇒ Object
Appends the middleware — the documented Sidekiq behaviour (spec
§10.1): any existing entry for klass is removed and the new one
lands at the tail (innermost).
One opt-in exception, declared per class: a middleware may name the
classes it must stay OUTSIDE of in a class-level outer_of. When one
of those anchors is registered, the entry is placed before the first
of them instead of at the tail — so re-registering such a middleware
cannot silently move it inside the middleware it depends on being
outside of. A class that declares no anchor keeps the plain append.
An entry already registered before the anchor keeps its slot (only its ctor args are refreshed): moving it would be a relocation the caller did not ask for.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/wurk/middleware/chain.rb', line 71 def add(klass, *) @on_registration&.call(klass) entry = make_entry(klass, *) anchors = anchor_classes(klass) existing = @entries.index { |e| e.klass == klass } anchor = @entries.index { |e| anchors.include?(e.klass) } # No anchor registered: remove the old entry, append at the tail. if anchor.nil? remove(klass) @entries << entry return @entries end # Already outside the anchor: keep the slot, refresh the entry. if existing && existing < anchor @entries[existing] = entry return @entries end # Registered inside the anchor (or not registered at all): move it # out, to just before the first anchor. remove(klass) @entries.insert(anchor, entry) @entries end |
#clear ⇒ Object
133 134 135 |
# File 'lib/wurk/middleware/chain.rb', line 133 def clear @entries.clear end |
#copy_for(capsule) ⇒ Object
Bind a duplicate of this chain to capsule. Entries carry their
config, so each is duplicated and only the duplicate rebound: the
capsule's middleware resolve the capsule while the parent's entries
keep the parent config. Array#dup on @entries keeps the two chains
independent after the first add/remove/etc.
42 43 44 45 46 47 48 49 50 51 |
# File 'lib/wurk/middleware/chain.rb', line 42 def copy_for(capsule) copy = dup copy.instance_variable_set(:@config, capsule) copy.entries.map! do |entry| rebound = entry.dup rebound.config = capsule rebound end copy end |
#each ⇒ Object
35 |
# File 'lib/wurk/middleware/chain.rb', line 35 def each(&) = @entries.each(&) |
#empty? ⇒ Boolean
125 126 127 |
# File 'lib/wurk/middleware/chain.rb', line 125 def empty? @entries.empty? end |
#exists?(klass) ⇒ Boolean Also known as: include?
120 121 122 |
# File 'lib/wurk/middleware/chain.rb', line 120 def exists?(klass) any? { |entry| entry.klass == klass } end |
#insert_after(oldklass, newklass) ⇒ Object
112 113 114 115 116 117 118 |
# File 'lib/wurk/middleware/chain.rb', line 112 def insert_after(oldklass, newklass, *) @on_registration&.call(newklass) i = @entries.index { |entry| entry.klass == newklass } new_entry = i.nil? ? make_entry(newklass, *) : @entries.delete_at(i) i = @entries.index { |entry| entry.klass == oldklass } || (@entries.size - 1) @entries.insert(i + 1, new_entry) end |
#insert_before(oldklass, newklass) ⇒ Object
104 105 106 107 108 109 110 |
# File 'lib/wurk/middleware/chain.rb', line 104 def insert_before(oldklass, newklass, *) @on_registration&.call(newklass) i = @entries.index { |entry| entry.klass == newklass } new_entry = i.nil? ? make_entry(newklass, *) : @entries.delete_at(i) i = @entries.index { |entry| entry.klass == oldklass } || 0 @entries.insert(i, new_entry) end |
#invoke(*args, &block) ⇒ Object
Walks the chain inside-out. Each middleware receives a block that
advances to the next; the innermost block is the caller's &block,
whose return value is propagated back out. Empty chain: yield.
Hot path — one job pays this per invocation, so instantiation is folded
into an index walk over @entries: no instance array from retrieve,
no traversal lambda, and no shift (which memmoves the array per entry).
Cost is one allocation per entry (the middleware itself), zero to walk.
145 146 147 148 149 150 |
# File 'lib/wurk/middleware/chain.rb', line 145 def invoke(*args, &block) raise ArgumentError, 'middleware chain requires a block' unless block return yield if @entries.empty? traverse(@entries, 0, args, &block) end |
#prepend(klass) ⇒ Object
98 99 100 101 102 |
# File 'lib/wurk/middleware/chain.rb', line 98 def prepend(klass, *) @on_registration&.call(klass) remove(klass) @entries.unshift(make_entry(klass, *)) end |
#remove(klass) ⇒ Object
53 54 55 |
# File 'lib/wurk/middleware/chain.rb', line 53 def remove(klass) @entries.delete_if { |entry| entry.klass == klass } end |
#retrieve ⇒ Object
129 130 131 |
# File 'lib/wurk/middleware/chain.rb', line 129 def retrieve map(&:make_new) end |