Skip to content

fix(task): stop locking Base.SendData so concurrent workers run in parallel - #132

Merged
Mahesh Kamble (ma-gk) merged 4 commits into
mainfrom
fix/file-reader-worker-lock
Oct 22, 2026
Merged

Mahesh Kamble (ma-gk) merged 4 commits into
mainfrom
fix/file-reader-worker-lock

Conversation

@ma-gk

@ma-gk Mahesh Kamble (ma-gk) commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Base.SendData held the task mutex while it marshalled the record, evaluated every context query, and blocked on the output send. So concurrent workers of one task waited on each other, even after their input was ready.
  • Only the record id needed guarding. recordIndex is now an atomic.Int64, and the mutex is gone from Base. Nothing else used it.
  • This fixes every task with task_concurrency > 1, not just the file reader. The earlier file-only workaround (per-worker task copies, a separate id counter and a SendData override) is gone, so file.go matches main.

Benchmark

Local Apple M3 (darwin/arm64). Five runs after one warmup. Each time includes a similar fixed overhead from the timing script, so compare the columns rather than reading them as absolute times.

Eight context fields, concurrency 10

64 local JSON files (60 items each), eight .data | fromjson | .items | map(...) | length context fields, task_concurrency: 10.

Run main this branch
1 66.1 ms 46.2 ms
2 64.7 ms 44.7 ms
3 64.8 ms 45.6 ms
4 66.4 ms 44.6 ms
5 66.0 ms 43.9 ms
Median 65.0 ms 44.7 ms
Files written 64 64
Exit code 0 0

Sample pipeline, no context fields

test/pipelines/file_concurrency_test.yaml (100 tiny .txt files, no context block) holds the lock for only microseconds per record, so there is no measurable change here. It still reads and writes 100 files and exits 0.

Test plan

  • go build ./...
  • go test -race ./...
  • Race check: 16 goroutines running Execute on one shared jq.Query (context queries can now run concurrently)
  • go vet ./internal/pkg/pipeline/... shows the same three copylocks warnings as main. They come from the raw struct copies during unmarshalling.
  • Context benchmark above, against main
  • test/pipelines/file_concurrency_test.yaml: 100 files written, exit 0

🤖 Generated with Claude Code

File read workers shared one task mutex, so context evaluation for every record ran one at a time. Each worker now owns a task copy and a disjoint set of paths.
@ma-gk
Mahesh Kamble (ma-gk) requested a review from a team as a code owner October 15, 2026 19:14
Comment thread internal/pkg/pipeline/task/file/file.go Resolved
Comment thread internal/pkg/pipeline/task/file/file.go Resolved
Comment thread internal/pkg/pipeline/task/file/file.go Resolved

@argus-pattern argus-pattern Bot 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.

Reviewed the change to concurrent file reads (one task copy per worker, paths split round-robin) and checked it against task.Base.SendData/SendRecord and pipeline.runTaskConcurrently. I left three inline comments: static partitioning can leave workers idle when file sizes vary, record IDs now repeat across workers, and the hand-copied fields in readWorker can fall out of sync with the struct.

Testing: the PR adds no unit tests. A small table test for partitionPaths (workers == len(paths), workers < len(paths), a single path) and a race-enabled test showing every path is emitted exactly once with task_concurrency > 1 would protect this path. Note that pipeline.runTaskConcurrently still forces source file tasks to concurrency 1, so this in-task fan-out is the only concurrency the reader gets.

Round-robin lists stall when file sizes differ, and each worker copy
restarted record ids at 1. Workers now take the next path from a
shared queue, and one counter keeps ids unique without a shared lock.
SendData held the task mutex while it marshalled the record, evaluated
every context query and blocked on the output send, so concurrent
workers of any task waited on each other. Only the record id needed
guarding; it is now an atomic counter and the mutex is gone.

This replaces the file-reader-only workaround (per-worker task copies,
a separate id counter and a SendData override), so file.go is back to
main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ma-gk Mahesh Kamble (ma-gk) changed the title fix(file): give each concurrent reader its own task lock fix(task): stop locking Base.SendData so concurrent workers run in parallel Oct 22, 2026
@ma-gk
Mahesh Kamble (ma-gk) merged commit 2cf38c6 into main Oct 22, 2026
7 checks passed
@ma-gk
Mahesh Kamble (ma-gk) deleted the fix/file-reader-worker-lock branch October 22, 2026 07:05
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.

3 participants