Make file cache size configurable via a tsconfig argument.
PiperOrigin-RevId: 182509779
diff --git a/internal/common/tsconfig.bzl b/internal/common/tsconfig.bzl
index 407a4a4..a663ad6 100644
--- a/internal/common/tsconfig.bzl
+++ b/internal/common/tsconfig.bzl
@@ -118,6 +118,12 @@
else:
bazel_options["allowedStrictDeps"] = [f.path for f in allowed_deps]
+ if "TYPESCRIPT_WORKER_CACHE_SIZE_MB" in ctx.var:
+ max_cache_size_mb = int(ctx.var["TYPESCRIPT_WORKER_CACHE_SIZE_MB"])
+ if max_cache_size_mb < 0:
+ fail("TYPESCRIPT_WORKER_CACHE_SIZE_MB set to a negative value (%d)." % max_cache_size_mb)
+ bazel_options["maxCacheSizeMb"] = max_cache_size_mb
+
# Keep these options in sync with those in playground/playground.ts.
compiler_options = {
# De-sugar to this language level
diff --git a/internal/tsc_wrapped/file_cache.ts b/internal/tsc_wrapped/file_cache.ts
index 176468d..a335570 100644
--- a/internal/tsc_wrapped/file_cache.ts
+++ b/internal/tsc_wrapped/file_cache.ts
@@ -31,10 +31,11 @@
inCache(key: string): boolean;
}
-// Cache at most to this amount of memory use. It appears that
-// without the cache involved, our steady state size after parsing is
-// in the ~150mb range.
-const MAX_CACHE_SIZE = 300 * (1 << 20 /* 1 MB */);
+/**
+ * Default cache size. Without the cache involved, our steady state size
+ * after parsing is in the ~150mb range.
+ */
+const DEFAULT_MAX_CACHE_SIZE = 300 * (1 << 20 /* 1 MB */);
/**
* FileCache is a trivial LRU cache for bazel outputs.
@@ -53,13 +54,29 @@
* digest to assign to a newly loaded file.
*/
private lastDigests: {[filePath: string]: string} = {};
- public cacheStats = {
+
+ cacheStats = {
hits: 0,
reads: 0,
readTimeMs: 0,
};
- constructor(private debug: (...msg: any[]) => void) {}
+ private maxCacheSize = DEFAULT_MAX_CACHE_SIZE;
+
+ constructor(private debug: (...msg: Array<{}>) => void) {}
+
+ setMaxCacheSize(maxCacheSize: number) {
+ if (maxCacheSize < 0) {
+ throw new Error(`FileCache max size is negative: ${maxCacheSize}`);
+ }
+ this.debug('FileCache max size is', maxCacheSize >> 20, 'MB');
+ this.maxCacheSize = maxCacheSize;
+ this.maybeFreeMemory();
+ }
+
+ resetMaxCacheSize() {
+ this.setMaxCacheSize(DEFAULT_MAX_CACHE_SIZE);
+ }
/**
* Updates the cache with the given digests.
@@ -112,18 +129,7 @@
putCache(filePath: string, entry: CacheEntry<CachedType>): void {
const readStart = Date.now();
this.cacheStats.readTimeMs += Date.now() - readStart;
-
- let dropped = 0;
- if (this.shouldFreeMemory()) {
- // Drop half the cache, the least recently used entry == the first
- // entry.
- this.debug('Evicting from the cache');
- const keys = Object.keys(this.fileCache);
- dropped = Math.round(keys.length / 2);
- for (let i = 0; i < dropped; i++) {
- delete this.fileCache[keys[i]];
- }
- }
+ const dropped = this.maybeFreeMemory();
this.fileCache[filePath] = entry;
this.debug('Loaded', filePath, 'dropped', dropped, 'cache entries');
}
@@ -178,8 +184,25 @@
* Defined as a property so it can be overridden in tests.
*/
shouldFreeMemory: () => boolean = () => {
- return process.memoryUsage().heapUsed > MAX_CACHE_SIZE;
+ return process.memoryUsage().heapUsed > this.maxCacheSize;
};
+
+ /**
+ * Frees memory if required. Returns the number of dropped entries.
+ */
+ private maybeFreeMemory() {
+ if (!this.shouldFreeMemory()) {
+ return 0;
+ }
+ // Drop half the cache, the least recently used entry == the first entry.
+ this.debug('Evicting from the cache');
+ const keys = Object.keys(this.fileCache);
+ const dropped = Math.round(keys.length / 2);
+ for (let i = 0; i < dropped; i++) {
+ delete this.fileCache[keys[i]];
+ }
+ return dropped;
+ }
}
/**
diff --git a/internal/tsc_wrapped/file_cache_test.ts b/internal/tsc_wrapped/file_cache_test.ts
index 2f5d692..1c00f39 100644
--- a/internal/tsc_wrapped/file_cache_test.ts
+++ b/internal/tsc_wrapped/file_cache_test.ts
@@ -50,11 +50,12 @@
});
it('caches in LRU order', () => {
- let free = false;
const fileCache = new FileCache<ts.SourceFile>(fauxDebug);
- const fileLoader = new CachedFileLoader(fileCache, true);
+ let free = false;
fileCache.shouldFreeMemory = () => free;
+ const fileLoader = new CachedFileLoader(fileCache, true);
+
function load(name: string, fn: string) {
return fileLoader.loadFile(name, fn, ts.ScriptTarget.ES5);
}
diff --git a/internal/tsc_wrapped/tsc_wrapped.ts b/internal/tsc_wrapped/tsc_wrapped.ts
index 27451ab..88a40e4 100644
--- a/internal/tsc_wrapped/tsc_wrapped.ts
+++ b/internal/tsc_wrapped/tsc_wrapped.ts
@@ -39,11 +39,33 @@
*/
function runOneBuild(
args: string[], inputs?: {[path: string]: string}): boolean {
+ // Strip leading at-signs, used in build_defs.bzl to indicate a params file
+ const tsconfigFile = args[0].replace(/^@+/, '');
+
+ const [parsed, errors, {target}] = parseTsconfig(tsconfigFile);
+ if (errors) {
+ console.error(diagnostics.format(target, errors));
+ return false;
+ }
+ if (!parsed) {
+ throw new Error(
+ 'Impossible state: if parseTsconfig returns no errors, then parsed should be non-null');
+ }
+ const {options, bazelOpts, files} = parsed;
+
// Reset cache stats.
fileCache.resetStats();
fileCache.traceStats();
+ if (bazelOpts.maxCacheSizeMb !== undefined) {
+ const maxCacheSizeBytes = bazelOpts.maxCacheSizeMb * 1 << 20;
+ fileCache.setMaxCacheSize(maxCacheSizeBytes);
+ } else {
+ fileCache.resetMaxCacheSize();
+ }
+
let fileLoader: FileLoader;
const allowNonHermeticReads = true;
+
if (inputs) {
fileLoader = new CachedFileLoader(fileCache, allowNonHermeticReads);
// Resolve the inputs to absolute paths to match TypeScript internals
@@ -60,19 +82,7 @@
console.error('Expected one argument: path to tsconfig.json');
return false;
}
- // Strip leading at-signs, used in build_defs.bzl to indicate a params file
- const tsconfigFile = args[0].replace(/^@+/, '');
- const [parsed, errors, {target}] = parseTsconfig(tsconfigFile);
- if (errors) {
- console.error(diagnostics.format(target, errors));
- return false;
- }
- if (!parsed) {
- throw new Error(
- 'Impossible state: if parseTsconfig returns no errors, then parsed should be non-null');
- }
- const {options, bazelOpts, files} = parsed;
const compilerHostDelegate =
ts.createCompilerHost({target: ts.ScriptTarget.ES5});
diff --git a/internal/tsc_wrapped/tsconfig.ts b/internal/tsc_wrapped/tsconfig.ts
index 9a10023..8475b36 100644
--- a/internal/tsc_wrapped/tsconfig.ts
+++ b/internal/tsc_wrapped/tsconfig.ts
@@ -111,6 +111,11 @@
* ಠ_ಠ.clutz namespace.
*/
addDtsClutzAliases: true;
+
+ /**
+ * The maximum cache size for bazel outputs, in megabytes.
+ */
+ maxCacheSizeMb?: number;
}
export interface ParsedTsConfig {