MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / flush

Method flush

src/sync/watcher.ts:838–945  ·  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

836 * unindexed, and the rescheduled sync will absorb the same set next time.
837 */
838 private async flush(): Promise<void> {
839 // If already syncing, the post-sync check will re-trigger
840 if (this.syncing || this.stopped) return;
841
842 this.syncStartedMs = Date.now();
843 this.syncing = true;
844
845 // Scoped fast path: when every pending change is a known file event, hand
846 // the exact paths to sync and skip its O(repo) scan-diff. Anything the
847 // events can't fully describe — a directory removal, an empty pending set
848 // (retry paths), or an event storm past the ceiling — runs the full
849 // scan-diff, which remains the ground truth.
850 const scoped =
851 !this.needsFullScan && this.pendingFiles.size > 0 && this.pendingFiles.size <= SCOPED_SYNC_MAX_PENDING
852 ? [...this.pendingFiles.keys()]
853 : undefined;
854
855 try {
856 const result = await this.syncFn(scoped);
857 if (!scoped) this.needsFullScan = false;
858 this.lockRetryCount = 0; // a clean sync clears any contention backoff
859 this.syncFailureRetryCount = 0; // ...and any generic-failure backoff
860 // Remove entries whose most recent event predates this sync — those
861 // edits are now in the DB. Entries with lastSeenMs > syncStartedMs
862 // arrived mid-sync; whether the in-flight sync captured them depends
863 // on when sync read that file, so we keep them as pending and let
864 // the follow-up sync handle them. We prefer false positives ("shown
865 // stale, actually fresh" → at worst one extra Read) over false
866 // negatives ("shown fresh, actually stale" → misleads the agent).
867 for (const [filePath, info] of this.pendingFiles) {
868 if (info.lastSeenMs <= this.syncStartedMs) {
869 this.pendingFiles.delete(filePath);
870 }
871 }
872 this.onSyncComplete?.(result);
873 } catch (err) {
874 if (err instanceof LockUnavailableError) {
875 this.lockRetryCount += 1;
876 // Lock-failure no-op (another writer holds the lock). pendingFiles
877 // stays intact and the `finally` block reschedules with backoff. Keep
878 // brief contention quiet (debug-only — a long external index would
879 // otherwise spam stderr every cycle), but stop retrying forever: once a
880 // writer holds the lock past the budget, degrade auto-sync explicitly.
881 logDebug('Watch sync skipped: file lock unavailable', {
882 pendingFiles: this.pendingFiles.size,
883 retryCount: this.lockRetryCount,
884 });
885 if (this.lockRetryCount > MAX_LOCK_RETRIES) {
886 this.degrade(
887 'CodeGraph file lock held by another process past the retry budget; ' +
888 'auto-sync disabled. Run `codegraph sync` once the other writer finishes ' +
889 '(or install git sync hooks) to refresh the graph.',
890 { pendingFiles: this.pendingFiles.size, retryCount: this.lockRetryCount }
891 );
892 }
893 } else {
894 this.lockRetryCount = 0; // a non-lock failure isn't contention; reset that streak
895 this.syncFailureRetryCount += 1;

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