b0a249cd07c5a7fff27d9d2358ec1470ddba328f0882a2f596bf797af5a83635cf85030a34e6b2acb323f38a318a5445147e1be2bbd9c33cb652458b0ababa 5.6 KB

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