Skip to content

Commit

Permalink
fix: cleanup invalid cache entries if they're not decodable
Browse files Browse the repository at this point in the history
  • Loading branch information
wessberg committed May 20, 2021
1 parent 292d9f5 commit a2d3376
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 39 deletions.
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,6 @@
"@webcomponents/shadydom": "1.8.0",
"@webcomponents/template": "1.4.4",
"@wessberg/di": "2.0.3",
"@wessberg/fileloader": "1.1.12",
"@wessberg/filesaver": "1.0.11",
"@wessberg/pointer-events": "1.0.9",
"@wessberg/stringutil": "1.0.19",
"Base64": "1.1.0",
Expand Down
6 changes: 6 additions & 0 deletions src/common/lib/file-system/file-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface FileSystem {
exists(path: string): Promise<boolean>;
readFile(path: string): Promise<Buffer | undefined>;
writeFile(path: string, content: string | Buffer): Promise<void>;
delete(path: string): Promise<boolean>;
}
41 changes: 41 additions & 0 deletions src/common/lib/file-system/real-file-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {FileSystem} from "./file-system";
import {promises} from "fs";
import {dirname} from "path";

export const realFileSystem: FileSystem = {
async exists(path: string): Promise<boolean> {
try {
await promises.stat(path);
return true;
} catch {
return false;
}
},
async readFile(path: string): Promise<Buffer | undefined> {
if (!(await this.exists(path))) return undefined;
try {
return promises.readFile(path);
} catch {
return undefined;
}
},

async delete(path: string): Promise<boolean> {
try {
await promises.rm(path, {force: true, recursive: true});
return true;
} catch {
return false;
}
},

async writeFile(path: string, content: string | Buffer): Promise<void> {
try {
await promises.mkdir(dirname(path), {recursive: true});
return promises.writeFile(path, content);
} catch {
// The FileSystem might not allow mutations at the given path.
// in any case, the operation failed
}
}
};
63 changes: 30 additions & 33 deletions src/service/registry/cache-registry/cache-registry-service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {ICacheRegistryService} from "./i-cache-registry-service";
import {IFileSaver} from "@wessberg/filesaver";
import {PolyfillFeature, PolyfillFeatureInput} from "../../../polyfill/polyfill-feature";
import {getPolyfillConfigChecksum, getPolyfillIdentifier, getPolyfillSetIdentifier} from "../../../util/polyfill/polyfill-util";
import {join} from "path";
import {constant} from "../../../constant/constant";
import {IFileLoader} from "@wessberg/fileloader";
import {IMemoryRegistryService, PolyfillCachingContext} from "../polyfill-registry/i-memory-registry-service";
import {PolyfillName} from "../../../polyfill/polyfill-name";
import {coerce, gt} from "semver";
Expand All @@ -13,18 +11,18 @@ import {ILoggerService} from "../../logger/i-logger-service";
import {PolyfillDictEntry, PolyfillDictNormalizedEntry} from "../../../polyfill/polyfill-dict";
import pkg from "../../../../package.json";
import {Config} from "../../../config/config";
import {ApiError} from "../../../api/lib/api-error";
import {StatusCodes} from "http-status-codes";
import {IMetricsService} from "../../metrics/i-metrics-service";
import {FileSystem} from "../../../common/lib/file-system/file-system";

/**
* A class that can cache generated Polyfills on disk
*/
export class CacheRegistryService implements ICacheRegistryService {
constructor(
private readonly fileSaver: IFileSaver,
private readonly fileSystem: FileSystem,
private readonly logger: ILoggerService,
private readonly fileLoader: IFileLoader,
private readonly memoryRegistry: IMemoryRegistryService,
private readonly metricsService: IMetricsService,
private readonly config: Config
) {}

Expand Down Expand Up @@ -66,18 +64,22 @@ export class CacheRegistryService implements ICacheRegistryService {
if (memoryHit != null) return memoryHit;

// Otherwise, attempt to get it from cache
const buffer = await this.getFromCache(this.getCachePathForPolyfillSet(input, context));
const cachePath = this.getCachePathForPolyfillSet(input, context);
const buffer = await this.getFromCache(cachePath);

// If not possible, return undefined
if (buffer == null) return undefined;
let polyfillFeatures: PolyfillFeature[];

// Otherwise, store it in the memory registry and return the Buffer
try {
polyfillFeatures = JSON.parse(buffer.toString());
const polyfillFeatures = JSON.parse(buffer.toString());
return await this.memoryRegistry.setPolyfillFeatureSet(input, new Set(polyfillFeatures), context);
} catch (ex) {
throw new ApiError(StatusCodes.INTERNAL_SERVER_ERROR, `Could not decode polyfill features based on the buffer: ${buffer.toString()}`);
// It wasn't possible to parse that buffer. The disk cache is in an invalid state, and should be cleaned up
await this.deleteFromCache(cachePath);
this.metricsService.captureMessage(`Wiped bad cache entry at path: ${cachePath}`);
return undefined;
}
return await this.memoryRegistry.setPolyfillFeatureSet(input, new Set(polyfillFeatures), context);
}

/**
Expand Down Expand Up @@ -131,7 +133,7 @@ export class CacheRegistryService implements ICacheRegistryService {
packageVersionMap[name] = version;
}

await this.fileSaver.save(constant.path.cachePackageVersionMap, JSON.stringify(packageVersionMap, null, " "));
await this.fileSystem.writeFile(constant.path.cachePackageVersionMap, JSON.stringify(packageVersionMap, null, " "));
}

private pickVersionForPolyfillEntry(entry: PolyfillDictNormalizedEntry): string {
Expand Down Expand Up @@ -211,23 +213,15 @@ export class CacheRegistryService implements ICacheRegistryService {
/**
* Flushes the cache entirely
*/
private async flushCache(): Promise<void> {
try {
await this.fileSaver.remove(constant.path.cacheRoot);
} catch {
// The environment does not allow writing to the cache root
}
private async flushCache(): Promise<boolean> {
return this.fileSystem.delete(constant.path.cacheRoot);
}

/**
* Writes the given Buffer to cache
*/
private async writeToCache(path: string, content: Buffer): Promise<void> {
try {
await this.fileSaver.save(path, content);
} catch {
// The environment does not allow writing to the cache root
}
return this.fileSystem.writeFile(path, content);
}

/**
Expand All @@ -240,7 +234,10 @@ export class CacheRegistryService implements ICacheRegistryService {
try {
return packageVersionMapRaw == null ? {} : JSON.parse(packageVersionMapRaw.toString());
} catch (ex) {
throw new ApiError(StatusCodes.INTERNAL_SERVER_ERROR, "Could not decode package version Map");
// If it couldn't be decoded, fall back to an empty cache map, but also flush that entry from the cache
await this.deleteFromCache(constant.path.cachePackageVersionMap);
this.metricsService.captureMessage(`Wiped bad package version map from the cache at path: ${constant.path.cachePackageVersionMap}`);
return {};
}
}

Expand All @@ -250,21 +247,21 @@ export class CacheRegistryService implements ICacheRegistryService {
}

private async updateCachedPolyfillConfigChecksumPackageVersionMap(): Promise<void> {
await this.fileSaver.save(constant.path.configChecksum, getPolyfillConfigChecksum());
await this.fileSystem.writeFile(constant.path.configChecksum, getPolyfillConfigChecksum());
}

/**
* Returns the contents on the given path from the cache
*
* @param path
* @returns
*/
private async getFromCache(path: string): Promise<Buffer | undefined> {
try {
return await this.fileLoader.load(path);
} catch {
return undefined;
}
return this.fileSystem.readFile(path);
}

/**
* Deletes the contents on the given path from the cache
*/
private async deleteFromCache(path: string): Promise<boolean> {
return this.fileSystem.delete(path);
}

/**
Expand Down
7 changes: 3 additions & 4 deletions src/services.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {DIContainer} from "@wessberg/di";
import {FileLoader, IFileLoader} from "@wessberg/fileloader";
import {FileSaver, IFileSaver} from "@wessberg/filesaver";
import {ILoggerService} from "./service/logger/i-logger-service";
import {LoggerService} from "./service/logger/logger-service";
import {Config, config} from "./config/config";
Expand All @@ -23,12 +21,13 @@ import {Server} from "./api/server/server";
import {PolyfillApiController} from "./api/controller/polyfill-api-controller";
import {StaticApiController} from "./api/controller/static-api-controller";
import {NoopMetricsService} from "./service/metrics/noop-metrics-service";
import {FileSystem} from "./common/lib/file-system/file-system";
import {realFileSystem} from "./common/lib/file-system/real-file-system";

export const container = new DIContainer();

// Utilities
container.registerSingleton<IFileLoader, FileLoader>();
container.registerSingleton<IFileSaver, FileSaver>();
container.registerSingleton<FileSystem>(() => realFileSystem);

// Services
container.registerSingleton<ILoggerService, LoggerService>();
Expand Down

0 comments on commit a2d3376

Please sign in to comment.