MCPcopy
hub / github.com/colbymchenry/codegraph / flush

Method flush

src/sync/watcher.ts:755–827  ·  view source on GitHub ↗

* Flush pending changes by running sync. * * pendingFiles is NOT cleared at the start of sync — entries are removed * only after sync commits successfully, and only for entries whose * lastSeenMs <= syncStartedMs. That way, a query that arrives mid-sync * still sees the affected files

()

Source from the content-addressed store, hash-verified

753 * unindexed, and the rescheduled sync will absorb the same set next time.
754 */
755 private async flush(): Promise<void> {
756 // If already syncing, the post-sync check will re-trigger
757 if (this.syncing || this.stopped) return;
758
759 this.syncStartedMs = Date.now();
760 this.syncing = true;
761
762 try {
763 const result = await this.syncFn();
764 this.lockRetryCount = 0; // a clean sync clears any contention backoff
765 // Remove entries whose most recent event predates this sync — those
766 // edits are now in the DB. Entries with lastSeenMs > syncStartedMs
767 // arrived mid-sync; whether the in-flight sync captured them depends
768 // on when sync read that file, so we keep them as pending and let
769 // the follow-up sync handle them. We prefer false positives ("shown
770 // stale, actually fresh" → at worst one extra Read) over false
771 // negatives ("shown fresh, actually stale" → misleads the agent).
772 for (const [filePath, info] of this.pendingFiles) {
773 if (info.lastSeenMs <= this.syncStartedMs) {
774 this.pendingFiles.delete(filePath);
775 }
776 }
777 this.onSyncComplete?.(result);
778 } catch (err) {
779 if (err instanceof LockUnavailableError) {
780 this.lockRetryCount += 1;
781 // Lock-failure no-op (another writer holds the lock). pendingFiles
782 // stays intact and the `finally` block reschedules with backoff. Keep
783 // brief contention quiet (debug-only — a long external index would
784 // otherwise spam stderr every cycle), but stop retrying forever: once a
785 // writer holds the lock past the budget, degrade auto-sync explicitly.
786 logDebug('Watch sync skipped: file lock unavailable', {
787 pendingFiles: this.pendingFiles.size,
788 retryCount: this.lockRetryCount,
789 });
790 if (this.lockRetryCount > MAX_LOCK_RETRIES) {
791 this.degrade(
792 'CodeGraph file lock held by another process past the retry budget; ' +
793 'auto-sync disabled. Run `codegraph sync` once the other writer finishes ' +
794 '(or install git sync hooks) to refresh the graph.',
795 { pendingFiles: this.pendingFiles.size, retryCount: this.lockRetryCount }
796 );
797 }
798 } else {
799 this.lockRetryCount = 0; // a non-lock failure isn't contention; reset backoff
800 const error = err instanceof Error ? err : new Error(String(err));
801 logWarn('Watch sync failed', { error: error.message });
802 this.onSyncError?.(error);
803 }
804 // Failure: leave pendingFiles untouched. Every edit it tracks is
805 // still unindexed; the rescheduled sync sees the same set.
806 } finally {
807 this.syncing = false;
808
809 // If pending files remain (mid-sync events, or this sync failed),
810 // schedule another pass. After lock contention, back off exponentially
811 // (debounceMs · 2^(n-1), capped) instead of retrying at the normal
812 // debounce cadence; a clean sync resets lockRetryCount so normal edits

Callers 2

scheduleSyncMethod · 0.95
scheduleRetrySyncMethod · 0.95

Calls 5

degradeMethod · 0.95
scheduleRetrySyncMethod · 0.95
scheduleSyncMethod · 0.95
logDebugFunction · 0.90
logWarnFunction · 0.90

Tested by

no test coverage detected