Skip to content

fix(warehouse/slave): guard outputFileWritersMap and tableEventCountMap reads in uploadLoadFiles (#7002) - #7256

Open
kotwal-itpro wants to merge 1 commit into
rudderlabs:masterfrom
kotwal-itpro:fix/warehouse-slave-worker-job-data-race-7002
Open

fix(warehouse/slave): guard outputFileWritersMap and tableEventCountMap reads in uploadLoadFiles (#7002)#7256
kotwal-itpro wants to merge 1 commit into
rudderlabs:masterfrom
kotwal-itpro:fix/warehouse-slave-worker-job-data-race-7002

Conversation

@kotwal-itpro

Copy link
Copy Markdown

Fixes #7002.

Problem

In warehouse/slave/worker_job.go, uploadLoadFiles reads jr.outputFileWritersMap and jr.tableEventCountMap from both the main goroutine and a spawned upload goroutine without holding their respective mutexes (outputFileWritersMapMu / tableEventCountMapMu). The Go race detector flags this as unsynchronised concurrent access — the exact behaviour described in #7002.

Sites affected on current master:

  • warehouse/slave/worker_job.go:415len(jr.outputFileWritersMap) in the main goroutine
  • warehouse/slave/worker_job.go:423for tableName, uploadFile := range jr.outputFileWritersMap in the spawned goroutine
  • warehouse/slave/worker_job.go:450jr.tableEventCountMap[tableName] in the errgroup worker
  • warehouse/slave/worker_job.go:473, 483, 484 — three more len(jr.outputFileWritersMap) reads in the main goroutine

In practice both maps are fully populated by handlePendingStagingFile before this method runs, but the ordering is implicit — there is no formal synchronisation guarantee, and layering more concurrent work on top would trip the race for real.

Fix

Rather than sprinkling RLock / RUnlock at each read site (which still leaves the errgroup workers touching the shared maps), snapshot both maps under their read locks into local slices/maps before starting the errgroup workers. The workers then run entirely over local data.

  • pending []pendingUpload — snapshot of outputFileWritersMap under outputFileWritersMapMu.RLock()
  • tableEventCounts map[string]int — snapshot of tableEventCountMap under tableEventCountMapMu.RLock()

The workers no longer read the shared maps, which eliminates both the reported race and any future regression as more concurrent work is layered on the same maps.

Semantics preserved

  • Same tables uploaded (iteration order of the snapshot mirrors the original range).
  • Same TotalRows values reported (via tableEventCounts[tableName]).
  • Same output-length invariant checked (len(output) != len(pending)).
  • Same channel-buffer size (len(pending)).

Verified locally

  • go build ./warehouse/slave/... — clean.
  • go vet ./warehouse/slave/... — clean.
  • go test -race -run "TestSlaveJobPayload|TestSlaveJob/writer_and_reader|TestSlaveJob/discards" ./warehouse/slave/ — passes (the docker-dependent TestSlaveJob/upload_load_files and TestSlaveJob/download_staging_file were skipped locally due to a docker socket path mismatch on the reviewer's machine; CI will exercise them).

…ap reads in uploadLoadFiles (rudderlabs#7002)

`uploadLoadFiles` read `jr.outputFileWritersMap` and `jr.tableEventCountMap`
in the main goroutine and in a spawned upload goroutine without holding
`outputFileWritersMapMu` / `tableEventCountMapMu`. The Go race detector
flags these as unsynchronised concurrent reads.

In practice `handlePendingStagingFile` populates both maps before this
function is called, but the ordering is implicit and there is no formal
synchronisation guarantee. Snapshot each map under its read lock into a
local slice and a local map before starting the errgroup workers, then let
the workers operate on those snapshots. The workers no longer touch the
shared maps, which eliminates both the reported race and any future
regression as more concurrent work is layered on top.

Semantics are preserved: the same tables are uploaded, the same
TotalRows values are reported, and the same output-length invariant is
checked. Existing tests still pass and `go vet` is clean.
@gitcommitshow
gitcommitshow requested a lite review from Copilot August 17, 2026 09:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@kotwal-itpro

Copy link
Copy Markdown
Author

Hi maintainers — checking in on this one too. It closes #7002 (data race in the warehouse-slave upload path). The fix snapshots the shared maps under the existing RWMutexes before the errgroup workers spawn, so no concurrent worker ever touches shared state directly.

Happy to adjust the approach if you'd rather see the synchronization done differently. Thanks!

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.

bug: unprotected concurrent map reads in warehouse slave worker_job.go (data race)

2 participants