Skip to content

fix(file): eliminate duplicate reads under task_concurrency - #117

Open
Mahesh Kamble (ma-gk) wants to merge 3 commits into
mainfrom
fix/file-task-concurrency
Open

fix(file): eliminate duplicate reads under task_concurrency#117
Mahesh Kamble (ma-gk) wants to merge 3 commits into
mainfrom
fix/file-task-concurrency

Conversation

@ma-gk

Copy link
Copy Markdown
Contributor

Summary

  • Fixes a correctness bug in the file task's read mode: when task_concurrency > 1, every worker independently expanded the glob and read every matched file, so each file was emitted once per worker instead of once total.
  • The glob is now expanded exactly once (sync.Once), and workers claim disjoint files off the shared matched-path list via an atomic index (readIdx), so N workers split the file list N ways instead of each reading every file.
  • writeFile now builds the per-worker file struct explicitly (only the fields the writer needs) instead of struct-copying f, since f now carries sync.Once/atomic read-concurrency state (and the embedded task.Base mutex) that must never be struct-copied.
  • Updated the task_concurrency doc row in the task README to describe the read/write semantics precisely.

Test plan

Added test/pipelines/file_concurrency_test.yaml, which exercises both directions in one pipeline:

  • Read: task_concurrency: 4 against a glob matching 2 fixture files (names.txt, birds.txt) — before the fix, 4 workers × 2 files = 8 read records; after the fix, exactly 2.
  • Write: task_concurrency: 8 writing one file per line after a split — output file count must exactly equal input line count (no drops, no duplicates).

Ran locally:

rm -rf /tmp/caterpillar/file_concurrency_test
go run cmd/caterpillar/caterpillar.go -conf test/pipelines/file_concurrency_test.yaml
ls /tmp/caterpillar/file_concurrency_test | wc -l   # 2123
cat test/pipelines/names.txt test/pipelines/birds.txt | wc -l   # 2121 newlines + 2 (no trailing newline in either fixture) = 2123

Output count matches expected exactly.

🤖 Generated with Claude Code

Previously, when task_concurrency > 1 in read mode, each worker
independently expanded the glob and read every matched file, so every
file was emitted once per worker (Nx duplication).

The glob is now expanded exactly once (guarded by sync.Once), and
workers claim disjoint files off the shared matched-path list via an
atomic index, so each matched file is read exactly once regardless of
worker count.

Also fixes writeFile to build the per-worker file struct explicitly
instead of struct-copying f, since f now carries a sync.Once/atomic
read-concurrency state (and the embedded task.Base mutex) that must
never be copied.

Adds test/pipelines/file_concurrency_test.yaml, which exercises both
directions: 4 concurrent read workers against a 2-file glob (expect
exactly 2 read records, not 8), and 8 concurrent write workers writing
one file per input line (expect the output file count to exactly match
the input line count).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ma-gk
Mahesh Kamble (ma-gk) requested a review from a team as a code owner September 8, 2026 08:33
The write task's path had drifted to a repo-relative test/{{ macro
"uuid" }}.txt, contradicting the file's own header comment and
dumping generated files straight into the git checkout instead of
/tmp. Restored to /tmp/caterpillar/file_concurrency_test/{{ macro
"uuid" }}.txt as documented.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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