Skip to content

Unhealable-gap strike defense (reintroduced for review) - #40

Closed
jpcamara wants to merge 3 commits into
mainfrom
feat/gap-strike-defense-v2
Closed

Unhealable-gap strike defense (reintroduced for review)#40
jpcamara wants to merge 3 commits into
mainfrom
feat/gap-strike-defense-v2

Conversation

@jpcamara

@jpcamara jpcamara commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Adds an unhealable-gap strike defense to the sync channel (now in yrby-rails).

Release context: these fixes shipped briefly as 0.3.0, were reverted from main (261e648), and were superseded on RubyGems by 0.3.1, which carried the identical fixes with the strike defense removed. This PR reintroduces the strike defense for review; its changelog entry sits in the Unreleased section.

After gap_strike_limit rejections (default 3, minimum 2) of the same update on one connection, the channel stops resyncing it forever and settles it with { "ack" => id, "dropped" => true }. This breaks the poisoned-client retransmit loop (id-less frames several times a second) while never dropping a healable gap, since those heal within a resync or two and healing frees the strike.

Design points:

  • sync_gap_strike / eviction (lib/y/action_cable/sync.rb): a SHA-256-keyed per-connection table bounded at 64 keys. At capacity it evicts one lowest-count entry, only on new-key insert, so distinct-gap flooding can't reset a tracked key. Mutex-guarded, since ActionCable dispatches one connection's messages to a worker pool.
  • AnyCable state: strikes persist across the per-RPC fresh channel instance via anycable-rails' state_attr_accessor (istate, round-tripped through anycable-go), declared automatically at include time, with a plain-ivar fallback when anycable-rails is absent.
  • The dropped ack flag and client handling (yrby-client): the client prunes the queue either way (retransmitting an unhealable update loops forever) but surfaces the drop via onError(..., "ack-dropped") instead of reporting synced over lost data. Older clients ignore the key.
  • gap_strike_limit floor of 2 (ArgumentError below): strike 1 must send a resync before any drop is justified. nil disables the feature entirely (resync-forever, the prior behavior).

Verification: 107 Ruby tests including an AnyCable simulation (fresh instance per message plus JSON istate round-trip), strike-out, eviction-can't-reset (72-update GapFlood fixture), heal-frees-slot, dropped-flag-only-on-settle, and limit validation; 54 client tests including dropped-ack surfacing; and e2e on both stacks (frontend/gap_strike.mjs, wired into CI): poisoned client → resync ×2 → {ack, dropped: true}, never recorded, healable gap still records; 9/9 on Puma ActionCable, 9/9 on AnyCable.

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@jpcamara jpcamara changed the title yrby-actioncable 0.4.0: unhealable-gap strike defense (reopened for review) yrby-actioncable 0.4.0: unhealable-gap strike defense Jul 19, 2026
@jpcamara
jpcamara force-pushed the feat/gap-strike-defense-v2 branch 2 times, most recently from 48fa998 to 25f2a05 Compare August 4, 2026 05:20
jpcamara and others added 2 commits August 5, 2026 14:57
Restores the gap-strike work that was merged and published prematurely
(actioncable 0.3.0) and then withdrawn from main (0.3.1). Content is
identical to what shipped in 0.3.0, reconciled to version 0.4.0 on top of
the published 0.3.1 history. Opened as a PR for actual review this time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TeaovH2jyHARHJSyQ8Afo
@jpcamara
jpcamara force-pushed the feat/gap-strike-defense-v2 branch from 25f2a05 to 4aa84ad Compare August 5, 2026 18:58
@jpcamara jpcamara changed the title yrby-actioncable 0.4.0: unhealable-gap strike defense Unhealable-gap strike defense (reintroduced for review) Aug 5, 2026
…se-v2

# Conflicts:
#	CHANGELOG-rails.md
#	lib/y/action_cable/sync.rb
@jpcamara

Copy link
Copy Markdown
Owner Author

Moot after #56: causally gapped updates are recorded and acked like any other, so there is no reject path left to storm and nothing for a strike defense to defend.

@jpcamara jpcamara closed this Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant