From 5b99a900d5923472e37f81398019f0d4e94c05e2 Mon Sep 17 00:00:00 2001 From: Marcin Czenko Date: Mon, 27 Oct 2025 20:46:39 +0100 Subject: [PATCH] pre-populate archiveDownloadCancel to correctly capture downloadComplete and avoid potential race condition --- communities/codex_archive_downloader.go | 31 +++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/communities/codex_archive_downloader.go b/communities/codex_archive_downloader.go index f9ae83b..8043048 100644 --- a/communities/codex_archive_downloader.go +++ b/communities/codex_archive_downloader.go @@ -147,6 +147,10 @@ func (d *CodexArchiveDownloader) downloadAllArchives() { var archivesList []archiveInfo for hash, metadata := range d.index.Archives { + // Skip archives we already have + if slices.Contains(d.existingArchiveIDs, hash) { + continue + } archivesList = append(archivesList, archiveInfo{ hash: hash, from: metadata.Metadata.From, @@ -166,6 +170,17 @@ func (d *CodexArchiveDownloader) downloadAllArchives() { return 0 // equal timestamps }) + // Pre-populate archiveDownloadCancel with all archives that need downloading + // This ensures len(archiveDownloadCancel) correctly represents total pending work + // and prevents race condition where fast-completing goroutines set downloadComplete=true + // before all archives are added to the map + d.mu.Lock() + for _, archive := range archivesList { + d.archiveDownloadCancel[archive.hash] = make(chan struct{}) + d.archiveDownloadProgress[archive.hash] = 0 + } + d.mu.Unlock() + // Monitor for cancellation in a separate goroutine go func() { ticker := time.NewTicker(100 * time.Millisecond) @@ -202,18 +217,10 @@ func (d *CodexArchiveDownloader) downloadAllArchives() { // Download each missing archive for _, archive := range archivesList { - // Check if we already have this archive - hasArchive := slices.Contains(d.existingArchiveIDs, archive.hash) - if hasArchive { - continue - } - - archiveCancelChan := make(chan struct{}) - - d.mu.Lock() - d.archiveDownloadProgress[archive.hash] = 0 - d.archiveDownloadCancel[archive.hash] = archiveCancelChan - d.mu.Unlock() + // Get the pre-created cancel channel for this archive + d.mu.RLock() + archiveCancelChan := d.archiveDownloadCancel[archive.hash] + d.mu.RUnlock() // Call callback before starting if d.onStartingArchiveDownload != nil {