Skip to content

fix: data race in loginInboundConn during Forge login relay#690

Merged
robinbraemer merged 1 commit into
masterfrom
fix/login-inbound-race
May 27, 2026
Merged

fix: data race in loginInboundConn during Forge login relay#690
robinbraemer merged 1 commit into
masterfrom
fix/login-inbound-race

Conversation

@robinbraemer
Copy link
Copy Markdown
Member

Bug

loginInboundConn's mutable state is accessed from two goroutines without synchronization:

  • SendLoginPluginMessage runs on event-handler / Forge-relay goroutines (writes outstandingResponses, pushes loginMessagesToSend).
  • handleLoginPluginResponse runs on the client read loop (reads/deletes outstandingResponses, reads len() + onAllMessagesHandled).

This is a real data race on the outstandingResponses map (and the other fields), reproducible today under go test -race ./pkg/edition/java/proxy/ via TestModernForgeIntegration_FullJoinFlow (write login_inbound.go:97 ↔ read :112/:116/:119/:124). It was introduced with the modern-Forge login relay (#680/#688), which drives these methods from separate goroutines.

Fix

Guard the four shared fields (outstandingResponses, loginMessagesToSend, isLoginEventFired, onAllMessagesHandled) with a sync.Mutex.

Key detail: external callbacks and connection I/O are invoked without the lock held — the message consumer and onAllMessagesHandled may re-enter SendLoginPluginMessage (which takes the same lock), so holding it across those calls would deadlock. The handleLoginPluginResponse flow takes the lock to look up + delete the consumer, releases it to run the consumer, then re-takes it to check whether all messages are handled. This preserves the original ordering (the all-handled callback fires after the consumer runs, so a consumer that queues follow-up messages correctly defers completion).

No behavior change beyond synchronization. All four guarded fields are confined to login_inbound.go (verified), so the lock fully covers them.

Test (TDD)

TestLoginInboundConn_ConcurrentSendAndResponse races SendLoginPluginMessage and handleLoginPluginResponse on one connection. It reports a data race under -race before the fix and passes after.

Verification

  • go test -race ./pkg/edition/java/proxy/ — clean (the new test, the previously-failing TestModernForgeIntegration_FullJoinFlow, and the full package)
  • go build ./... — clean
  • go test ./... — all packages pass (0 failures)
  • gofmt — clean

Independent of #689 (both branch from master; this touches only login_inbound.go).

loginInboundConn's mutable state (outstandingResponses map,
loginMessagesToSend deque, isLoginEventFired, onAllMessagesHandled) is
accessed from two goroutines: SendLoginPluginMessage runs on event-handler
/ relay goroutines while handleLoginPluginResponse runs on the client read
loop. The fields had no synchronization, producing a data race on the
outstandingResponses map (write at SendLoginPluginMessage vs read/delete +
len() in handleLoginPluginResponse), reproducible under `go test -race`
via TestModernForgeIntegration_FullJoinFlow.

Guard the shared fields with a mutex. External callbacks (the message
consumer and onAllMessagesHandled) and connection I/O (WritePacket /
BufferPacket / Flush) are invoked without the lock held to avoid
re-entrant deadlocks, since a consumer may call back into
SendLoginPluginMessage. Behavior is otherwise unchanged: the all-handled
callback still fires after the consumer runs when no responses remain.

Adds TestLoginInboundConn_ConcurrentSendAndResponse, a focused regression
test that races the two methods and fails under -race before the fix.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying gate-minekube with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6b2a127
Status: ✅  Deploy successful!
Preview URL: https://95182faa.gate-minekube.pages.dev
Branch Preview URL: https://fix-login-inbound-race.gate-minekube.pages.dev

View logs

@robinbraemer robinbraemer merged commit 1b03ac8 into master May 27, 2026
13 checks passed
@robinbraemer robinbraemer deleted the fix/login-inbound-race branch May 27, 2026 21:30
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