Skip to content

Reduce lock contention#9

Merged
mgomes merged 6 commits intomasterfrom
mgomes/max-dl-speed
Dec 4, 2025
Merged

Reduce lock contention#9
mgomes merged 6 commits intomasterfrom
mgomes/max-dl-speed

Conversation

@mgomes
Copy link
Copy Markdown
Owner

@mgomes mgomes commented Dec 4, 2025

No description provided.

Copilot AI review requested due to automatic review settings December 4, 2025 03:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes download performance by eliminating lock contention in the multi-part download progress tracking system. The key improvement is replacing mutex-protected progress updates with lock-free atomic operations, which significantly reduces contention when multiple download goroutines are running concurrently.

  • Introduces cache-line padded atomic counters to prevent false sharing between CPU cores
  • Separates progress bar UI updates from download goroutines using a dedicated ticker
  • Optimizes I/O with 1MB buffers and connection pool tuning
  • Removes pull_request trigger from release workflow to prevent unnecessary runs

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
main.go Implements lock-free progress tracking with atomic counters, adds UI update ticker goroutine, optimizes buffer usage with io.CopyBuffer, and disables HTTP/2 for better connection distribution
.github/workflows/release.yml Removes pull_request trigger and adds redundant tag check to ensure releases only run on tag pushes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread main.go
finalTotal += atomic.LoadUint64(&dl.partDownloaded[i].val)
}
if diff := finalTotal - lastTotal; diff > 0 {
bar.Add64(int64(diff))
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The final progress bar update on context cancellation doesn't update lastTotal, which could lead to inconsistent state. Consider updating lastTotal = finalTotal after calling bar.Add64() to maintain consistency with the ticker case.

Suggested change
bar.Add64(int64(diff))
bar.Add64(int64(diff))
lastTotal = finalTotal

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Doesn't it return immediately after and lastTotal being a locally scoped var become out of scope?

Comment thread main.go
Comment on lines +991 to +992
// Use 1MB buffer to reduce syscall overhead
buf := make([]byte, 1024*1024)
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

A 1MB buffer is allocated per goroutine (one per download part). With the default 8 parts, this allocates 8MB. Consider using a sync.Pool to reuse buffers across parts, which would reduce memory allocation overhead and GC pressure during downloads.

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/release.yml Outdated
Comment thread main.go
Comment on lines +90 to +95
// atomicCounter is padded to 64 bytes to prevent false sharing between CPU cores.
// Without padding, adjacent counters share a cache line, causing cache thrashing.
type atomicCounter struct {
val uint64
_ [56]byte // pad to 64 bytes (cache line size)
}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The new atomicCounter type and lock-free progress tracking implementation lack test coverage. Consider adding tests that verify: 1) cache line padding is correct (64 bytes), 2) concurrent access to atomic counters doesn't cause data races, and 3) progress bar updates correctly reflect concurrent writes from multiple goroutines.

Copilot uses AI. Check for mistakes.
Comment thread main.go
Comment on lines +809 to +838
// UI update ticker - updates progress bar without mutex contention in download loop
go func() {
uiTicker := time.NewTicker(100 * time.Millisecond)
defer uiTicker.Stop()

var lastTotal uint64
for {
select {
case <-ctx.Done():
// Final update to ensure bar reaches 100%
var finalTotal uint64
for i := range dl.partDownloaded {
finalTotal += atomic.LoadUint64(&dl.partDownloaded[i].val)
}
if diff := finalTotal - lastTotal; diff > 0 {
bar.Add64(int64(diff))
}
return
case <-uiTicker.C:
var currentTotal uint64
for i := range dl.partDownloaded {
currentTotal += atomic.LoadUint64(&dl.partDownloaded[i].val)
}
if diff := currentTotal - lastTotal; diff > 0 {
bar.Add64(int64(diff))
lastTotal = currentTotal
}
}
}
}()
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The UI update ticker goroutine lacks test coverage. Consider adding tests to verify: 1) progress bar updates correctly aggregate atomic counter values, 2) the final update on context cancellation completes properly, and 3) the ticker handles edge cases like zero progress or rapid completion.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@mgomes mgomes merged commit 87173ed into master Dec 4, 2025
1 check passed
@mgomes mgomes deleted the mgomes/max-dl-speed branch December 4, 2025 03:21
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.

2 participants