diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-29 16:46:50 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-29 16:46:50 +0300 |
| commit | f6e69200944a1fbc0f1fc0ecd8beb2f8659161c1 (patch) | |
| tree | e4b52e6449a476b683ada33397d1c8ecfded79f7 | |
| parent | 1125cdcda9590c81308ca82e6b0eb9fcb10e7a6e (diff) | |
refactor(sync): move backup session state to Syncer instance (qq)
| -rw-r--r-- | internal/sync/backup_test.go | 63 | ||||
| -rw-r--r-- | internal/sync/sync.go | 15 |
2 files changed, 60 insertions, 18 deletions
diff --git a/internal/sync/backup_test.go b/internal/sync/backup_test.go index 9bdbff3..fd15d04 100644 --- a/internal/sync/backup_test.go +++ b/internal/sync/backup_test.go @@ -2,15 +2,15 @@ package sync import ( "errors" + "fmt" + stdsync "sync" + "sync/atomic" "testing" "codeberg.org/snonux/gitsyncer/internal/config" ) func TestHandlePushError_DisablesBackupForSession(t *testing.T) { - resetBackupSessionState() - t.Cleanup(resetBackupSessionState) - syncer := &Syncer{} syncer.SetBackupEnabled(true) @@ -24,9 +24,6 @@ func TestHandlePushError_DisablesBackupForSession(t *testing.T) { } func TestHandlePushError_PropagatesPrimaryRemoteFailure(t *testing.T) { - resetBackupSessionState() - t.Cleanup(resetBackupSessionState) - syncer := &Syncer{} syncer.SetBackupEnabled(true) @@ -37,6 +34,60 @@ func TestHandlePushError_PropagatesPrimaryRemoteFailure(t *testing.T) { } } +func TestHandlePushError_BackupDisableIsIsolatedPerSyncer(t *testing.T) { + backupOrg := &config.Organization{BackupLocation: true} + + syncerA := &Syncer{} + syncerA.SetBackupEnabled(true) + + syncerB := &Syncer{} + syncerB.SetBackupEnabled(true) + + err := syncerA.handlePushError("backup-a", backupOrg, errors.New("dial tcp: connection refused")) + if err != nil { + t.Fatalf("expected backup push failure to be downgraded, got %v", err) + } + + if syncerA.backupActive() { + t.Fatal("expected syncerA backup sync to be disabled for the remainder of the session") + } + if !syncerB.backupActive() { + t.Fatal("expected syncerB backup session to remain active") + } +} + +func TestBackupSessionState_DisableIsThreadSafe(t *testing.T) { + var session backupSessionState + var firstDisableCount atomic.Int32 + + const workers = 32 + var wg stdsync.WaitGroup + wg.Add(workers) + + for i := 0; i < workers; i++ { + go func(i int) { + defer wg.Done() + if session.disable(fmt.Sprintf("reason-%d", i)) { + firstDisableCount.Add(1) + } + }(i) + } + + wg.Wait() + + if got := firstDisableCount.Load(); got != 1 { + t.Fatalf("expected exactly one successful disable transition, got %d", got) + } + + disabled, reason := session.status() + if !disabled { + t.Fatal("expected backup session to be disabled") + } + if reason == "" { + t.Fatal("expected disable reason to be recorded") + } +} + func TestParseSSHLocation_SupportsSSHURLWithPort(t *testing.T) { t.Parallel() diff --git a/internal/sync/sync.go b/internal/sync/sync.go index e0a8215..2f05110 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -17,8 +17,6 @@ type backupSessionState struct { reason string } -var currentBackupSession backupSessionState - // Syncer handles repository synchronization between organizations type Syncer struct { config *config.Config @@ -27,6 +25,7 @@ type Syncer struct { abandonedReports map[string]*AbandonedBranchReport // Collects reports across repos branchFilter *BranchFilter // Filter for excluding branches backupEnabled bool // Whether to sync to backup locations + backupSession backupSessionState } // CLAUDE: Is there a reason, we return a pointer to Syncer? @@ -59,7 +58,7 @@ func (s *Syncer) backupActive() bool { return false } - disabled, _ := currentBackupSession.status() + disabled, _ := s.backupSession.status() return !disabled } @@ -69,7 +68,7 @@ func (s *Syncer) disableBackupForSession(remoteName string, err error) { } reason := fmt.Sprintf("%s: %v", remoteName, err) - if currentBackupSession.disable(reason) { + if s.backupSession.disable(reason) { fmt.Printf("Warning: Backup sync to %s failed: %v\n", remoteName, err) fmt.Println("Warning: Disabling backup sync for the remainder of this session.") } @@ -95,14 +94,6 @@ func (b *backupSessionState) status() (bool, string) { return b.disabled, b.reason } -func resetBackupSessionState() { - currentBackupSession.mu.Lock() - defer currentBackupSession.mu.Unlock() - - currentBackupSession.disabled = false - currentBackupSession.reason = "" -} - // SyncRepository synchronizes a repository across all configured organizations func (s *Syncer) SyncRepository(repoName string) error { s.repoName = repoName |
