Skip to content

Conversation

@ggreer
Copy link
Contributor

@ggreer ggreer commented Dec 31, 2025

Related to #604

Summary by CodeRabbit

  • Tests

    • Added test coverage verifying cache invalidation and resource-listing behavior across consecutive synchronizations.
  • Refactor

    • Replaced one-time initialization with a mutex-protected cache for synchronization state and ensured cache is invalidated on sync lifecycle events to maintain correct concurrent behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Walkthrough

Replaces a sync.Once-based lazy initialization with a mutex-guarded cached view-sync state, adds explicit cache invalidation on StartNewSync and EndSync, and adjusts cache read/write flows to return cached values early and recompute when invalidated.

Changes

Cohort / File(s) Summary
Cache synchronization refactor
pkg/dotc1z/c1file.go
Removes cachedViewSyncOnce; adds cachedViewSyncMu mutex, explicit validity tracking and guarded access to cachedViewSyncRun/cachedViewSyncErr.
Cache lifecycle & invalidation
pkg/dotc1z/sync_runs.go
Replaces per-call lazy-init with mutex-guarded cache access (getCachedViewSyncRun), adds invalidateCachedViewSyncRun() and calls it from StartNewSync and EndSync; computes finished sync first, returns cached early when valid.
Tests for cache behavior
pkg/dotc1z/c1file_test.go
Adds TestC1ZCachedViewSyncRunInvalidation to exercise caching across successive syncs and resource changes. Note: the test appears duplicated in the diff.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • loganintech
  • pquerna

Poem

🐰 I hopped through code at break of dawn,

Cached whispers cleared to greet the lawn,
Mutex in paw, I guard the run,
When syncs conclude, fresh flowers come. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly describes the main change: fixing a caching issue where sync run state persists incorrectly after sync lifecycle events.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b1a6b2 and ae3c464.

📒 Files selected for processing (2)
  • pkg/dotc1z/c1file.go
  • pkg/dotc1z/sync_runs.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/dotc1z/c1file.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: go-test (1.25.2, ubuntu-latest)
  • GitHub Check: go-test (1.25.2, windows-latest)
🔇 Additional comments (4)
pkg/dotc1z/sync_runs.go (4)

100-124: LGTM! Cache implementation is thread-safe.

The mutex-based caching correctly replaces the sync.Once pattern. The implementation properly:

  • Guards cache reads/writes with a mutex
  • Returns early when cached values exist
  • Caches both results and errors
  • Holds the lock during database queries to prevent duplicate computation

The lock-during-I/O pattern (lines 113, 120) may cause brief contention during first access, but this is an intentional trade-off to avoid multiple threads computing the same cache concurrently.


126-132: LGTM! Clean invalidation helper.

The invalidation method correctly clears both cached fields under mutex protection, ensuring proper synchronization with getCachedViewSyncRun.


555-555: LGTM! Cache invalidation correctly placed.

Invalidating after the new sync is inserted into the database and currentSyncID is updated ensures subsequent reads will fetch the new state.


614-614: LGTM! Cache invalidation correctly placed.

Invalidating after the sync is marked as ended in the database and currentSyncID is cleared ensures subsequent reads will fetch the newly finished sync.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
pkg/dotc1z/sync_runs.go (1)

101-125: Consider avoiding holding the mutex during database operations.

The mutex is held while calling getFinishedSync and getLatestUnfinishedSync, which perform database queries. This could cause contention if multiple goroutines attempt to access the cache simultaneously while a slow query is in progress.

A common pattern is to check validity under the lock, release the lock during the expensive operation, then re-acquire and re-check before storing:

🔎 Suggested alternative approach (double-check locking)
 func (c *C1File) getCachedViewSyncRun(ctx context.Context) (*syncRun, error) {
 	ctx, span := tracer.Start(ctx, "C1File.getCachedViewSyncRun")
 	defer span.End()

 	c.cachedViewSyncMu.Lock()
-	defer c.cachedViewSyncMu.Unlock()
-
 	if c.cachedViewSyncValid {
+		run, err := c.cachedViewSyncRun, c.cachedViewSyncErr
+		c.cachedViewSyncMu.Unlock()
-		return c.cachedViewSyncRun, c.cachedViewSyncErr
+		return run, err
 	}
+	c.cachedViewSyncMu.Unlock()

-	// First try to get a finished full sync
-	c.cachedViewSyncRun, c.cachedViewSyncErr = c.getFinishedSync(ctx, 0, connectorstore.SyncTypeFull)
-	if c.cachedViewSyncErr != nil {
-		return c.cachedViewSyncRun, c.cachedViewSyncErr
+	// Compute outside the lock
+	run, err := c.getFinishedSync(ctx, 0, connectorstore.SyncTypeFull)
+	if err != nil {
+		c.cachedViewSyncMu.Lock()
+		c.cachedViewSyncRun, c.cachedViewSyncErr = run, err
+		c.cachedViewSyncValid = true
+		c.cachedViewSyncMu.Unlock()
+		return run, err
 	}

-	// If no finished sync, try to get an unfinished one
-	if c.cachedViewSyncRun == nil {
-		c.cachedViewSyncRun, c.cachedViewSyncErr = c.getLatestUnfinishedSync(ctx, connectorstore.SyncTypeAny)
+	if run == nil {
+		run, err = c.getLatestUnfinishedSync(ctx, connectorstore.SyncTypeAny)
 	}

-	c.cachedViewSyncValid = true
-	return c.cachedViewSyncRun, c.cachedViewSyncErr
+	c.cachedViewSyncMu.Lock()
+	// Re-check in case another goroutine populated the cache
+	if !c.cachedViewSyncValid {
+		c.cachedViewSyncRun, c.cachedViewSyncErr = run, err
+		c.cachedViewSyncValid = true
+	}
+	result, resultErr := c.cachedViewSyncRun, c.cachedViewSyncErr
+	c.cachedViewSyncMu.Unlock()
+	return result, resultErr
 }

However, if concurrency is not a major concern in the current usage pattern (e.g., single-threaded access), the current implementation is simpler and correct. Please verify whether concurrent access to this method is expected.

pkg/dotc1z/c1file_test.go (1)

598-604: Consider updating misleading comments.

The comments on lines 598-599 and 602-604 describe the buggy behavior ("but it will return resource-1... because the cache wasn't invalidated"), but the test assertions expect the correct behavior after the fix. This could confuse future readers.

🔎 Suggested comment update
-	// Call ListResources again - it should return resource-2 from the new finished sync (sync2),
-	// but it will return resource-1 from the cached sync (sync1) instead because the cache wasn't invalidated
 	resp2, err := f.ListResources(ctx, v2.ResourcesServiceListResourcesRequest_builder{}.Build())
 	require.NoError(t, err)
-	// This assertion will fail because the cache wasn't invalidated when sync2 finished
+	// After the fix, the cache is invalidated when sync2 finishes, so we get resource-2
 	require.Len(t, resp2.GetList(), 1, "should return resource from new sync")
 	require.Equal(t, "resource-2", resp2.GetList()[0].GetId().GetResource(), "should return resource-2 from the new finished sync (sync2), not resource-1 from cached sync (sync1)")
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aaeb86e and 4b1a6b2.

📒 Files selected for processing (3)
  • pkg/dotc1z/c1file.go
  • pkg/dotc1z/c1file_test.go
  • pkg/dotc1z/sync_runs.go
🧰 Additional context used
🧬 Code graph analysis (2)
pkg/dotc1z/sync_runs.go (2)
pkg/dotc1z/c1file.go (1)
  • C1File (36-60)
pkg/connectorstore/connectorstore.go (2)
  • SyncTypeFull (14-14)
  • SyncTypeAny (17-17)
pkg/dotc1z/c1file_test.go (2)
pkg/dotc1z/c1file.go (2)
  • NewC1ZFile (180-216)
  • WithPragma (151-155)
pkg/connectorstore/connectorstore.go (1)
  • SyncTypeFull (14-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: go-test (1.25.2, ubuntu-latest)
  • GitHub Check: go-test (1.25.2, windows-latest)
🔇 Additional comments (5)
pkg/dotc1z/sync_runs.go (3)

127-134: LGTM!

The invalidateCachedViewSyncRun helper correctly clears all cache state under the mutex, ensuring thread-safe invalidation.


557-557: LGTM!

Cache invalidation on StartNewSync ensures that subsequent reads will recompute the cached sync run after a new sync begins.


616-616: LGTM!

Cache invalidation on EndSync ensures that the cached view sync run reflects the newly finished sync state.

pkg/dotc1z/c1file.go (1)

49-53: LGTM!

The struct fields are correctly defined for the mutex-protected cache pattern:

  • cachedViewSyncMu as a value (not pointer) is correct for embedded mutexes
  • cachedViewSyncValid as an explicit validity flag enables cache invalidation
  • cachedViewSyncErr stores any error from cache population
pkg/dotc1z/c1file_test.go (1)

548-608: Good test coverage for cache invalidation.

The test effectively validates that:

  1. The cache is populated on first ListResources call
  2. Starting and ending a new sync invalidates the cache
  3. Subsequent ListResources calls return data from the new finished sync

@kans kans enabled auto-merge (squash) December 31, 2025 20:06
@kans kans merged commit 5a5a941 into main Dec 31, 2025
6 checks passed
@kans kans deleted the ggreer/kans/invalidate-sync-run-cache branch December 31, 2025 20:09
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