UNPKG

igir

Version:

🕹 A zero-setup ROM collection manager that sorts, filters, extracts or archives, patches, and reports on collections of any size on any OS.

59 lines (58 loc) • 2.65 kB
import path from 'node:path'; import Defaults from '../globals/defaults.js'; import ElasticSemaphore from './elasticSemaphore.js'; import KeyedMutex from './keyedMutex.js'; import MappableSemaphore from './mappableSemaphore.js'; /** * A wrapper for an `async-mutex` {@link Semaphore} that limits how many writes can be in progress * at once. To be used by {@link CandidateWriter}. */ export default class CandidateWriterSemaphore { mappableSemaphore; outputPathsMutex = new KeyedMutex(1000); // WARN(cemmer): there is an undocumented semaphore max value that can be used, the full // 4,700,372,992 bytes of a DVD+R will cause runExclusive() to never run or return. filesizeSemaphore = new ElasticSemaphore(Defaults.MAX_READ_WRITE_CONCURRENT_KILOBYTES); _openLocks = 0; constructor(threads) { this.mappableSemaphore = new MappableSemaphore(threads); } /** * Run some {@link callback}. for every {@link candidates}. */ async map(candidates, callback) { const candidatesSorted = candidates.sort((a, b) => { // First, prefer candidates with fewer files if (a.getRomsWithFiles().length !== b.getRomsWithFiles().length) { return a.getRomsWithFiles().length - b.getRomsWithFiles().length; } // Otherwise, stable sort by name return a.getName().localeCompare(b.getName()); }); // First, limit writes by the global max number of threads allowed return this.mappableSemaphore.map(candidatesSorted, async (candidate) => { // Then, restrict concurrent writes to the same output paths const outputFilePaths = candidate .getRomsWithFiles() .map((romWithFiles) => path.normalize(romWithFiles.getOutputFile().getFilePath())); return await this.outputPathsMutex.runExclusiveForKeys(outputFilePaths, async () => { // Then, limit writing too much data to one disk const totalKilobytes = candidate .getRomsWithFiles() .reduce((sum, romWithFiles) => sum + romWithFiles.getInputFile().getSize(), 0) / 1024; return await this.filesizeSemaphore.runExclusive(async () => { this._openLocks += 1; const result = await callback(candidate); this._openLocks -= 1; return result; }, totalKilobytes); }); }); } /** * Get the number of currently open/acquired locks. */ openLocks() { return this._openLocks; } }