UNPKG

azurite

Version:

An open source Azure Storage API compatible server

201 lines 8.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const constants_1 = require("../utils/constants"); var Status; (function (Status) { Status[Status["Initializing"] = 0] = "Initializing"; Status[Status["Running"] = 1] = "Running"; Status[Status["Closing"] = 2] = "Closing"; Status[Status["Closed"] = 3] = "Closed"; })(Status || (Status = {})); /** * GC manager to clean up unused extents mapped local files based on mark and sweep * algorithm. * * In the future, GC manager can also help merging small extent mapped files * into one big file to improve the performance. * * @export * @class BlobGCManager * @implements {IGCManager} */ class BlobGCManager { /** * Creates an instance of BlobGCManager. * * @param {IBlobMetadataStore} blobDataStore * @param {(err: Error) => void} errorHandler Error handler callback to handle critical errors during GC loop * When an error happens, GC loop will close automatically * @param {ILogger} logger * @param {number} [gcIntervalInMS=DEFAULT_GC_INTERVAL_MS] * @memberof BlobGCManager */ constructor(referredExtentsProvider, allExtentsProvider, extentStore, errorHandler, logger, gcIntervalInMS = constants_1.DEFAULT_GC_INTERVAL_MS) { this.referredExtentsProvider = referredExtentsProvider; this.allExtentsProvider = allExtentsProvider; this.extentStore = extentStore; this.errorHandler = errorHandler; this.logger = logger; this.gcIntervalInMS = gcIntervalInMS; this._status = Status.Closed; this.emitter = new events_1.EventEmitter(); this.emitter.once("error", this.errorHandler); // Avoid infinite GC loop if (gcIntervalInMS <= 0) { this.gcIntervalInMS = 1; } } get status() { return this._status; } /** * Initialize and start GC manager. * * @returns {Promise<void>} * @memberof BlobGCManager */ async start() { if (this._status === Status.Running) { this.logger.info(`BlobGCManager:start() BlobGCManager successfully started. BlobGCManager is already in Running status.`); return; } if (this._status !== Status.Closed) { const error = new Error(`BlobGCManager:start() BlobGCManager cannot start, current manager is under ${Status[this._status]}`); this.logger.error(error.message); throw error; } this.logger.info(`BlobGCManager:start() Starting BlobGCManager. Set status to Initializing.`); this._status = Status.Initializing; if (!this.referredExtentsProvider.isInitialized()) { this.logger.info(`BlobGCManager:start() blobDataStore doesn't boot up. Starting blobDataStore.`); await this.referredExtentsProvider.init(); this.logger.info(`BlobGCManager:start() blobDataStore successfully started.`); } if (!this.allExtentsProvider.isInitialized()) { this.logger.info(`BlobGCManager:start() extentMetadata doesn't boot up. Starting extentMetadata.`); await this.allExtentsProvider.init(); this.logger.info(`BlobGCManager:start() extentMetadata successfully started.`); } if (!this.extentStore.isInitialized()) { this.logger.info(`BlobGCManager:start() extentStore doesn't boot up. Starting extentStore.`); await this.extentStore.init(); this.logger.info(`BlobGCManager:start() extentStore successfully started.`); } this.logger.info(`BlobGCManager:start() Trigger mark and sweep loop. Set status to Running.`); this._status = Status.Running; this.markSweepLoop() .then(() => { this.logger.info(`BlobGCManager:start() Mark and sweep loop is closed.`); this.emitter.emit("closed"); }) .catch(err => { this.logger.info(`BlobGCManager:start() Mark and sweep loop emits error: ${err.name} ${err.message}`); this.logger.info(`BlobGCManager:start() Set status to Closed.`); this._status = Status.Closed; this.emitter.emit("error", err); }); this.logger.info(`BlobGCManager:start() BlobGCManager successfully started.`); } async close() { if (this._status === Status.Closed) { this.logger.info(`BlobGCManager:close() BlobGCManager successfully closed. BlobGCManager is already in Closed status.`); return; } if (this._status !== Status.Running) { const error = new Error(`BlobGCManager:close() BlobGCManager cannot close, current manager is under ${Status[this._status]}`); this.logger.error(error.message); throw error; } this.logger.info(`BlobGCManager:close() Start closing BlobGCManager. Set status to Closing.`); this._status = Status.Closing; this.emitter.emit("abort"); return new Promise(resolve => { this.emitter.once("closed", () => { this.logger.info(`BlobGCManager:close() BlobGCManager successfully closed. Set status to Closed.`); this._status = Status.Closed; resolve(); }); }); } async markSweepLoop() { while (this._status === Status.Running) { this.logger.info(`BlobGCManager:markSweepLoop() Start next mark and sweep.`); const start = Date.now(); await this.markSweep(); const period = Date.now() - start; this.logger.info(`BlobGCManager:markSweepLoop() Mark and sweep finished, taken ${period}ms.`); if (this._status === Status.Running) { this.logger.info(`BlobGCManager:markSweepLoop() Sleep for ${this.gcIntervalInMS}ms.`); await this.sleep(this.gcIntervalInMS); } } } /** * Typical mark-sweep GC algorithm. * * @private * @returns {Promise<void>} * @memberof BlobGCManager */ async markSweep() { // mark this.logger.info(`BlobGCManager:markSweep() Get all extents.`); const allExtents = await this.getAllExtents(); this.logger.info(`BlobGCManager:markSweep() Got ${allExtents.size} extents.`); if (this._status !== Status.Running) { return; } this.logger.info(`BlobGCManager:markSweep() Get referred extents.`); const iter = this.referredExtentsProvider.iteratorExtents(); for (let res = await iter.next(); (res.done === false || res.value.length > 0) && this._status === Status.Running; res = await iter.next()) { const chunks = res.value; for (const chunk of chunks) { allExtents.delete(chunk); // TODO: Mark instead of removing from Set to improve performance } } this.logger.info(`BlobGCManager:markSweep() Got referred extents, unreferenced extents count is ${allExtents.size}.`); // sweep if (allExtents.size > 0) { this.logger.info(`BlobGCManager:markSweep() Try to delete ${allExtents.entries} unreferenced extents.`); const deletedCount = await this.extentStore.deleteExtents(allExtents); this.logger.info(`BlobGCManager:markSweep() Deleted unreferenced ${deletedCount} extents, after excluding active write extents.`); } } async getAllExtents() { const ids = new Set(); const iter = this.allExtentsProvider.iteratorExtents(); for (let res = await iter.next(); (res.done === false || res.value.length > 0) && this._status === Status.Running; res = await iter.next()) { for (const chunk of res.value) { ids.add(chunk); } } return ids; } async sleep(timeInMS) { if (timeInMS === 0) { return; } return new Promise(resolve => { let timer; const abortListener = () => { if (timer) { clearTimeout(timer); } this.emitter.removeListener("abort", abortListener); resolve(); }; // https://stackoverflow.com/questions/45802988/typescript-use-correct-version-of-settimeout-node-vs-window timer = setTimeout(() => { this.emitter.removeListener("abort", abortListener); resolve(); }, timeInMS); timer.unref(); this.emitter.on("abort", abortListener); }); } } exports.default = BlobGCManager; //# sourceMappingURL=BlobGCManager.js.map