path-reservations.js
5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"use strict";
// A path exclusive reservation system
// reserve([list, of, paths], fn)
// When the fn is first in line for all its paths, it
// is called with a cb that clears the reservation.
//
// Used by async unpack to avoid clobbering paths in use,
// while still allowing maximal safe parallelization.
Object.defineProperty(exports, "__esModule", { value: true });
exports.PathReservations = void 0;
const node_path_1 = require("node:path");
const normalize_unicode_js_1 = require("./normalize-unicode.js");
const strip_trailing_slashes_js_1 = require("./strip-trailing-slashes.js");
const platform = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform;
const isWindows = platform === 'win32';
// return a set of parent dirs for a given path
// '/a/b/c/d' -> ['/', '/a', '/a/b', '/a/b/c', '/a/b/c/d']
const getDirs = (path) => {
const dirs = path
.split('/')
.slice(0, -1)
.reduce((set, path) => {
const s = set.at(-1);
if (s !== undefined) {
path = (0, node_path_1.join)(s, path);
}
set.push(path || '/');
return set;
}, []);
return dirs;
};
class PathReservations {
// path => [function or Set]
// A Set object means a directory reservation
// A fn is a direct reservation on that path
#queues = new Map();
// fn => {paths:[path,...], dirs:[path, ...]}
#reservations = new Map();
// functions currently running
#running = new Set();
reserve(paths, fn) {
paths =
isWindows ?
['win32 parallelization disabled']
: paths.map(p => {
// don't need normPath, because we skip this entirely for windows
return (0, strip_trailing_slashes_js_1.stripTrailingSlashes)((0, node_path_1.join)((0, normalize_unicode_js_1.normalizeUnicode)(p)));
});
const dirs = new Set(paths.map(path => getDirs(path)).reduce((a, b) => a.concat(b)));
this.#reservations.set(fn, { dirs, paths });
for (const p of paths) {
const q = this.#queues.get(p);
if (!q) {
this.#queues.set(p, [fn]);
}
else {
q.push(fn);
}
}
for (const dir of dirs) {
const q = this.#queues.get(dir);
if (!q) {
this.#queues.set(dir, [new Set([fn])]);
}
else {
const l = q.at(-1);
if (l instanceof Set) {
l.add(fn);
}
else {
q.push(new Set([fn]));
}
}
}
return this.#run(fn);
}
// return the queues for each path the function cares about
// fn => {paths, dirs}
#getQueues(fn) {
const res = this.#reservations.get(fn);
/* c8 ignore start */
if (!res) {
throw new Error('function does not have any path reservations');
}
/* c8 ignore stop */
return {
paths: res.paths.map((path) => this.#queues.get(path)),
dirs: [...res.dirs].map(path => this.#queues.get(path)),
};
}
// check if fn is first in line for all its paths, and is
// included in the first set for all its dir queues
check(fn) {
const { paths, dirs } = this.#getQueues(fn);
return (paths.every(q => q && q[0] === fn) &&
dirs.every(q => q && q[0] instanceof Set && q[0].has(fn)));
}
// run the function if it's first in line and not already running
#run(fn) {
if (this.#running.has(fn) || !this.check(fn)) {
return false;
}
this.#running.add(fn);
fn(() => this.#clear(fn));
return true;
}
#clear(fn) {
if (!this.#running.has(fn)) {
return false;
}
const res = this.#reservations.get(fn);
/* c8 ignore start */
if (!res) {
throw new Error('invalid reservation');
}
/* c8 ignore stop */
const { paths, dirs } = res;
const next = new Set();
for (const path of paths) {
const q = this.#queues.get(path);
/* c8 ignore start */
if (!q || q?.[0] !== fn) {
continue;
}
/* c8 ignore stop */
const q0 = q[1];
if (!q0) {
this.#queues.delete(path);
continue;
}
q.shift();
if (typeof q0 === 'function') {
next.add(q0);
}
else {
for (const f of q0) {
next.add(f);
}
}
}
for (const dir of dirs) {
const q = this.#queues.get(dir);
const q0 = q?.[0];
/* c8 ignore next - type safety only */
if (!q || !(q0 instanceof Set))
continue;
if (q0.size === 1 && q.length === 1) {
this.#queues.delete(dir);
continue;
}
else if (q0.size === 1) {
q.shift();
// next one must be a function,
// or else the Set would've been reused
const n = q[0];
if (typeof n === 'function') {
next.add(n);
}
}
else {
q0.delete(fn);
}
}
this.#running.delete(fn);
next.forEach(fn => this.#run(fn));
return true;
}
}
exports.PathReservations = PathReservations;
//# sourceMappingURL=path-reservations.js.map