Skip to content

Commit e377767

Browse files
authored
fix(core): invalidate sync generator cache on file changes and use up-to-date project graph (#33780)
## Current Behavior Sync generators are processed in the background by the daemon server. Their results are cached and reprocessed when the project graph is recomputed. There are currently two issues: - The cache is only invalidated after the project graph finishes recomputing, which means that there's an interval between files changed (triggering the project graph recomputation) and the recomputation finishes, where the cache is not invalidated, and it's stale. During that interval, any request to get the sync generator changes will use the stale cache. - Sync generators are scheduled to be processed after the project graph is recomputed, so a quick succession of recomputations can be coalesced. The problem is that the scheduled closure uses the project graph from the initial scheduling, rather than the latest available project graph at the time it runs. This results in the usage of stale data to process the sync generators. ## Expected Behavior Getting sync generators changes should always return up-to-date information.
1 parent 99d226f commit e377767

4 files changed

Lines changed: 57 additions & 3 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { serverLogger } from '../logger';
2+
3+
export interface FileChangeEvent {
4+
createdFiles: string[];
5+
updatedFiles: string[];
6+
deletedFiles: string[];
7+
}
8+
9+
const fileChangeListeners = new Set<(event: FileChangeEvent) => void>();
10+
11+
export function registerFileChangeListener(
12+
listener: (event: FileChangeEvent) => void
13+
): void {
14+
fileChangeListeners.add(listener);
15+
}
16+
17+
export function notifyFileChangeListeners(event: FileChangeEvent): void {
18+
for (const listener of fileChangeListeners) {
19+
try {
20+
listener(event);
21+
} catch (error) {
22+
serverLogger.log('Error notifying file change listener:', error);
23+
}
24+
}
25+
}

packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from '../../utils/workspace-context';
2828
import { workspaceRoot } from '../../utils/workspace-root';
2929
import { notifyFileWatcherSockets } from './file-watching/file-watcher-sockets';
30+
import { notifyFileChangeListeners } from './file-watching/file-change-events';
3031
import { notifyProjectGraphListenerSockets } from './project-graph-listener-sockets';
3132
import { serverLogger } from './logger';
3233
import { NxWorkspaceFilesExternals } from '../../native';
@@ -171,6 +172,15 @@ export function addUpdatedAndDeletedFiles(
171172
collectedDeletedFiles.add(f);
172173
}
173174

175+
// Notify file change listeners immediately when files change
176+
if (
177+
createdFiles.length > 0 ||
178+
updatedFiles.length > 0 ||
179+
deletedFiles.length > 0
180+
) {
181+
notifyFileChangeListeners({ createdFiles, updatedFiles, deletedFiles });
182+
}
183+
174184
if (updatedFiles.length > 0 || deletedFiles.length > 0) {
175185
notifyFileWatcherSockets(null, updatedFiles, deletedFiles);
176186
}

packages/nx/src/daemon/server/server.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,11 @@ import {
110110
isHandleGetSyncGeneratorChangesMessage,
111111
} from '../message-types/get-sync-generator-changes';
112112
import { handleGetSyncGeneratorChanges } from './handle-get-sync-generator-changes';
113-
import { collectAndScheduleSyncGenerators } from './sync-generators';
113+
import {
114+
clearSyncGeneratorsCache,
115+
collectAndScheduleSyncGenerators,
116+
} from './sync-generators';
117+
import { registerFileChangeListener } from './file-watching/file-change-events';
114118
import {
115119
GET_REGISTERED_SYNC_GENERATORS,
116120
isHandleGetRegisteredSyncGeneratorsMessage,
@@ -720,6 +724,8 @@ export async function startServer(): Promise<Server> {
720724
registerProjectGraphRecomputationListener(
721725
collectAndScheduleSyncGenerators
722726
);
727+
// register file change listener to invalidate sync generator cache
728+
registerFileChangeListener(clearSyncGeneratorsCache);
723729
// trigger an initial project graph recomputation
724730
addUpdatedAndDeletedFiles([], [], []);
725731

packages/nx/src/daemon/server/sync-generators.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ const log = (...messageParts: unknown[]) => {
3838
serverLogger.log('[SYNC]:', ...messageParts);
3939
};
4040

41+
export function clearSyncGeneratorsCache(): void {
42+
log('clearing sync generators cache due to file changes');
43+
syncGeneratorsCacheResultPromises.clear();
44+
}
45+
4146
export async function getCachedSyncGeneratorChanges(
4247
generators: string[]
4348
): Promise<SyncGeneratorRunResult[]> {
@@ -136,8 +141,16 @@ export function collectAndScheduleSyncGenerators(
136141
return;
137142
}
138143

139-
const { projects } =
140-
readProjectsConfigurationFromProjectGraph(projectGraph);
144+
// Fetch the latest graph instead of using the captured one which might be stale
145+
const { projectGraph: latestGraph, error } =
146+
await getCachedSerializedProjectGraphPromise();
147+
148+
if (!latestGraph || error) {
149+
log('Cannot run scheduled generators: graph unavailable or errored');
150+
return;
151+
}
152+
153+
const { projects } = readProjectsConfigurationFromProjectGraph(latestGraph);
141154

142155
for (const generator of scheduledGenerators) {
143156
syncGeneratorsCacheResultPromises.set(

0 commit comments

Comments
 (0)