Files
dex/pkg
zeekayandGitHub cbba335268 lx: fix data race in Reconciler settle/Submit paths (#6)
settle() held r.mu across user callbacks (onFailed, onSettled), which
meant the callback's writes to caller-captured variables raced with the
caller's later reads despite a sleep. SubmitDEXTrade/SubmitCEXTrade
also returned the live merged trade pointer, and the background settle
goroutine mutated Status/SettledAt/FailureReason on that same pointer
under the lock — racing with any caller that read fields after return
(TestReconcilerMatchCEXFirst).

Two minimal, orthogonal fixes:

1. settle() captures the callback funcs + the failure reason under
   r.mu, releases the lock, then invokes the user callbacks outside
   the lock. Foreign code never runs while we hold the Reconciler's
   mutex, which is also good production practice (avoids re-entrant
   lock-ordering hazards if a callback calls back into the Reconciler).

2. SubmitDEXTrade/SubmitCEXTrade return a value-copy snapshot of the
   merged trade when settlement begins. The live (mutable) trade is
   owned by the background settle goroutine and stays inside the
   Reconciler — callers must use GetStatus for current state. The
   pending (unmatched) return path is unchanged for backward compat.

Tests use sync/atomic primitives for the captured callback state so
the test-side read is happens-before-ordered with the callback write.

Verified with:
  go test -race -count=50 -run TestReconcilerBridgeSettlementFails ./pkg/lx/
  go test -race -count=20 -run TestReconciler ./pkg/lx/
  go test -race -count=1 ./pkg/lx/...
2026-05-17 10:24:58 -07:00
..