Skip to content

fix(server): publish MultiService user table atomically - #6

Open
CloudPassenger wants to merge 1 commit into
SagerNet:mainfrom
CloudPassenger:fix/multiservice-users-race
Open

fix(server): publish MultiService user table atomically#6
CloudPassenger wants to merge 1 commit into
SagerNet:mainfrom
CloudPassenger:fix/multiservice-users-race

Conversation

@CloudPassenger

Copy link
Copy Markdown

MultiService[U] keeps the user table in a plain map that UpdateUsers replaces while the authentication path reads it. Since every request reads the table (including on session-reuse connections), concurrent UpdateUsers calls race with map reads and can crash the process with a fatal concurrent map read/write panic.

Upstream only calls UpdateUsers once at construction, which is why this has stayed latent, but it is an exported API intended for runtime user updates.

The fix replaces the field with atomic.Pointer[map[string]U]: UpdateUsers still builds and validates a full replacement table before publishing it, and readers load one immutable snapshot per lookup without locking. Validation errors and their order are unchanged; failed validation never touches the published state.

Both changes come with an in-package -race regression test that drives UpdateUsers against authenticate concurrently; it fails before the patch and passes after.

This bug was tracked down under high load with the help of an AI agent (ox-alpha), which also assisted in implementing the fix.

MultiService[U] stored the user table as a plain map that UpdateUsers
replaced while the authentication path read it concurrently. Every
request on both the fresh-connection and session-reuse paths reads
the table, so concurrent UpdateUsers calls raced with map reads and
could crash the process with a concurrent map read/write panic.

Replace the field with atomic.Pointer[map[string]U]. UpdateUsers keeps
building a fully validated table before publishing it, so failed
validation never modifies the published state; readers load one
immutable snapshot per lookup without locking.

Validation errors and their order are unchanged, authenticate still
returns snell.ErrBadUserKey on miss (also before the first successful
UpdateUsers), and the snell.Service interface assertions are intact.

Add in-package regression tests driving UpdateUsers against
authenticate concurrently; they fail under -race before this change.
@CloudPassenger
CloudPassenger force-pushed the fix/multiservice-users-race branch from 1a8948b to c8e4502 Compare August 24, 2026 12:29
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