diff --git a/packages/core/src/service/CacheService.ts b/packages/core/src/service/CacheService.ts index 2208c8c31..6209f54b5 100644 --- a/packages/core/src/service/CacheService.ts +++ b/packages/core/src/service/CacheService.ts @@ -58,6 +58,7 @@ interface ValidateResult { export class CacheService { checksums = Checksums.create() errors: ErrorCache = {} + dirtyFiles = new Set() #hasValidatedFiles = false /** @@ -69,15 +70,9 @@ export class CacheService { if (!this.#hasValidatedFiles) { return } - try { - // TODO: Don't update this for every single change. - this.checksums.files[doc.uri] = await this.project.externals.crypto.getSha1( - doc.getText(), - ) - } catch (e) { - if (!this.project.externals.error.isKind(e, 'EISDIR')) { - this.project.logger.error(`[CacheService#hash-file] ${doc.uri}`) - } + this.dirtyFiles.add(doc.uri) + if (this.dirtyFiles.size > 100) { + await this.flushDirtyFiles() } }) this.project.on('rootsUpdated', async ({ roots }) => { @@ -89,7 +84,7 @@ export class CacheService { this.checksums.roots[root] = await this.project.fs.hash(root) } catch (e) { if (!this.project.externals.error.isKind(e, 'EISDIR')) { - this.project.logger.error(`[CacheService#hash-root] ${root}`) + this.project.logger.error(`[CacheService#hash-root] ${root}`, e) } } } @@ -114,6 +109,19 @@ export class CacheService { return this.#cacheFilePath } + private async flushDirtyFiles() { + for (const uri of this.dirtyFiles) { + try { + this.checksums.files[uri] = await this.project.fs.hash(uri) + } catch (e) { + if (!this.project.externals.error.isKind(e, 'EISDIR')) { + this.project.logger.error(`[CacheService#flushDirtyFiles] ${uri}`, e) + } + } + } + this.dirtyFiles.clear() + } + async load(): Promise { const ans: LoadResult = { symbols: {} } if (this.project.projectRoots.length === 0) { @@ -163,7 +171,7 @@ export class CacheService { } } catch (e) { if (!this.project.externals.error.isKind(e, 'EISDIR')) { - this.project.logger.error(`[CacheService#hash-file] ${uri}`) + this.project.logger.error(`[CacheService#hash-file] ${uri}`, e) } // Failed calculating hash. Assume the root has changed. } @@ -222,6 +230,10 @@ export class CacheService { let filePath: string | undefined try { filePath = await this.getCacheFileUri() + + await this.flushDirtyFiles() + __profiler.task('Hash Files') + const cache: CacheFile = { version: LatestCacheVersion, projectRoots: this.project.projectRoots, diff --git a/packages/core/src/service/Project.ts b/packages/core/src/service/Project.ts index 9643ee7f9..90d30236d 100644 --- a/packages/core/src/service/Project.ts +++ b/packages/core/src/service/Project.ts @@ -36,7 +36,7 @@ import { fileUtil } from './fileUtil.js' import { MetaRegistry } from './MetaRegistry.js' import { ProfilerFactory } from './Profiler.js' -const CacheAutoSaveInterval = 600_000 // 10 Minutes. +const CacheAutoSaveInterval = 300_000 // 5 Minutes. export type ProjectInitializerContext = Pick< Project, @@ -160,7 +160,7 @@ export class Project implements ExternalEventEmitter { /** Prevent circular binding. */ readonly #bindingInProgressUris = new Set() - readonly #cacheSaverIntervalId: IntervalId + #cacheSaverIntervalId: IntervalId | undefined = undefined readonly cacheService: CacheService /** URI of files that are currently managed by the language client. */ readonly #clientManagedUris = new Set() @@ -341,10 +341,6 @@ export class Project implements ExternalEventEmitter { this.setInitPromise() this.setReadyPromise() - this.#cacheSaverIntervalId = setInterval( - () => this.cacheService.save(), - CacheAutoSaveInterval, - ) this.on('documentUpdated', ({ doc, node }) => { // if (!this.#isReady) { @@ -586,6 +582,13 @@ export class Project implements ExternalEventEmitter { __profiler.finalize() this.emit('ready', {}) + + // Save the cache after ready and start the auto-cache interval + await this.cacheService.save() + this.#cacheSaverIntervalId = setInterval( + () => this.cacheService.save(), + CacheAutoSaveInterval, + ) } this.#isReady = false this.#readyPromise = ready()