Skip to content

fix(xray): make add_user idempotent instead of aborting on EmailExistsError#43

Open
lArtiquel wants to merge 1 commit into
marzneshin:masterfrom
lArtiquel:fix/idempotent-add-user
Open

fix(xray): make add_user idempotent instead of aborting on EmailExistsError#43
lArtiquel wants to merge 1 commit into
marzneshin:masterfrom
lArtiquel:fix/idempotent-add-user

Conversation

@lArtiquel

Copy link
Copy Markdown

Problem

XrayBackend.add_user re-raises EmailExistsError:

except (EmailExistsError, TagNotFoundError):
    raise

Nothing up the call chain (_add_user / _update_user / SyncUsers / RepopulateUsers in service.py) catches it. When the panel opens overlapping streams — a RepopulateUsers running alongside a SyncUsers, or a reconnect that re-pushes the user set — a user can be added to an inbound that already contains it. The duplicate add raises EmailExistsError, which propagates out of the stream handler and aborts the whole iteration, so every user after the offending one is skipped. In production this shows up as a node silently serving only a subset of its users after a reconnect, until the next clean full resync.

Fix

Adding a user who already exists in the inbound is semantically a no-op, so log at debug and continue instead of raising. TagNotFoundError and OSError handling are unchanged.

Testing

  • python -m py_compile clean; no new dependencies.
  • I've run this change (together with its companion PR that serializes RepopulateUsers) in production across several marznode instances since 2026-05. The periodic post-reconnect user-drops and EmailExistsError log spam disappeared and have not recurred.

Note

Best reviewed alongside the companion PR that guards RepopulateUsers with a lock — that removes the interleaving which produces most of these duplicate adds, while this PR absorbs the residual cases (reconnects, SyncUsers/RepopulateUsers overlap). Each stands on its own, though.

@khodedawsh @erfjab — would appreciate a review when you get a chance. Happy to move the handling one level up into service.py instead if you'd prefer it there.

…sError

add_user re-raises EmailExistsError, but nothing up the call chain
(_add_user / _update_user / SyncUsers / RepopulateUsers) catches it. When
the panel opens overlapping streams — e.g. a RepopulateUsers running
alongside a SyncUsers, or a reconnect that re-pushes users — a user can be
added to an inbound twice. The second add raises EmailExistsError, which
propagates out of the stream handler and aborts the whole iteration, so the
users after it are never synced. In production this shows up as nodes
silently dropping a subset of users after a reconnect until the next full
resync.

Adding a user that already exists is semantically a no-op, so log at debug
and continue rather than raising. TagNotFoundError and OSError handling are
unchanged.
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

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