11cb0ef41Sopenharmony_ci// A path exclusive reservation system 21cb0ef41Sopenharmony_ci// reserve([list, of, paths], fn) 31cb0ef41Sopenharmony_ci// When the fn is first in line for all its paths, it 41cb0ef41Sopenharmony_ci// is called with a cb that clears the reservation. 51cb0ef41Sopenharmony_ci// 61cb0ef41Sopenharmony_ci// Used by async unpack to avoid clobbering paths in use, 71cb0ef41Sopenharmony_ci// while still allowing maximal safe parallelization. 81cb0ef41Sopenharmony_ci 91cb0ef41Sopenharmony_ciconst assert = require('assert') 101cb0ef41Sopenharmony_ciconst normalize = require('./normalize-unicode.js') 111cb0ef41Sopenharmony_ciconst stripSlashes = require('./strip-trailing-slashes.js') 121cb0ef41Sopenharmony_ciconst { join } = require('path') 131cb0ef41Sopenharmony_ci 141cb0ef41Sopenharmony_ciconst platform = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform 151cb0ef41Sopenharmony_ciconst isWindows = platform === 'win32' 161cb0ef41Sopenharmony_ci 171cb0ef41Sopenharmony_cimodule.exports = () => { 181cb0ef41Sopenharmony_ci // path => [function or Set] 191cb0ef41Sopenharmony_ci // A Set object means a directory reservation 201cb0ef41Sopenharmony_ci // A fn is a direct reservation on that path 211cb0ef41Sopenharmony_ci const queues = new Map() 221cb0ef41Sopenharmony_ci 231cb0ef41Sopenharmony_ci // fn => {paths:[path,...], dirs:[path, ...]} 241cb0ef41Sopenharmony_ci const reservations = new Map() 251cb0ef41Sopenharmony_ci 261cb0ef41Sopenharmony_ci // return a set of parent dirs for a given path 271cb0ef41Sopenharmony_ci // '/a/b/c/d' -> ['/', '/a', '/a/b', '/a/b/c', '/a/b/c/d'] 281cb0ef41Sopenharmony_ci const getDirs = path => { 291cb0ef41Sopenharmony_ci const dirs = path.split('/').slice(0, -1).reduce((set, path) => { 301cb0ef41Sopenharmony_ci if (set.length) { 311cb0ef41Sopenharmony_ci path = join(set[set.length - 1], path) 321cb0ef41Sopenharmony_ci } 331cb0ef41Sopenharmony_ci set.push(path || '/') 341cb0ef41Sopenharmony_ci return set 351cb0ef41Sopenharmony_ci }, []) 361cb0ef41Sopenharmony_ci return dirs 371cb0ef41Sopenharmony_ci } 381cb0ef41Sopenharmony_ci 391cb0ef41Sopenharmony_ci // functions currently running 401cb0ef41Sopenharmony_ci const running = new Set() 411cb0ef41Sopenharmony_ci 421cb0ef41Sopenharmony_ci // return the queues for each path the function cares about 431cb0ef41Sopenharmony_ci // fn => {paths, dirs} 441cb0ef41Sopenharmony_ci const getQueues = fn => { 451cb0ef41Sopenharmony_ci const res = reservations.get(fn) 461cb0ef41Sopenharmony_ci /* istanbul ignore if - unpossible */ 471cb0ef41Sopenharmony_ci if (!res) { 481cb0ef41Sopenharmony_ci throw new Error('function does not have any path reservations') 491cb0ef41Sopenharmony_ci } 501cb0ef41Sopenharmony_ci return { 511cb0ef41Sopenharmony_ci paths: res.paths.map(path => queues.get(path)), 521cb0ef41Sopenharmony_ci dirs: [...res.dirs].map(path => queues.get(path)), 531cb0ef41Sopenharmony_ci } 541cb0ef41Sopenharmony_ci } 551cb0ef41Sopenharmony_ci 561cb0ef41Sopenharmony_ci // check if fn is first in line for all its paths, and is 571cb0ef41Sopenharmony_ci // included in the first set for all its dir queues 581cb0ef41Sopenharmony_ci const check = fn => { 591cb0ef41Sopenharmony_ci const { paths, dirs } = getQueues(fn) 601cb0ef41Sopenharmony_ci return paths.every(q => q[0] === fn) && 611cb0ef41Sopenharmony_ci dirs.every(q => q[0] instanceof Set && q[0].has(fn)) 621cb0ef41Sopenharmony_ci } 631cb0ef41Sopenharmony_ci 641cb0ef41Sopenharmony_ci // run the function if it's first in line and not already running 651cb0ef41Sopenharmony_ci const run = fn => { 661cb0ef41Sopenharmony_ci if (running.has(fn) || !check(fn)) { 671cb0ef41Sopenharmony_ci return false 681cb0ef41Sopenharmony_ci } 691cb0ef41Sopenharmony_ci running.add(fn) 701cb0ef41Sopenharmony_ci fn(() => clear(fn)) 711cb0ef41Sopenharmony_ci return true 721cb0ef41Sopenharmony_ci } 731cb0ef41Sopenharmony_ci 741cb0ef41Sopenharmony_ci const clear = fn => { 751cb0ef41Sopenharmony_ci if (!running.has(fn)) { 761cb0ef41Sopenharmony_ci return false 771cb0ef41Sopenharmony_ci } 781cb0ef41Sopenharmony_ci 791cb0ef41Sopenharmony_ci const { paths, dirs } = reservations.get(fn) 801cb0ef41Sopenharmony_ci const next = new Set() 811cb0ef41Sopenharmony_ci 821cb0ef41Sopenharmony_ci paths.forEach(path => { 831cb0ef41Sopenharmony_ci const q = queues.get(path) 841cb0ef41Sopenharmony_ci assert.equal(q[0], fn) 851cb0ef41Sopenharmony_ci if (q.length === 1) { 861cb0ef41Sopenharmony_ci queues.delete(path) 871cb0ef41Sopenharmony_ci } else { 881cb0ef41Sopenharmony_ci q.shift() 891cb0ef41Sopenharmony_ci if (typeof q[0] === 'function') { 901cb0ef41Sopenharmony_ci next.add(q[0]) 911cb0ef41Sopenharmony_ci } else { 921cb0ef41Sopenharmony_ci q[0].forEach(fn => next.add(fn)) 931cb0ef41Sopenharmony_ci } 941cb0ef41Sopenharmony_ci } 951cb0ef41Sopenharmony_ci }) 961cb0ef41Sopenharmony_ci 971cb0ef41Sopenharmony_ci dirs.forEach(dir => { 981cb0ef41Sopenharmony_ci const q = queues.get(dir) 991cb0ef41Sopenharmony_ci assert(q[0] instanceof Set) 1001cb0ef41Sopenharmony_ci if (q[0].size === 1 && q.length === 1) { 1011cb0ef41Sopenharmony_ci queues.delete(dir) 1021cb0ef41Sopenharmony_ci } else if (q[0].size === 1) { 1031cb0ef41Sopenharmony_ci q.shift() 1041cb0ef41Sopenharmony_ci 1051cb0ef41Sopenharmony_ci // must be a function or else the Set would've been reused 1061cb0ef41Sopenharmony_ci next.add(q[0]) 1071cb0ef41Sopenharmony_ci } else { 1081cb0ef41Sopenharmony_ci q[0].delete(fn) 1091cb0ef41Sopenharmony_ci } 1101cb0ef41Sopenharmony_ci }) 1111cb0ef41Sopenharmony_ci running.delete(fn) 1121cb0ef41Sopenharmony_ci 1131cb0ef41Sopenharmony_ci next.forEach(fn => run(fn)) 1141cb0ef41Sopenharmony_ci return true 1151cb0ef41Sopenharmony_ci } 1161cb0ef41Sopenharmony_ci 1171cb0ef41Sopenharmony_ci const reserve = (paths, fn) => { 1181cb0ef41Sopenharmony_ci // collide on matches across case and unicode normalization 1191cb0ef41Sopenharmony_ci // On windows, thanks to the magic of 8.3 shortnames, it is fundamentally 1201cb0ef41Sopenharmony_ci // impossible to determine whether two paths refer to the same thing on 1211cb0ef41Sopenharmony_ci // disk, without asking the kernel for a shortname. 1221cb0ef41Sopenharmony_ci // So, we just pretend that every path matches every other path here, 1231cb0ef41Sopenharmony_ci // effectively removing all parallelization on windows. 1241cb0ef41Sopenharmony_ci paths = isWindows ? ['win32 parallelization disabled'] : paths.map(p => { 1251cb0ef41Sopenharmony_ci // don't need normPath, because we skip this entirely for windows 1261cb0ef41Sopenharmony_ci return stripSlashes(join(normalize(p))).toLowerCase() 1271cb0ef41Sopenharmony_ci }) 1281cb0ef41Sopenharmony_ci 1291cb0ef41Sopenharmony_ci const dirs = new Set( 1301cb0ef41Sopenharmony_ci paths.map(path => getDirs(path)).reduce((a, b) => a.concat(b)) 1311cb0ef41Sopenharmony_ci ) 1321cb0ef41Sopenharmony_ci reservations.set(fn, { dirs, paths }) 1331cb0ef41Sopenharmony_ci paths.forEach(path => { 1341cb0ef41Sopenharmony_ci const q = queues.get(path) 1351cb0ef41Sopenharmony_ci if (!q) { 1361cb0ef41Sopenharmony_ci queues.set(path, [fn]) 1371cb0ef41Sopenharmony_ci } else { 1381cb0ef41Sopenharmony_ci q.push(fn) 1391cb0ef41Sopenharmony_ci } 1401cb0ef41Sopenharmony_ci }) 1411cb0ef41Sopenharmony_ci dirs.forEach(dir => { 1421cb0ef41Sopenharmony_ci const q = queues.get(dir) 1431cb0ef41Sopenharmony_ci if (!q) { 1441cb0ef41Sopenharmony_ci queues.set(dir, [new Set([fn])]) 1451cb0ef41Sopenharmony_ci } else if (q[q.length - 1] instanceof Set) { 1461cb0ef41Sopenharmony_ci q[q.length - 1].add(fn) 1471cb0ef41Sopenharmony_ci } else { 1481cb0ef41Sopenharmony_ci q.push(new Set([fn])) 1491cb0ef41Sopenharmony_ci } 1501cb0ef41Sopenharmony_ci }) 1511cb0ef41Sopenharmony_ci 1521cb0ef41Sopenharmony_ci return run(fn) 1531cb0ef41Sopenharmony_ci } 1541cb0ef41Sopenharmony_ci 1551cb0ef41Sopenharmony_ci return { check, reserve } 1561cb0ef41Sopenharmony_ci} 157