Class: Insika::Tick
- Inherits:
-
Object
- Object
- Insika::Tick
- Defined in:
- lib/insika/tick.rb
Overview
The periodic tick: durability stops waiting for a reboot. One pass does two things, in this order:
1. DRAIN the outbox (`ChannelDelivery#sweep`) — replies a previous pass
(or process) recorded and never claimed. Ungated: every record carries
its own transactional claim, so N workers draining is safe.
2. SWEEP stale orphaned tasks (`Recovery#run(stale_after:)`) — gated by a
bucketed claim (`Recovery.claim_sweep` on "tick:<epoch/interval>"), so
exactly one worker per window sweeps. The staleness threshold is the
liveness gate: a live :running turn is bounded by turn_timeout, so
anything untouched past it cannot be alive.
It is NOT a job queue: no schedules, no priorities, no fan-out. The refinement hook once pictured here is dropped by merit — docs/REFINEMENT.md's "no scheduler in the engine" stands.
Constant Summary collapse
- DEFAULT_INTERVAL =
60s: a customer waiting on WhatsApp is the deadline. 900s = 3x the default turn_timeout (300s) — the rule, not the number: the threshold must exceed the deployment's largest turn_timeout, or the sweep would judge live turns orphaned.
60- DEFAULT_STALE_AFTER =
900- SCOPE =
"tick"- KEY =
"claim"
Instance Method Summary collapse
- #enabled? ⇒ Boolean
-
#initialize(store:, recovery:, channel_delivery:, logger: nil, interval: DEFAULT_INTERVAL, stale_after: DEFAULT_STALE_AFTER, sleeper: nil) ⇒ Tick
constructor
A new instance of Tick.
-
#run_once ⇒ Object
One pass, pure (no reactor needed): the serving loop calls it on a timer, specs call it directly.
-
#start(parent:) ⇒ Object
The loop, spawned as a child of the turn supervisor (the tick lives on the supervisor fiber — every serving arm gets it the moment
supervised = truematters, with no arm edits).
Constructor Details
#initialize(store:, recovery:, channel_delivery:, logger: nil, interval: DEFAULT_INTERVAL, stale_after: DEFAULT_STALE_AFTER, sleeper: nil) ⇒ Tick
Returns a new instance of Tick.
32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/insika/tick.rb', line 32 def initialize(store:, recovery:, channel_delivery:, logger: nil, interval: DEFAULT_INTERVAL, stale_after: DEFAULT_STALE_AFTER, sleeper: nil) @store = store @recovery = recovery @channel_delivery = channel_delivery @logger = logger @interval = interval.to_i @stale_after = stale_after.to_i @sleeper = sleeper || method(:default_sleep) end |
Instance Method Details
#enabled? ⇒ Boolean
44 |
# File 'lib/insika/tick.rb', line 44 def enabled? = @interval.positive? |
#run_once ⇒ Object
One pass, pure (no reactor needed): the serving loop calls it on a timer, specs call it directly. A StoreError propagates to the loop, which logs and keeps ticking.
49 50 51 52 53 54 55 56 |
# File 'lib/insika/tick.rb', line 49 def run_once drained = @channel_delivery ? @channel_delivery.sweep : { dispatched: [] } summary = { dispatched: drained[:dispatched], resumed: [], failed: [] } return summary unless claim_window result = @recovery.run(stale_after: @stale_after) summary.merge(resumed: result[:resumed], failed: result[:failed]) end |
#start(parent:) ⇒ Object
The loop, spawned as a child of the turn supervisor (the
tick lives on the supervisor fiber — every serving arm gets it the moment
supervised = true matters, with no arm edits). A failing pass logs and
the loop continues: a sweeper that dies silently is the outage it exists
to prevent. Restartable: when the supervisor is recreated its children
died with it, so a stopped task is not a running one.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/insika/tick.rb', line 64 def start(parent:) return false unless enabled? return true if @task&.running? @task = parent.async do |t| t.annotate("insika-tick") loop do @sleeper.call(@interval) run_once rescue StandardError => e log(:warn, "tick failed: #{e.class}: #{e.}") end end true end |