@jsii/kernel 1.86.1 → 1.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,12 +13,32 @@ export declare class Entry {
13
13
  constructor(path: string);
14
14
  get atime(): Date;
15
15
  get pathExists(): boolean;
16
+ /**
17
+ * Whether the directory has been completely written
18
+ *
19
+ * The presence of the marker file is a signal that we can skip trying to lock the directory.
20
+ */
21
+ get isComplete(): boolean;
22
+ /**
23
+ * Retrieve an entry from the cache
24
+ *
25
+ * If the entry doesn't exist yet, use 'cb' to produce the file contents.
26
+ */
27
+ retrieve(cb: (path: string) => void): {
28
+ path: string;
29
+ cache: 'hit' | 'miss';
30
+ };
16
31
  lock<T>(cb: (entry: LockedEntry) => T): T;
17
32
  read(file: string): Buffer | undefined;
18
33
  }
19
34
  export interface LockedEntry {
20
35
  delete(): void;
21
36
  write(name: string, data: Buffer): void;
22
- touch(): void;
37
+ /**
38
+ * Mark the entry has having been completed
39
+ *
40
+ * The modification time of this file is used for cleanup.
41
+ */
42
+ markComplete(): void;
23
43
  }
24
44
  //# sourceMappingURL=disk-cache.d.ts.map
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _DiskCache_instances, _a, _DiskCache_CACHE, _DiskCache_root, _DiskCache_entries, _Entry_instances, _Entry_lockFile_get, _Entry_markerFile_get;
13
+ var _DiskCache_instances, _a, _DiskCache_CACHE, _DiskCache_root, _DiskCache_entries, _Entry_instances, _Entry_lockFile_get, _Entry_markerFile_get, _Entry_touchMarkerFile;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.Entry = exports.DiskCache = void 0;
16
16
  const fs_1 = require("fs");
@@ -128,9 +128,56 @@ class Entry {
128
128
  get pathExists() {
129
129
  return (0, fs_1.existsSync)(this.path);
130
130
  }
131
+ /**
132
+ * Whether the directory has been completely written
133
+ *
134
+ * The presence of the marker file is a signal that we can skip trying to lock the directory.
135
+ */
136
+ get isComplete() {
137
+ return (0, fs_1.existsSync)(__classPrivateFieldGet(this, _Entry_instances, "a", _Entry_markerFile_get));
138
+ }
139
+ /**
140
+ * Retrieve an entry from the cache
141
+ *
142
+ * If the entry doesn't exist yet, use 'cb' to produce the file contents.
143
+ */
144
+ retrieve(cb) {
145
+ // If the marker file already exists, update its timestamp and immediately return.
146
+ // We don't even try to lock.
147
+ if (this.isComplete) {
148
+ __classPrivateFieldGet(this, _Entry_instances, "m", _Entry_touchMarkerFile).call(this);
149
+ return { path: this.path, cache: 'hit' };
150
+ }
151
+ let cache = 'miss';
152
+ this.lock((lock) => {
153
+ // While we all fought to acquire the lock, someone else might have completed already.
154
+ if (this.isComplete) {
155
+ cache = 'hit';
156
+ return;
157
+ }
158
+ // !!!IMPORTANT!!!
159
+ // Extract directly into the final target directory, as certain antivirus
160
+ // software configurations on Windows will make a `renameSync` operation
161
+ // fail with EPERM until the files have been fully analyzed.
162
+ (0, fs_1.mkdirSync)(this.path, { recursive: true });
163
+ try {
164
+ cb(this.path);
165
+ }
166
+ catch (error) {
167
+ (0, fs_1.rmSync)(this.path, { force: true, recursive: true });
168
+ throw error;
169
+ }
170
+ lock.markComplete();
171
+ });
172
+ return { path: this.path, cache };
173
+ }
131
174
  lock(cb) {
132
175
  (0, fs_1.mkdirSync)((0, path_1.dirname)(this.path), { recursive: true });
133
- (0, lockfile_1.lockSync)(__classPrivateFieldGet(this, _Entry_instances, "a", _Entry_lockFile_get), { retries: 12, stale: 5000 });
176
+ lockSyncWithWait(__classPrivateFieldGet(this, _Entry_instances, "a", _Entry_lockFile_get), {
177
+ retries: 12,
178
+ // Extracting the largest tarball takes ~5s
179
+ stale: 10000,
180
+ });
134
181
  let disposed = false;
135
182
  try {
136
183
  return cb({
@@ -147,19 +194,11 @@ class Entry {
147
194
  (0, fs_1.mkdirSync)((0, path_1.dirname)((0, path_1.join)(this.path, name)), { recursive: true });
148
195
  (0, fs_1.writeFileSync)((0, path_1.join)(this.path, name), content);
149
196
  },
150
- touch: () => {
197
+ markComplete: () => {
151
198
  if (disposed) {
152
199
  throw new Error(`Cannot touch ${this.path} once the lock block was returned!`);
153
200
  }
154
- if (this.pathExists) {
155
- if ((0, fs_1.existsSync)(__classPrivateFieldGet(this, _Entry_instances, "a", _Entry_markerFile_get))) {
156
- const now = new Date();
157
- (0, fs_1.utimesSync)(__classPrivateFieldGet(this, _Entry_instances, "a", _Entry_markerFile_get), now, now);
158
- }
159
- else {
160
- (0, fs_1.writeFileSync)(__classPrivateFieldGet(this, _Entry_instances, "a", _Entry_markerFile_get), '');
161
- }
162
- }
201
+ __classPrivateFieldGet(this, _Entry_instances, "m", _Entry_touchMarkerFile).call(this);
163
202
  },
164
203
  });
165
204
  }
@@ -185,6 +224,19 @@ _Entry_instances = new WeakSet(), _Entry_lockFile_get = function _Entry_lockFile
185
224
  return `${this.path}.lock`;
186
225
  }, _Entry_markerFile_get = function _Entry_markerFile_get() {
187
226
  return (0, path_1.join)(this.path, MARKER_FILE_NAME);
227
+ }, _Entry_touchMarkerFile = function _Entry_touchMarkerFile() {
228
+ if (this.pathExists) {
229
+ try {
230
+ const now = new Date();
231
+ (0, fs_1.utimesSync)(__classPrivateFieldGet(this, _Entry_instances, "a", _Entry_markerFile_get), now, now);
232
+ }
233
+ catch (e) {
234
+ if (e.code !== 'ENOENT') {
235
+ throw e;
236
+ }
237
+ (0, fs_1.writeFileSync)(__classPrivateFieldGet(this, _Entry_instances, "a", _Entry_markerFile_get), '');
238
+ }
239
+ }
188
240
  };
189
241
  function* directoriesUnder(root, recursive = false, ignoreErrors = true) {
190
242
  for (const file of (0, fs_1.readdirSync)(root)) {
@@ -205,4 +257,45 @@ function* directoriesUnder(root, recursive = false, ignoreErrors = true) {
205
257
  }
206
258
  }
207
259
  }
260
+ /**
261
+ * We must use 'lockSync', but that doesn't support waiting (because waiting is only supported for async APIs)
262
+ * so we have to build our own looping locker with waits
263
+ */
264
+ function lockSyncWithWait(path, options) {
265
+ var _b;
266
+ let retries = (_b = options.retries) !== null && _b !== void 0 ? _b : 0;
267
+ let sleep = 100;
268
+ // eslint-disable-next-line no-constant-condition
269
+ while (true) {
270
+ try {
271
+ (0, lockfile_1.lockSync)(path, {
272
+ retries: 0,
273
+ stale: options.stale,
274
+ });
275
+ return;
276
+ }
277
+ catch (e) {
278
+ if (retries === 0) {
279
+ throw e;
280
+ }
281
+ retries--;
282
+ if (e.code === 'EEXIST') {
283
+ // Most common case, needs longest sleep. Randomize the herd.
284
+ sleepSync(Math.floor(Math.random() * sleep));
285
+ sleep *= 1.5;
286
+ }
287
+ else {
288
+ sleepSync(5);
289
+ }
290
+ }
291
+ }
292
+ }
293
+ /**
294
+ * Abuse Atomics.wait() to come up with a sync sleep
295
+ *
296
+ * We must use a sync sleep because all of jsii is sync.
297
+ */
298
+ function sleepSync(ms) {
299
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
300
+ }
208
301
  //# sourceMappingURL=disk-cache.js.map
package/lib/link.d.ts CHANGED
@@ -1,9 +1,14 @@
1
1
  /**
2
- * Creates directories containing hard links if possible, and falls back on
3
- * copy otherwise.
2
+ * Link existing to destination directory
4
3
  *
5
- * @param existing is the original file or directory to link.
6
- * @param destination is the new file or directory to create.
4
+ * - If Node has been started with a module resolution strategy that does not
5
+ * resolve symlinks (so peerDependencies can be found), use symlinking.
6
+ * Symlinking may fail on Windows for non-Admin users.
7
+ * - If not symlinking the entire directory, crawl the directory tree and
8
+ * hardlink all files (if possible), copying them if not.
9
+ *
10
+ * @param existingRoot is the original file or directory to link.
11
+ * @param destinationRoot is the new file or directory to create.
7
12
  */
8
- export declare function link(existing: string, destination: string): void;
13
+ export declare function link(existingRoot: string, destinationRoot: string): void;
9
14
  //# sourceMappingURL=link.d.ts.map
package/lib/link.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.link = void 0;
4
4
  const fs_1 = require("fs");
5
+ const os = require("os");
5
6
  const path_1 = require("path");
6
7
  /**
7
8
  * If `node` is started with `--preserve-symlinks`, the module loaded will
@@ -10,31 +11,49 @@ const path_1 = require("path");
10
11
  */
11
12
  const PRESERVE_SYMLINKS = process.execArgv.includes('--preserve-symlinks');
12
13
  /**
13
- * Creates directories containing hard links if possible, and falls back on
14
- * copy otherwise.
14
+ * Link existing to destination directory
15
15
  *
16
- * @param existing is the original file or directory to link.
17
- * @param destination is the new file or directory to create.
16
+ * - If Node has been started with a module resolution strategy that does not
17
+ * resolve symlinks (so peerDependencies can be found), use symlinking.
18
+ * Symlinking may fail on Windows for non-Admin users.
19
+ * - If not symlinking the entire directory, crawl the directory tree and
20
+ * hardlink all files (if possible), copying them if not.
21
+ *
22
+ * @param existingRoot is the original file or directory to link.
23
+ * @param destinationRoot is the new file or directory to create.
18
24
  */
19
- function link(existing, destination) {
25
+ function link(existingRoot, destinationRoot) {
26
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(destinationRoot), { recursive: true });
20
27
  if (PRESERVE_SYMLINKS) {
21
- (0, fs_1.mkdirSync)((0, path_1.dirname)(destination), { recursive: true });
22
- (0, fs_1.symlinkSync)(existing, destination);
23
- return;
24
- }
25
- const stat = (0, fs_1.statSync)(existing);
26
- if (!stat.isDirectory()) {
27
28
  try {
28
- (0, fs_1.linkSync)(existing, destination);
29
+ (0, fs_1.symlinkSync)(existingRoot, destinationRoot);
30
+ return;
29
31
  }
30
- catch {
31
- (0, fs_1.copyFileSync)(existing, destination);
32
+ catch (e) {
33
+ // On Windows, non-Admin users aren't allowed to create symlinks. In that case, fall back to the copying workflow.
34
+ const winNoSymlink = e.code === 'EPERM' && os.platform() === 'win32';
35
+ if (!winNoSymlink) {
36
+ throw e;
37
+ }
32
38
  }
33
- return;
34
39
  }
35
- (0, fs_1.mkdirSync)(destination, { recursive: true });
36
- for (const file of (0, fs_1.readdirSync)(existing)) {
37
- link((0, path_1.join)(existing, file), (0, path_1.join)(destination, file));
40
+ // Fall back to the slow method
41
+ recurse(existingRoot, destinationRoot);
42
+ function recurse(existing, destination) {
43
+ const stat = (0, fs_1.statSync)(existing);
44
+ if (!stat.isDirectory()) {
45
+ try {
46
+ (0, fs_1.linkSync)(existing, destination);
47
+ }
48
+ catch {
49
+ (0, fs_1.copyFileSync)(existing, destination);
50
+ }
51
+ return;
52
+ }
53
+ (0, fs_1.mkdirSync)(destination, { recursive: true });
54
+ for (const file of (0, fs_1.readdirSync)(existing)) {
55
+ recurse((0, path_1.join)(existing, file), (0, path_1.join)(destination, file));
56
+ }
38
57
  }
39
58
  }
40
59
  exports.link = link;
@@ -28,38 +28,27 @@ function extract(file, outDir, options, ...comments) {
28
28
  }
29
29
  }
30
30
  exports.extract = extract;
31
+ /**
32
+ * Extract the tarball into a cached directory, symlink that directory into the target location
33
+ */
31
34
  function extractViaCache(file, outDir, options = {}, ...comments) {
32
35
  var _a;
33
36
  const cacheRoot = (_a = process.env.JSII_RUNTIME_PACKAGE_CACHE_ROOT) !== null && _a !== void 0 ? _a : (0, default_cache_root_1.defaultCacheRoot)();
34
37
  const dirCache = disk_cache_1.DiskCache.inDirectory(cacheRoot);
35
38
  const entry = dirCache.entryFor(file, ...comments);
36
- const { path, cache } = entry.lock((lock) => {
37
- let cache = 'hit';
38
- if (!entry.pathExists) {
39
- // !!!IMPORTANT!!!
40
- // Extract directly into the final target directory, as certain antivirus
41
- // software configurations on Windows will make a `renameSync` operation
42
- // fail with EPERM until the files have been fully analyzed.
43
- (0, fs_1.mkdirSync)(entry.path, { recursive: true });
44
- try {
45
- untarInto({
46
- ...options,
47
- cwd: entry.path,
48
- file,
49
- });
50
- }
51
- catch (error) {
52
- (0, fs_1.rmSync)(entry.path, { force: true, recursive: true });
53
- throw error;
54
- }
55
- cache = 'miss';
56
- }
57
- lock.touch();
58
- return { path: entry.path, cache };
39
+ const { path, cache } = entry.retrieve((path) => {
40
+ untarInto({
41
+ ...options,
42
+ cwd: path,
43
+ file,
44
+ });
59
45
  });
60
46
  (0, link_1.link)(path, outDir);
61
47
  return { cache };
62
48
  }
49
+ /**
50
+ * Extract directory into the target location
51
+ */
63
52
  function extractToOutDir(file, cwd, options = {}) {
64
53
  // The output directory must already exist...
65
54
  (0, fs_1.mkdirSync)(cwd, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsii/kernel",
3
- "version": "1.86.1",
3
+ "version": "1.87.0",
4
4
  "description": "kernel for jsii execution environment",
5
5
  "license": "Apache-2.0",
6
6
  "author": {
@@ -31,19 +31,19 @@
31
31
  "package": "package-js"
32
32
  },
33
33
  "dependencies": {
34
- "@jsii/spec": "^1.86.1",
34
+ "@jsii/spec": "^1.87.0",
35
35
  "fs-extra": "^10.1.0",
36
36
  "lockfile": "^1.0.4",
37
37
  "tar": "^6.1.15"
38
38
  },
39
39
  "devDependencies": {
40
- "@scope/jsii-calc-base": "^1.86.1",
41
- "@scope/jsii-calc-lib": "^1.86.1",
40
+ "@scope/jsii-calc-base": "^1.87.0",
41
+ "@scope/jsii-calc-lib": "^1.87.0",
42
42
  "@types/fs-extra": "^9.0.13",
43
43
  "@types/lockfile": "^1.0.2",
44
44
  "@types/tar": "^6.1.5",
45
45
  "jest-expect-message": "^1.1.3",
46
- "jsii-build-tools": "^1.86.1",
46
+ "jsii-build-tools": "^1.87.0",
47
47
  "jsii-calc": "^3.20.120"
48
48
  }
49
49
  }