Skip to content

Fix/persist worker down - #63

Merged
hi-liang merged 2 commits into
mainfrom
fix/persist-worker-down
Oct 27, 2026
Merged

hi-liang merged 2 commits into
mainfrom
fix/persist-worker-down

Conversation

@hi-liang

Copy link
Copy Markdown
Member

Fixes #51.

worker-down was held only in memory. On a corkd restart every worker got a fresh connection with no down marker, so each downed worker went back into placement as soon as its reconcile passed. The worst case is autoscaler scale-in: it downs a worker before terminating it, so a restart in between puts player instances on a box that is about to disappear. A restart also put boxes back into placement in the middle of a drain.

What changes

  • workers.downsince: the time worker-down took the worker down (unix nanos), NULL when it is not down. Older databases get the column on startup, and every worker in them reads as up.
  • worker-down marks the worker down in memory first, then writes downsince. Both happen under the same lock, so a worker-add cannot land between them and leave the database saying down while the worker is back in placement. If the write fails, the worker stays down for the life of this corkd. The PATCH answers 500 with an error that says the worker is down but not persisted. Running worker-down again retries the write with the same timestamp.
  • worker-add clears downsince along with the in-memory down.
  • On startup, initWorkers restores the worker as down, with its original time and reason, and logs it. The restored worker is neither reconciled nor probed until a worker-add, the same as a worker downed while corkd is running. Its stops still take the DB-only path.
  • Only down is stored. unresponsive is an observation, and the probe works it out again after a restart. If it were stored, nothing would ever lift it.

Rollback

A corkd from before this change leaves the downsince column in place but ignores it. A worker-add run while the old version is running is therefore lost: the next start of this version brings that worker back down. corkd logs it at startup as down since its original time, and worker-add lifts it again.

Left as is

  • If worker-add fails after its upsert (for example, unreadable TLS material), downsince is already cleared while the in-memory worker stays down. The public address already behaves this way, and the operator was asking for the worker back.
  • On SQLITE_BUSY the worker endpoints answer 500, not 503. This applies to all three of them, so changing it is separate work.

Testing

  • New tests:
    • A downed worker survives a restart with its time and reason; an ejected worker does not come back down.
    • worker-add clears the stored down.
    • A worker-down whose write fails still leaves the worker down, and its error says so.
    • An old workers table gets the column on upgrade.
  • Each new test fails when the behaviour it covers is removed: restore turned off, worker-add not clearing, write before mark.
  • Three existing worker-down tests now get a database, since worker-down writes to one.
  • go test ./... passes, go test ./cork/ -race passes, and go vet and gofmt are clean.

worker-down lived only in memory. initWorkers built every worker a fresh
conn, and a fresh conn carries no assertion, so a restart returned each
downed worker to placement once its reconcile passed. That bites hardest on
autoscaler scale-in, which downs a worker before terminating it: a restart in
between places player instances on a box about to disappear, with nobody
watching. It also reopens a box mid-drain.

workers gains downsince, the time worker-down took the worker down, NULL
otherwise. SetWorkerDown writes it under the same read lock as the
assertion, so a worker-add cannot land between the two; it marks first, so a
failed write still leaves the worker down for the life of this corkd and the
PATCH reports the error. worker-add clears it. initWorkers restores the
worker down, with its original since and reason, and logs it; its conn is
neither reconciled nor probed until a worker-add replaces it, as for a
worker downed in a running corkd.

Only down is stored. unresponsive is an observation and re-derives itself
from the probe after a restart; stored, nothing would ever lift it.
Existing databases gain the column in place, every worker reading as up.
…r down

The PATCH answers with the error alone, and a bare "database is locked"
reads as a worker-down that did nothing, when the worker is down and only
the write is missing. The error now says so; worker-down again retries the
write, writing the same since.

The downsince migration moves beside the workers.public one. Between the
worker-column loop and the port index it broke that index's comment, which
explains itself by "the migration above".

Comments that still had pollers marking workers down, a restart starting
every worker afresh, and only restored conns parking in runWorker now say
what the code does. A test pins the mark-before-write order.
@hi-liang
hi-liang merged commit 855ce2a into main Oct 27, 2026
4 checks passed
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.

worker-down is not persisted, so a corkd restart returns a downed worker to placement

1 participant