mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
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/...