@tinycloud/operations 0.6.1-beta.1 → 0.6.1-beta.11

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.
@@ -3759,14 +3759,18 @@ var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIE
3759
3759
  var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
3760
3760
  var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
3761
3761
  function processWideContext(key) {
3762
- var _a4;
3762
+ var _a5;
3763
3763
  const registry = globalThis;
3764
- return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new import_node_async_hooks.AsyncLocalStorage());
3764
+ return registry[_a5 = Symbol.for(key)] ?? (registry[_a5] = new import_node_async_hooks.AsyncLocalStorage());
3765
3765
  }
3766
3766
  var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
3767
3767
  var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
3768
+ var pinnedProfiles = processWideContext("tinycloud.operations.pinnedProfiles.v2");
3769
+ var pinnedHomes = processWideContext("tinycloud.operations.pinnedHomes.v2");
3768
3770
  var _a2;
3769
- var processInstanceId = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a2] = (0, import_node_crypto.randomUUID)());
3771
+ var canonicalHomes = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.canonicalHomes.v1")] ?? (globalThis[_a2] = /* @__PURE__ */ new Map());
3772
+ var _a3;
3773
+ var processInstanceId = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a3] = (0, import_node_crypto.randomUUID)());
3770
3774
  var cachedProcessOrigin;
3771
3775
  function processOrigin() {
3772
3776
  if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
@@ -3944,9 +3948,14 @@ function tinycloudHomePath() {
3944
3948
  function profilesPath() {
3945
3949
  return (0, import_node_path.join)(tinycloudHomePath(), "profiles");
3946
3950
  }
3947
- function profilePath(profile) {
3951
+ function namedProfilePath(profile) {
3948
3952
  return (0, import_node_path.join)(profilesPath(), validateProfileName(profile));
3949
3953
  }
3954
+ function profilePath(profile) {
3955
+ const named = namedProfilePath(profile);
3956
+ const pinned = pinnedProfiles.getStore()?.get(named);
3957
+ return pinned ? livePinnedPath(profile, pinned) : named;
3958
+ }
3950
3959
  function profileConfigPath(profile) {
3951
3960
  return (0, import_node_path.join)(profilePath(profile), "profile.json");
3952
3961
  }
@@ -3960,10 +3969,12 @@ function profileStoreMetadataPath(profile, store) {
3960
3969
  return `${profileStorePath(profile, store)}.metadata.json`;
3961
3970
  }
3962
3971
  function profileLockPath(profile) {
3963
- return (0, import_node_path.join)(profilePath(profile), ".lock");
3972
+ return (0, import_node_path.join)(namedProfilePath(profile), ".lock");
3964
3973
  }
3965
3974
  function profileTurnLockPath(profile) {
3966
- return (0, import_node_path.join)(tinycloudHomePath(), "profile-locks", validateProfileName(profile));
3975
+ const home = tinycloudHomePath();
3976
+ const pinned = pinnedHomes.getStore()?.get(home);
3977
+ return (0, import_node_path.join)(pinned ? livePinnedPath(profile, pinned) : home, "profile-locks", validateProfileName(profile));
3967
3978
  }
3968
3979
  async function readJson(filePath) {
3969
3980
  try {
@@ -3973,6 +3984,44 @@ async function readJson(filePath) {
3973
3984
  throw error;
3974
3985
  }
3975
3986
  }
3987
+ async function readProfileJson(profile, filePath) {
3988
+ const before = process.platform !== "linux" ? await (0, import_promises.lstat)(filePath, { bigint: true }).catch((error) => {
3989
+ if (error.code === "ENOENT") return null;
3990
+ throw error;
3991
+ }) : null;
3992
+ if (process.platform !== "linux" && !before) return null;
3993
+ if (before && !before.isFile()) throw new ProfileSymlinkError(profile);
3994
+ const dev = before && exactFileId(before.dev);
3995
+ const ino = before && exactFileId(before.ino);
3996
+ try {
3997
+ const handle = await (0, import_promises.open)(filePath, import_node_fs.constants.O_RDONLY | (import_node_fs.constants.O_NOFOLLOW ?? 0));
3998
+ try {
3999
+ if (before) {
4000
+ const opened = await handle.stat({ bigint: true });
4001
+ if (!opened.isFile() || dev === null || ino === null || dev !== exactFileId(opened.dev) || ino !== exactFileId(opened.ino)) {
4002
+ throw new ProfileSymlinkError(profile, "changed while opening its file");
4003
+ }
4004
+ }
4005
+ const contents = await handle.readFile("utf8");
4006
+ if (before) {
4007
+ const named = await (0, import_promises.lstat)(filePath, { bigint: true }).catch((error) => {
4008
+ if (error.code === "ENOENT") return null;
4009
+ throw error;
4010
+ });
4011
+ if (!named?.isFile() || dev !== exactFileId(named.dev) || ino !== exactFileId(named.ino)) {
4012
+ throw new ProfileSymlinkError(profile, "changed while reading its file");
4013
+ }
4014
+ }
4015
+ return JSON.parse(contents);
4016
+ } finally {
4017
+ await handle.close();
4018
+ }
4019
+ } catch (error) {
4020
+ if (isErrno(error, "ENOENT")) return null;
4021
+ if (isErrno(error, "ELOOP") || isErrno(error, "ENOTDIR")) throw new ProfileSymlinkError(profile);
4022
+ throw error;
4023
+ }
4024
+ }
3976
4025
  async function writeJsonAtomic(filePath, value) {
3977
4026
  await replaceJson(filePath, value, false);
3978
4027
  }
@@ -4006,49 +4055,203 @@ async function writeFileDurably(path, contents) {
4006
4055
  }
4007
4056
  }
4008
4057
  async function readSession(profile) {
4009
- await readStoreMetadata(profile, "session");
4010
- return readJson(sessionPath(profile));
4058
+ return withPinnedProfileDirectory(profile, async (directory) => {
4059
+ if (!directory) return null;
4060
+ await readStoreMetadata(profile, "session");
4061
+ return readProfileJson(profile, sessionPath(profile));
4062
+ });
4011
4063
  }
4012
4064
  async function readAdditionalDelegations(profile) {
4013
4065
  return (await readProfileStore(profile, "additional-delegations")).records;
4014
4066
  }
4015
4067
  async function readStoreMetadata(profile, store) {
4016
- const metadata = await readJson(profileStoreMetadataPath(profile, store));
4017
- if (metadata === null) return { formatVersion: 1 };
4018
- if (typeof metadata !== "object" || metadata.formatVersion !== 1) {
4019
- throw new TypeError(`Unsupported store format for "${store}".`);
4020
- }
4021
- return metadata;
4068
+ return withPinnedProfileDirectory(profile, async (directory) => {
4069
+ const metadata = directory ? await readProfileJson(profile, profileStoreMetadataPath(profile, store)) : null;
4070
+ if (metadata === null) return { formatVersion: 1 };
4071
+ if (typeof metadata !== "object" || metadata.formatVersion !== 1) {
4072
+ throw new TypeError(`Unsupported store format for "${store}".`);
4073
+ }
4074
+ return metadata;
4075
+ });
4022
4076
  }
4023
4077
  async function readProfileStore(profile, store) {
4024
- const [metadata, raw] = await Promise.all([
4025
- readStoreMetadata(profile, store),
4026
- readJson(profileStorePath(profile, store))
4027
- ]);
4028
- return {
4029
- formatVersion: metadata.formatVersion,
4030
- records: Array.isArray(raw) ? raw : []
4031
- };
4078
+ return withPinnedProfileDirectory(profile, async (directory) => {
4079
+ if (!directory) return { formatVersion: 1, records: [] };
4080
+ const [metadata, raw] = await Promise.all([
4081
+ readStoreMetadata(profile, store),
4082
+ readProfileJson(profile, profileStorePath(profile, store))
4083
+ ]);
4084
+ return {
4085
+ formatVersion: metadata.formatVersion,
4086
+ records: Array.isArray(raw) ? raw : []
4087
+ };
4088
+ });
4032
4089
  }
4033
- async function withProfileLock(profile, action, options = {}) {
4034
- const normalizedProfile = validateProfileName(profile);
4035
- const lockPath = profileLockPath(normalizedProfile);
4036
- const held = (heldProfileLocks.getStore() ?? []).filter((ownership2) => ownership2.active);
4037
- if (held.some((ownership2) => ownership2.lockPath === lockPath)) return action();
4038
- const acquired = await acquireProfileLock(normalizedProfile, options);
4039
- const ownership = {
4040
- lockPath,
4041
- turnSlot: acquired.turnSlot,
4042
- deletedWhileWaiting: acquired.deletedWhileWaiting,
4043
- active: true
4044
- };
4090
+ var ProfileSymlinkError = class extends Error {
4091
+ constructor(profile, detail = "must be a real directory, not a symlink or another file type") {
4092
+ super(`Profile "${profile}" ${detail}.`);
4093
+ this.code = "PROFILE_SYMLINK";
4094
+ this.name = "ProfileSymlinkError";
4095
+ }
4096
+ };
4097
+ function exactFileId(value) {
4098
+ if (typeof value === "bigint") return value >= 0n ? value : null;
4099
+ return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : null;
4100
+ }
4101
+ function isPinnedLive(pinned) {
4102
+ if (!pinned.active) return false;
4045
4103
  try {
4046
- return await heldProfileLocks.run([...held, ownership], action);
4104
+ const stats = (0, import_node_fs.fstatSync)(pinned.fd, { bigint: true });
4105
+ return exactFileId(stats.dev) === pinned.dev && exactFileId(stats.ino) === pinned.ino;
4106
+ } catch {
4107
+ return false;
4108
+ }
4109
+ }
4110
+ function livePinnedPath(profile, pinned) {
4111
+ if (isPinnedLive(pinned)) return pinned.path;
4112
+ throw new ProfileSymlinkError(profile, "its pinned directory is no longer active or changed during use");
4113
+ }
4114
+ async function withPinnedProfileDirectory(profile, action, options = {}) {
4115
+ const named = namedProfilePath(profile);
4116
+ const existing = pinnedProfiles.getStore()?.get(named);
4117
+ if (existing) return action(livePinnedPath(profile, existing));
4118
+ if (process.platform !== "linux") {
4119
+ if (options.create) await (0, import_promises.mkdir)(named, { recursive: true, mode: PRIVATE_DIR_MODE });
4120
+ const stats = await (0, import_promises.lstat)(named).catch((error) => {
4121
+ if (error.code === "ENOENT") return null;
4122
+ throw error;
4123
+ });
4124
+ if (!stats) return action(null);
4125
+ if (!stats.isDirectory() || stats.isSymbolicLink()) throw new ProfileSymlinkError(profile);
4126
+ return action(named);
4127
+ }
4128
+ const home = tinycloudHomePath();
4129
+ const handles = [];
4130
+ const fdPath = (fd) => `/proc/self/fd/${fd}`;
4131
+ const flags = import_node_fs.constants.O_RDONLY | import_node_fs.constants.O_DIRECTORY | import_node_fs.constants.O_NOFOLLOW;
4132
+ try {
4133
+ if (options.create) await (0, import_promises.mkdir)(home, { recursive: true, mode: PRIVATE_DIR_MODE });
4134
+ const resolved = canonicalHomes.get(home) ?? await (0, import_promises.realpath)(home).catch((error) => {
4135
+ if (error.code === "ENOENT" && !options.create) return null;
4136
+ if (error.code === "ELOOP" || error.code === "ENOTDIR") throw new ProfileSymlinkError(profile);
4137
+ throw error;
4138
+ });
4139
+ if (!resolved) return await action(null);
4140
+ const root = canonicalHomes.get(home) ?? resolved;
4141
+ canonicalHomes.set(home, root);
4142
+ const segments = [root, "profiles", validateProfileName(profile)];
4143
+ for (let index = 0; index < segments.length; index++) {
4144
+ const path = handles.length ? (0, import_node_path.join)(fdPath(handles[handles.length - 1].fd), segments[index]) : segments[index];
4145
+ if (options.create && handles.length) {
4146
+ await (0, import_promises.mkdir)(path, { mode: PRIVATE_DIR_MODE }).catch((error) => {
4147
+ if (error.code !== "EEXIST") throw error;
4148
+ });
4149
+ }
4150
+ const handle = await (0, import_promises.open)(path, index === 1 ? flags & ~import_node_fs.constants.O_NOFOLLOW : flags).catch((error) => {
4151
+ if (error.code === "ENOENT" && !options.create) return null;
4152
+ if (error.code === "ELOOP" || error.code === "ENOTDIR") throw new ProfileSymlinkError(profile);
4153
+ throw error;
4154
+ });
4155
+ if (!handle) return await action(null);
4156
+ handles.push(handle);
4157
+ if (options.create) await handle.chmod(PRIVATE_DIR_MODE);
4158
+ }
4159
+ const opened = handles[2];
4160
+ const [actual, namedStats] = await Promise.all([opened.stat({ bigint: true }), (0, import_promises.lstat)(named, { bigint: true })]);
4161
+ const dev = exactFileId(actual.dev);
4162
+ const ino = exactFileId(actual.ino);
4163
+ if (!namedStats.isDirectory() || dev === null || ino === null || dev !== exactFileId(namedStats.dev) || ino !== exactFileId(namedStats.ino)) {
4164
+ throw new ProfileSymlinkError(profile, "changed while opening its directory");
4165
+ }
4166
+ const directory = fdPath(opened.fd);
4167
+ const pinned = {
4168
+ path: directory,
4169
+ fd: opened.fd,
4170
+ dev,
4171
+ ino,
4172
+ active: true
4173
+ };
4174
+ const homeHandle = handles[0];
4175
+ const homeStats = await homeHandle.stat({ bigint: true });
4176
+ const homeDev = exactFileId(homeStats.dev);
4177
+ const homeIno = exactFileId(homeStats.ino);
4178
+ if (homeDev === null || homeIno === null) throw new ProfileSymlinkError(profile, "has an unverified home directory identity");
4179
+ const homePinned = {
4180
+ path: fdPath(homeHandle.fd),
4181
+ fd: homeHandle.fd,
4182
+ dev: homeDev,
4183
+ ino: homeIno,
4184
+ active: true
4185
+ };
4186
+ const context = new Map(pinnedProfiles.getStore());
4187
+ context.set(named, pinned);
4188
+ const homes = new Map(pinnedHomes.getStore());
4189
+ homes.set(home, homePinned);
4190
+ try {
4191
+ return await pinnedHomes.run(homes, () => pinnedProfiles.run(context, () => action(directory)));
4192
+ } finally {
4193
+ pinned.active = false;
4194
+ homePinned.active = false;
4195
+ }
4047
4196
  } finally {
4048
- ownership.active = false;
4049
- await acquired.release();
4050
- if (acquired.deletedWhileWaiting) await (0, import_promises.rmdir)(profilePath(normalizedProfile)).catch(() => void 0);
4197
+ for (const handle of handles.reverse()) await handle.close();
4198
+ }
4199
+ }
4200
+ async function assertRealProfileDirectory(profile) {
4201
+ const named = namedProfilePath(profile);
4202
+ const pinned = pinnedProfiles.getStore()?.get(named);
4203
+ if (pinned) {
4204
+ livePinnedPath(profile, pinned);
4205
+ return;
4051
4206
  }
4207
+ const stats = await (0, import_promises.lstat)(named).catch((error) => {
4208
+ if (error.code === "ENOENT") return null;
4209
+ throw error;
4210
+ });
4211
+ if (stats && (!stats.isDirectory() || stats.isSymbolicLink())) throw new ProfileSymlinkError(profile);
4212
+ }
4213
+ async function withProfileLock(profile, action, options = {}) {
4214
+ const normalizedProfile = validateProfileName(profile);
4215
+ const lockPath = profileLockPath(normalizedProfile);
4216
+ const held = (heldProfileLocks.getStore() ?? []).filter((ownership) => ownership.active);
4217
+ if (held.some((ownership) => ownership.lockPath === lockPath)) {
4218
+ await assertRealProfileDirectory(normalizedProfile);
4219
+ return action();
4220
+ }
4221
+ const named = namedProfilePath(normalizedProfile);
4222
+ const inherited = pinnedProfiles.getStore()?.get(named);
4223
+ if (inherited && !isPinnedLive(inherited)) {
4224
+ const profiles = new Map(pinnedProfiles.getStore());
4225
+ profiles.delete(named);
4226
+ const homes = new Map(pinnedHomes.getStore());
4227
+ homes.delete(tinycloudHomePath());
4228
+ return pinnedHomes.run(homes, () => pinnedProfiles.run(profiles, () => withProfileLock(normalizedProfile, action, options)));
4229
+ }
4230
+ return withPinnedProfileDirectory(normalizedProfile, async (directory) => {
4231
+ if (!directory) throw new ProfileDeletedError(normalizedProfile);
4232
+ const acquired = await acquireProfileLock(normalizedProfile, options);
4233
+ const ownership = {
4234
+ lockPath,
4235
+ turnSlot: acquired.turnSlot,
4236
+ deletedWhileWaiting: acquired.deletedWhileWaiting,
4237
+ active: true
4238
+ };
4239
+ try {
4240
+ return await heldProfileLocks.run([...held, ownership], action);
4241
+ } finally {
4242
+ ownership.active = false;
4243
+ await acquired.release();
4244
+ if (ownership.deletedProfile || acquired.deletedWhileWaiting) {
4245
+ const current = await (0, import_promises.lstat)(namedProfilePath(normalizedProfile), { bigint: true }).catch(() => null);
4246
+ const original = await (0, import_promises.stat)(directory, { bigint: true }).catch(() => null);
4247
+ const currentDev = current && exactFileId(current.dev);
4248
+ const currentIno = current && exactFileId(current.ino);
4249
+ if (currentDev !== null && currentIno !== null && original && currentDev === exactFileId(original.dev) && currentIno === exactFileId(original.ino)) {
4250
+ await (0, import_promises.rmdir)(namedProfilePath(normalizedProfile)).catch(() => void 0);
4251
+ }
4252
+ }
4253
+ }
4254
+ }, { create: !options.requireProfile });
4052
4255
  }
4053
4256
  async function refuseWriteToDeletedProfile(profile) {
4054
4257
  const lockPath = profileLockPath(profile);
@@ -4082,17 +4285,15 @@ async function acquireProfileLock(profile, options) {
4082
4285
  retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
4083
4286
  };
4084
4287
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
4085
- const lockPath = profileLockPath(profile);
4288
+ const lockPath = (0, import_node_path.join)(profilePath(profile), ".lock");
4086
4289
  const turnDirectory = profileTurnLockPath(profile);
4087
4290
  const directory = profilePath(profile);
4291
+ await assertRealProfileDirectory(profile);
4088
4292
  if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
4089
4293
  if (!options.requireProfile) await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
4294
+ await assertRealProfileDirectory(profile);
4090
4295
  await (0, import_promises.mkdir)((0, import_node_path.dirname)(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
4091
- for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
4092
- await (0, import_promises.chmod)(path, PRIVATE_DIR_MODE).catch((error) => {
4093
- if (path !== directory || !isErrno(error, "ENOENT")) throw error;
4094
- });
4095
- }
4296
+ await (0, import_promises.chmod)(directory, PRIVATE_DIR_MODE);
4096
4297
  const liveness = { directory: turnDirectory, held: void 0 };
4097
4298
  const closeAcquisitionLiveness = async () => {
4098
4299
  if (liveness.held) await closeLiveness(liveness.held);
@@ -4112,6 +4313,7 @@ async function acquireProfileLock(profile, options) {
4112
4313
  }
4113
4314
  const deletion = await readJson((0, import_node_path.join)(turnDirectory, "deleted.json")).catch(() => null);
4114
4315
  const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
4316
+ if (deletedWhileWaiting && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
4115
4317
  while (true) {
4116
4318
  const token = (0, import_node_crypto.randomUUID)();
4117
4319
  if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
@@ -4148,8 +4350,8 @@ async function waitOrTimeOut(profile, deadline, detail) {
4148
4350
  await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
4149
4351
  }
4150
4352
  var TURN = /^(?:0|[1-9][0-9]*)$/;
4151
- var _a3;
4152
- var relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
4353
+ var _a4;
4354
+ var relinquishedTurns = globalThis[_a4 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a4] = /* @__PURE__ */ new Set());
4153
4355
  var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
4154
4356
  var SETTLE_RETRY_MS = 1e3;
4155
4357
  var SETTLE_RETRIES = 300;
@@ -4263,7 +4465,7 @@ async function takeTurn(profile, directory, latest, liveness) {
4263
4465
  if (published) await markTurn(path, token, "done");
4264
4466
  return null;
4265
4467
  } catch (error) {
4266
- if (published) settleOwnTurn(directory, slot, token);
4468
+ if (published) await settleOwnTurn(directory, slot, token);
4267
4469
  throw error;
4268
4470
  }
4269
4471
  }
@@ -4274,17 +4476,25 @@ async function settleAbandonedTurn(directory, abandoned) {
4274
4476
  }
4275
4477
  await markTurn(path, abandoned.token, "done");
4276
4478
  }
4277
- function settleOwnTurn(directory, slot, token, retries = SETTLE_RETRIES) {
4479
+ async function settleOwnTurn(directory, slot, token) {
4278
4480
  relinquishedTurns.add(token);
4279
- void (async () => {
4280
- try {
4281
- const turn = await readTurn(directory, slot);
4282
- if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(directory, turn);
4283
- relinquishedTurns.delete(token);
4284
- } catch {
4285
- if (retries > 0) setTimeout(() => settleOwnTurn(directory, slot, token, retries - 1), SETTLE_RETRY_MS).unref();
4286
- }
4287
- })();
4481
+ const pinned = await pinDirectory(directory).catch(() => null);
4482
+ const path = pinned?.path(".") ?? directory;
4483
+ let retries = SETTLE_RETRIES;
4484
+ const attempt = () => {
4485
+ void (async () => {
4486
+ try {
4487
+ const turn = await readTurn(path, slot);
4488
+ if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(path, turn);
4489
+ relinquishedTurns.delete(token);
4490
+ await pinned?.close();
4491
+ } catch {
4492
+ if (retries-- > 0) setTimeout(attempt, SETTLE_RETRY_MS).unref();
4493
+ else await pinned?.close();
4494
+ }
4495
+ })();
4496
+ };
4497
+ attempt();
4288
4498
  }
4289
4499
  async function markTurn(path, token, kind) {
4290
4500
  for (let attempt = 0; ; attempt++) {
@@ -4329,7 +4539,7 @@ async function releaseTurn(profile, turn, liveness) {
4329
4539
  try {
4330
4540
  await markTurn((0, import_node_path.join)(turn.directory, String(turn.slot)), turn.token, "done");
4331
4541
  } catch {
4332
- settleOwnTurn(turn.directory, turn.slot, turn.token);
4542
+ await settleOwnTurn(turn.directory, turn.slot, turn.token);
4333
4543
  return;
4334
4544
  }
4335
4545
  await collectTurns(profile, turn, liveness).catch(() => void 0);