@atomist/sdm
Version:
Atomist Software Delivery Machine SDK
133 lines • 5.36 kB
JavaScript
;
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.save = exports.CachingProjectLoader = void 0;
const shutdown_1 = require("@atomist/automation-client/lib/internal/util/shutdown");
const logger_1 = require("@atomist/automation-client/lib/util/logger");
const fs = require("fs-extra");
const sha = require("sha-regex");
const util_1 = require("util");
const cloningProjectLoader_1 = require("./cloningProjectLoader");
const cacheKey_1 = require("./support/cacheKey");
const LruCache_1 = require("./support/LruCache");
/**
* Caching implementation of ProjectLoader
*/
class CachingProjectLoader {
constructor(delegate = cloningProjectLoader_1.CloningProjectLoader, maxEntries = 20) {
this.delegate = delegate;
this.deleteOnExit = [];
this.cache = new LruCache_1.LruCache(maxEntries, p => this.cleanUp(p.baseDir, "eviction"));
shutdown_1.registerShutdownHook(async () => {
if (this.deleteOnExit.length > 0) {
logger_1.logger.debug("Deleting cached projects");
}
await Promise.all(this.deleteOnExit.map(p => this.cleanUp(p, "shutdown")));
return 0;
}, 10000, `deleting cached projects`);
}
async doWithProject(params, action) {
// read-only == false means the consumer is going to make changes; don't cache such projects
if (!params.readOnly) {
logger_1.logger.debug("Forcing fresh clone for non readonly use of '%j'", params.id);
return this.saveAndRunAction(this.delegate, params, action);
}
// Caching projects by branch references is wrong as the branch might change; give out new versions
if (!sha({ exact: true }).test(params.id.sha)) {
logger_1.logger.debug("Forcing fresh clone for branch use of '%j'", params.id);
return this.saveAndRunAction(this.delegate, params, action);
}
logger_1.logger.debug("Attempting to reuse clone for readonly use of '%j'", params.id);
const key = cacheKey_1.cacheKey(params);
let project = this.cache.get(key);
if (!!project) {
// Validate it, as the directory may have been cleaned up
try {
await util_1.promisify(fs.access)(project.baseDir);
}
catch (_a) {
this.cache.evict(key);
project = undefined;
}
}
if (!project) {
project = await save(this.delegate, params);
logger_1.logger.debug("Caching project '%j' at '%s'", project.id, project.baseDir);
this.cache.put(key, project);
}
logger_1.logger.debug("About to invoke action. Cache stats: %j", this.cache.stats);
return action(project);
}
/**
* Save project and run provided WithLoadedProject action on it.
* @param delegate
* @param params
* @param action
*/
async saveAndRunAction(delegate, params, action) {
const p = await save(delegate, params);
if (params.context && params.context.lifecycle) {
params.context.lifecycle.registerDisposable(async () => this.cleanUp(p.baseDir, "disposal"));
}
else {
// schedule a cleanup timer but don't block the Node.js event loop for this
setTimeout(async () => this.cleanUp(p.baseDir, "timeout"), 10000).unref();
// also store a reference to this project to be deleted when we exit
this.deleteOnExit.push(p.baseDir);
}
return action(p);
}
/**
* Eviction callback to clean up file system resources.
* @param dir
* @param reason
*/
async cleanUp(dir, reason) {
if (dir && await fs.pathExists(dir)) {
if (reason === "timeout") {
logger_1.logger.debug(`Deleting project '%s' because a timeout passed`, dir);
}
else {
logger_1.logger.debug(`Deleting project '%s' because %s was triggered`, dir, reason);
}
try {
await fs.remove(dir);
const ix = this.deleteOnExit.indexOf(dir);
if (ix >= 0) {
this.deleteOnExit.slice(ix, 1);
}
}
catch (err) {
logger_1.logger.warn(err);
}
}
}
}
exports.CachingProjectLoader = CachingProjectLoader;
/**
* Delegate to the underlying ProjectLoader to load the project.
* @param pl
* @param params
*/
function save(pl, params) {
let p;
return pl.doWithProject(params, async (loaded) => {
p = loaded;
}).then(() => p);
}
exports.save = save;
//# sourceMappingURL=CachingProjectLoader.js.map