Team Ai
Datasetpublic

codekingpro/portable-devtools

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