Skip to content

fix: re-create the message session on refresh 404 instead of failing - #123

Open
buildpulser wants to merge 1 commit into
actions:mainfrom
buildpulser:session-recreate-on-404
Open

fix: re-create the message session on refresh 404 instead of failing#123
buildpulser wants to merge 1 commit into
actions:mainfrom
buildpulser:session-recreate-on-404

Conversation

@buildpulser

Copy link
Copy Markdown

We run ARC (0.14.2) for a fleet of orgs and hit a nasty failure mode on one of them: the broker evicts every message session server-side, so the listener's session refresh always comes back 404 with RunnerAdminException: runner referenced a session ID that doesn't exist in redis. The client treats any refresh failure as fatal, the error bubbles out of GetMessage, and the listener process exits.

Since the refresh happens on the token cycle, the listener for that org died every ~50 minutes, around the clock, for almost two weeks before we noticed. Readiness probes never caught it because the pod is back in a few seconds. The part that actually hurts is that each restart throws away the in-memory lastMessageID, so the message cursor resets to 0 every time.

There are a few issues in the ARC repo that look like the same family (actions/actions-runner-controller#4356, actions/actions-runner-controller#3942), and actions/actions-runner-controller#4571 explicitly punts on healing an already crash-looping listener, so the session client seemed like the right place to fix it.

The change: when a session refresh comes back 404, create a new session in place and retry once instead of giving up. A 404 on refresh means the service doesn't know the session anymore, so there's nothing left to lose by re-creating. Everything else (500s, network errors, token expiry itself) behaves exactly as before. Session-request 404s now map to the existing NotFoundError sentinel so callers aren't string-matching, and the re-create runs under the same mutex and session-identity double-check that refresh already uses, wired through the three places that do the refresh-and-retry dance (GetMessage, DeleteMessage, AcquireJobs).

lastMessageID is owned by the caller and sent on every request, so the delivery position survives the swap. Worst case, a re-created session behaves like what a process restart already does today, minus losing the cursor.

Tests cover the recovery path (including that the caller's lastMessageId still goes out on the retried request against the new session), that a 500 on refresh does not trigger a re-create, and that a failed re-create surfaces both errors with errors.Is(err, NotFoundError) intact.

For what it's worth, we've been running this patch in production (backported onto v0.4.0): the affected org's listeners went from dying every 50 minutes to logging a single re-create line and moving on. Happy to reshape it if you'd rather see the recovery live in the listener instead of the client, or handled differently.

When the Actions service no longer knows a session, the refresh PATCH
returns 404 and the client surfaces it as fatal from GetMessage/
DeleteMessage/AcquireJobs. If the condition is persistent (broker-side
session eviction), the caller dies on every token refresh even though a
freshly created session works fine — in ARC's listener that means a
crash loop every ~50 minutes that also resets the in-memory message
cursor.

Map session-request 404s to the existing NotFoundError sentinel, and on
a refresh 404 create a new session in place (same mutex and session-
identity double-check as refresh) and retry once. All other refresh
failures surface exactly as before. The caller's lastMessageId is sent
per request, so delivery position survives re-creation.
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