@tinycloud/operations 0.6.1-beta.4 → 0.6.1-beta.5

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.
@@ -3705,15 +3705,16 @@ function canonicalizeCaveats(caveats) {
3705
3705
  // src/state.ts
3706
3706
  import { randomUUID } from "crypto";
3707
3707
  import { AsyncLocalStorage } from "async_hooks";
3708
- import { readFileSync, readlinkSync } from "fs";
3708
+ import { constants as fsConstants, fstatSync, readFileSync, readlinkSync } from "fs";
3709
3709
  import {
3710
3710
  chmod,
3711
3711
  link,
3712
- lstat,
3713
3712
  mkdir,
3713
+ lstat,
3714
3714
  open,
3715
3715
  readFile,
3716
3716
  readdir,
3717
+ realpath,
3717
3718
  rename,
3718
3719
  rmdir,
3719
3720
  rm,
@@ -3743,14 +3744,18 @@ var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIE
3743
3744
  var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
3744
3745
  var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
3745
3746
  function processWideContext(key) {
3746
- var _a4;
3747
+ var _a5;
3747
3748
  const registry = globalThis;
3748
- return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new AsyncLocalStorage());
3749
+ return registry[_a5 = Symbol.for(key)] ?? (registry[_a5] = new AsyncLocalStorage());
3749
3750
  }
3750
3751
  var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
3751
3752
  var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
3753
+ var pinnedProfiles = processWideContext("tinycloud.operations.pinnedProfiles.v2");
3754
+ var pinnedHomes = processWideContext("tinycloud.operations.pinnedHomes.v2");
3752
3755
  var _a2;
3753
- var processInstanceId = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a2] = randomUUID());
3756
+ var canonicalHomes = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.canonicalHomes.v1")] ?? (globalThis[_a2] = /* @__PURE__ */ new Map());
3757
+ var _a3;
3758
+ var processInstanceId = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a3] = randomUUID());
3754
3759
  var cachedProcessOrigin;
3755
3760
  function processOrigin() {
3756
3761
  if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
@@ -3928,9 +3933,14 @@ function tinycloudHomePath() {
3928
3933
  function profilesPath() {
3929
3934
  return join(tinycloudHomePath(), "profiles");
3930
3935
  }
3931
- function profilePath(profile) {
3936
+ function namedProfilePath(profile) {
3932
3937
  return join(profilesPath(), validateProfileName(profile));
3933
3938
  }
3939
+ function profilePath(profile) {
3940
+ const named = namedProfilePath(profile);
3941
+ const pinned = pinnedProfiles.getStore()?.get(named);
3942
+ return pinned ? livePinnedPath(profile, pinned) : named;
3943
+ }
3934
3944
  function profileConfigPath(profile) {
3935
3945
  return join(profilePath(profile), "profile.json");
3936
3946
  }
@@ -3944,10 +3954,12 @@ function profileStoreMetadataPath(profile, store) {
3944
3954
  return `${profileStorePath(profile, store)}.metadata.json`;
3945
3955
  }
3946
3956
  function profileLockPath(profile) {
3947
- return join(profilePath(profile), ".lock");
3957
+ return join(namedProfilePath(profile), ".lock");
3948
3958
  }
3949
3959
  function profileTurnLockPath(profile) {
3950
- return join(tinycloudHomePath(), "profile-locks", validateProfileName(profile));
3960
+ const home = tinycloudHomePath();
3961
+ const pinned = pinnedHomes.getStore()?.get(home);
3962
+ return join(pinned ? livePinnedPath(profile, pinned) : home, "profile-locks", validateProfileName(profile));
3951
3963
  }
3952
3964
  async function readJson(filePath) {
3953
3965
  try {
@@ -3957,6 +3969,44 @@ async function readJson(filePath) {
3957
3969
  throw error;
3958
3970
  }
3959
3971
  }
3972
+ async function readProfileJson(profile, filePath) {
3973
+ const before = process.platform !== "linux" ? await lstat(filePath, { bigint: true }).catch((error) => {
3974
+ if (error.code === "ENOENT") return null;
3975
+ throw error;
3976
+ }) : null;
3977
+ if (process.platform !== "linux" && !before) return null;
3978
+ if (before && !before.isFile()) throw new ProfileSymlinkError(profile);
3979
+ const dev = before && exactFileId(before.dev);
3980
+ const ino = before && exactFileId(before.ino);
3981
+ try {
3982
+ const handle = await open(filePath, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
3983
+ try {
3984
+ if (before) {
3985
+ const opened = await handle.stat({ bigint: true });
3986
+ if (!opened.isFile() || dev === null || ino === null || dev !== exactFileId(opened.dev) || ino !== exactFileId(opened.ino)) {
3987
+ throw new ProfileSymlinkError(profile, "changed while opening its file");
3988
+ }
3989
+ }
3990
+ const contents = await handle.readFile("utf8");
3991
+ if (before) {
3992
+ const named = await lstat(filePath, { bigint: true }).catch((error) => {
3993
+ if (error.code === "ENOENT") return null;
3994
+ throw error;
3995
+ });
3996
+ if (!named?.isFile() || dev !== exactFileId(named.dev) || ino !== exactFileId(named.ino)) {
3997
+ throw new ProfileSymlinkError(profile, "changed while reading its file");
3998
+ }
3999
+ }
4000
+ return JSON.parse(contents);
4001
+ } finally {
4002
+ await handle.close();
4003
+ }
4004
+ } catch (error) {
4005
+ if (isErrno(error, "ENOENT")) return null;
4006
+ if (isErrno(error, "ELOOP") || isErrno(error, "ENOTDIR")) throw new ProfileSymlinkError(profile);
4007
+ throw error;
4008
+ }
4009
+ }
3960
4010
  async function writeJsonAtomic(filePath, value) {
3961
4011
  await replaceJson(filePath, value, false);
3962
4012
  }
@@ -3990,50 +4040,204 @@ async function writeFileDurably(path, contents) {
3990
4040
  }
3991
4041
  }
3992
4042
  async function readSession(profile) {
3993
- await readStoreMetadata(profile, "session");
3994
- return readJson(sessionPath(profile));
4043
+ return withPinnedProfileDirectory(profile, async (directory) => {
4044
+ if (!directory) return null;
4045
+ await readStoreMetadata(profile, "session");
4046
+ return readProfileJson(profile, sessionPath(profile));
4047
+ });
3995
4048
  }
3996
4049
  async function readAdditionalDelegations(profile) {
3997
4050
  return (await readProfileStore(profile, "additional-delegations")).records;
3998
4051
  }
3999
4052
  async function readStoreMetadata(profile, store) {
4000
- const metadata = await readJson(profileStoreMetadataPath(profile, store));
4001
- if (metadata === null) return { formatVersion: 1 };
4002
- if (typeof metadata !== "object" || metadata.formatVersion !== 1) {
4003
- throw new TypeError(`Unsupported store format for "${store}".`);
4004
- }
4005
- return metadata;
4053
+ return withPinnedProfileDirectory(profile, async (directory) => {
4054
+ const metadata = directory ? await readProfileJson(profile, profileStoreMetadataPath(profile, store)) : null;
4055
+ if (metadata === null) return { formatVersion: 1 };
4056
+ if (typeof metadata !== "object" || metadata.formatVersion !== 1) {
4057
+ throw new TypeError(`Unsupported store format for "${store}".`);
4058
+ }
4059
+ return metadata;
4060
+ });
4006
4061
  }
4007
4062
  async function readProfileStore(profile, store) {
4008
- const [metadata, raw] = await Promise.all([
4009
- readStoreMetadata(profile, store),
4010
- readJson(profileStorePath(profile, store))
4011
- ]);
4012
- return {
4013
- formatVersion: metadata.formatVersion,
4014
- records: Array.isArray(raw) ? raw : []
4015
- };
4063
+ return withPinnedProfileDirectory(profile, async (directory) => {
4064
+ if (!directory) return { formatVersion: 1, records: [] };
4065
+ const [metadata, raw] = await Promise.all([
4066
+ readStoreMetadata(profile, store),
4067
+ readProfileJson(profile, profileStorePath(profile, store))
4068
+ ]);
4069
+ return {
4070
+ formatVersion: metadata.formatVersion,
4071
+ records: Array.isArray(raw) ? raw : []
4072
+ };
4073
+ });
4016
4074
  }
4017
- async function withProfileLock(profile, action, options = {}) {
4018
- const normalizedProfile = validateProfileName(profile);
4019
- const lockPath = profileLockPath(normalizedProfile);
4020
- const held = (heldProfileLocks.getStore() ?? []).filter((ownership2) => ownership2.active);
4021
- if (held.some((ownership2) => ownership2.lockPath === lockPath)) return action();
4022
- const acquired = await acquireProfileLock(normalizedProfile, options);
4023
- const ownership = {
4024
- lockPath,
4025
- turnSlot: acquired.turnSlot,
4026
- deletedWhileWaiting: acquired.deletedWhileWaiting,
4027
- active: true
4028
- };
4075
+ var ProfileSymlinkError = class extends Error {
4076
+ constructor(profile, detail = "must be a real directory, not a symlink or another file type") {
4077
+ super(`Profile "${profile}" ${detail}.`);
4078
+ this.code = "PROFILE_SYMLINK";
4079
+ this.name = "ProfileSymlinkError";
4080
+ }
4081
+ };
4082
+ function exactFileId(value) {
4083
+ if (typeof value === "bigint") return value >= 0n ? value : null;
4084
+ return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : null;
4085
+ }
4086
+ function isPinnedLive(pinned) {
4087
+ if (!pinned.active) return false;
4029
4088
  try {
4030
- return await heldProfileLocks.run([...held, ownership], action);
4089
+ const stats = fstatSync(pinned.fd, { bigint: true });
4090
+ return exactFileId(stats.dev) === pinned.dev && exactFileId(stats.ino) === pinned.ino;
4091
+ } catch {
4092
+ return false;
4093
+ }
4094
+ }
4095
+ function livePinnedPath(profile, pinned) {
4096
+ if (isPinnedLive(pinned)) return pinned.path;
4097
+ throw new ProfileSymlinkError(profile, "its pinned directory is no longer active or changed during use");
4098
+ }
4099
+ async function withPinnedProfileDirectory(profile, action, options = {}) {
4100
+ const named = namedProfilePath(profile);
4101
+ const existing = pinnedProfiles.getStore()?.get(named);
4102
+ if (existing) return action(livePinnedPath(profile, existing));
4103
+ if (process.platform !== "linux") {
4104
+ if (options.create) await mkdir(named, { recursive: true, mode: PRIVATE_DIR_MODE });
4105
+ const stats = await lstat(named).catch((error) => {
4106
+ if (error.code === "ENOENT") return null;
4107
+ throw error;
4108
+ });
4109
+ if (!stats) return action(null);
4110
+ if (!stats.isDirectory() || stats.isSymbolicLink()) throw new ProfileSymlinkError(profile);
4111
+ return action(named);
4112
+ }
4113
+ const home = tinycloudHomePath();
4114
+ const handles = [];
4115
+ const fdPath = (fd) => `/proc/self/fd/${fd}`;
4116
+ const flags = fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW;
4117
+ try {
4118
+ if (options.create) await mkdir(home, { recursive: true, mode: PRIVATE_DIR_MODE });
4119
+ const resolved = canonicalHomes.get(home) ?? await realpath(home).catch((error) => {
4120
+ if (error.code === "ENOENT" && !options.create) return null;
4121
+ if (error.code === "ELOOP" || error.code === "ENOTDIR") throw new ProfileSymlinkError(profile);
4122
+ throw error;
4123
+ });
4124
+ if (!resolved) return await action(null);
4125
+ const root = canonicalHomes.get(home) ?? resolved;
4126
+ canonicalHomes.set(home, root);
4127
+ const segments = [root, "profiles", validateProfileName(profile)];
4128
+ for (let index = 0; index < segments.length; index++) {
4129
+ const path = handles.length ? join(fdPath(handles[handles.length - 1].fd), segments[index]) : segments[index];
4130
+ if (options.create && handles.length) {
4131
+ await mkdir(path, { mode: PRIVATE_DIR_MODE }).catch((error) => {
4132
+ if (error.code !== "EEXIST") throw error;
4133
+ });
4134
+ }
4135
+ const handle = await open(path, index === 1 ? flags & ~fsConstants.O_NOFOLLOW : flags).catch((error) => {
4136
+ if (error.code === "ENOENT" && !options.create) return null;
4137
+ if (error.code === "ELOOP" || error.code === "ENOTDIR") throw new ProfileSymlinkError(profile);
4138
+ throw error;
4139
+ });
4140
+ if (!handle) return await action(null);
4141
+ handles.push(handle);
4142
+ if (options.create) await handle.chmod(PRIVATE_DIR_MODE);
4143
+ }
4144
+ const opened = handles[2];
4145
+ const [actual, namedStats] = await Promise.all([opened.stat({ bigint: true }), lstat(named, { bigint: true })]);
4146
+ const dev = exactFileId(actual.dev);
4147
+ const ino = exactFileId(actual.ino);
4148
+ if (!namedStats.isDirectory() || dev === null || ino === null || dev !== exactFileId(namedStats.dev) || ino !== exactFileId(namedStats.ino)) {
4149
+ throw new ProfileSymlinkError(profile, "changed while opening its directory");
4150
+ }
4151
+ const directory = fdPath(opened.fd);
4152
+ const pinned = {
4153
+ path: directory,
4154
+ fd: opened.fd,
4155
+ dev,
4156
+ ino,
4157
+ active: true
4158
+ };
4159
+ const homeHandle = handles[0];
4160
+ const homeStats = await homeHandle.stat({ bigint: true });
4161
+ const homeDev = exactFileId(homeStats.dev);
4162
+ const homeIno = exactFileId(homeStats.ino);
4163
+ if (homeDev === null || homeIno === null) throw new ProfileSymlinkError(profile, "has an unverified home directory identity");
4164
+ const homePinned = {
4165
+ path: fdPath(homeHandle.fd),
4166
+ fd: homeHandle.fd,
4167
+ dev: homeDev,
4168
+ ino: homeIno,
4169
+ active: true
4170
+ };
4171
+ const context = new Map(pinnedProfiles.getStore());
4172
+ context.set(named, pinned);
4173
+ const homes = new Map(pinnedHomes.getStore());
4174
+ homes.set(home, homePinned);
4175
+ try {
4176
+ return await pinnedHomes.run(homes, () => pinnedProfiles.run(context, () => action(directory)));
4177
+ } finally {
4178
+ pinned.active = false;
4179
+ homePinned.active = false;
4180
+ }
4031
4181
  } finally {
4032
- ownership.active = false;
4033
- await acquired.release();
4034
- if (acquired.deletedWhileWaiting) await rmdir(profilePath(normalizedProfile)).catch(() => void 0);
4182
+ for (const handle of handles.reverse()) await handle.close();
4035
4183
  }
4036
4184
  }
4185
+ async function assertRealProfileDirectory(profile) {
4186
+ const named = namedProfilePath(profile);
4187
+ const pinned = pinnedProfiles.getStore()?.get(named);
4188
+ if (pinned) {
4189
+ livePinnedPath(profile, pinned);
4190
+ return;
4191
+ }
4192
+ const stats = await lstat(named).catch((error) => {
4193
+ if (error.code === "ENOENT") return null;
4194
+ throw error;
4195
+ });
4196
+ if (stats && (!stats.isDirectory() || stats.isSymbolicLink())) throw new ProfileSymlinkError(profile);
4197
+ }
4198
+ async function withProfileLock(profile, action, options = {}) {
4199
+ const normalizedProfile = validateProfileName(profile);
4200
+ const lockPath = profileLockPath(normalizedProfile);
4201
+ const held = (heldProfileLocks.getStore() ?? []).filter((ownership) => ownership.active);
4202
+ if (held.some((ownership) => ownership.lockPath === lockPath)) {
4203
+ await assertRealProfileDirectory(normalizedProfile);
4204
+ return action();
4205
+ }
4206
+ const named = namedProfilePath(normalizedProfile);
4207
+ const inherited = pinnedProfiles.getStore()?.get(named);
4208
+ if (inherited && !isPinnedLive(inherited)) {
4209
+ const profiles = new Map(pinnedProfiles.getStore());
4210
+ profiles.delete(named);
4211
+ const homes = new Map(pinnedHomes.getStore());
4212
+ homes.delete(tinycloudHomePath());
4213
+ return pinnedHomes.run(homes, () => pinnedProfiles.run(profiles, () => withProfileLock(normalizedProfile, action, options)));
4214
+ }
4215
+ return withPinnedProfileDirectory(normalizedProfile, async (directory) => {
4216
+ if (!directory) throw new ProfileDeletedError(normalizedProfile);
4217
+ const acquired = await acquireProfileLock(normalizedProfile, options);
4218
+ const ownership = {
4219
+ lockPath,
4220
+ turnSlot: acquired.turnSlot,
4221
+ deletedWhileWaiting: acquired.deletedWhileWaiting,
4222
+ active: true
4223
+ };
4224
+ try {
4225
+ return await heldProfileLocks.run([...held, ownership], action);
4226
+ } finally {
4227
+ ownership.active = false;
4228
+ await acquired.release();
4229
+ if (ownership.deletedProfile || acquired.deletedWhileWaiting) {
4230
+ const current = await lstat(namedProfilePath(normalizedProfile), { bigint: true }).catch(() => null);
4231
+ const original = await stat(directory, { bigint: true }).catch(() => null);
4232
+ const currentDev = current && exactFileId(current.dev);
4233
+ const currentIno = current && exactFileId(current.ino);
4234
+ if (currentDev !== null && currentIno !== null && original && currentDev === exactFileId(original.dev) && currentIno === exactFileId(original.ino)) {
4235
+ await rmdir(namedProfilePath(normalizedProfile)).catch(() => void 0);
4236
+ }
4237
+ }
4238
+ }
4239
+ }, { create: !options.requireProfile });
4240
+ }
4037
4241
  async function refuseWriteToDeletedProfile(profile) {
4038
4242
  const lockPath = profileLockPath(profile);
4039
4243
  const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
@@ -4066,17 +4270,15 @@ async function acquireProfileLock(profile, options) {
4066
4270
  retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
4067
4271
  };
4068
4272
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
4069
- const lockPath = profileLockPath(profile);
4273
+ const lockPath = join(profilePath(profile), ".lock");
4070
4274
  const turnDirectory = profileTurnLockPath(profile);
4071
4275
  const directory = profilePath(profile);
4276
+ await assertRealProfileDirectory(profile);
4072
4277
  if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
4073
4278
  if (!options.requireProfile) await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
4279
+ await assertRealProfileDirectory(profile);
4074
4280
  await mkdir(dirname(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
4075
- for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
4076
- await chmod(path, PRIVATE_DIR_MODE).catch((error) => {
4077
- if (path !== directory || !isErrno(error, "ENOENT")) throw error;
4078
- });
4079
- }
4281
+ await chmod(directory, PRIVATE_DIR_MODE);
4080
4282
  const liveness = { directory: turnDirectory, held: void 0 };
4081
4283
  const closeAcquisitionLiveness = async () => {
4082
4284
  if (liveness.held) await closeLiveness(liveness.held);
@@ -4096,6 +4298,7 @@ async function acquireProfileLock(profile, options) {
4096
4298
  }
4097
4299
  const deletion = await readJson(join(turnDirectory, "deleted.json")).catch(() => null);
4098
4300
  const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
4301
+ if (deletedWhileWaiting && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
4099
4302
  while (true) {
4100
4303
  const token = randomUUID();
4101
4304
  if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
@@ -4132,8 +4335,8 @@ async function waitOrTimeOut(profile, deadline, detail) {
4132
4335
  await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
4133
4336
  }
4134
4337
  var TURN = /^(?:0|[1-9][0-9]*)$/;
4135
- var _a3;
4136
- var relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
4338
+ var _a4;
4339
+ var relinquishedTurns = globalThis[_a4 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a4] = /* @__PURE__ */ new Set());
4137
4340
  var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
4138
4341
  var SETTLE_RETRY_MS = 1e3;
4139
4342
  var SETTLE_RETRIES = 300;
@@ -4247,7 +4450,7 @@ async function takeTurn(profile, directory, latest, liveness) {
4247
4450
  if (published) await markTurn(path, token, "done");
4248
4451
  return null;
4249
4452
  } catch (error) {
4250
- if (published) settleOwnTurn(directory, slot, token);
4453
+ if (published) await settleOwnTurn(directory, slot, token);
4251
4454
  throw error;
4252
4455
  }
4253
4456
  }
@@ -4258,17 +4461,25 @@ async function settleAbandonedTurn(directory, abandoned) {
4258
4461
  }
4259
4462
  await markTurn(path, abandoned.token, "done");
4260
4463
  }
4261
- function settleOwnTurn(directory, slot, token, retries = SETTLE_RETRIES) {
4464
+ async function settleOwnTurn(directory, slot, token) {
4262
4465
  relinquishedTurns.add(token);
4263
- void (async () => {
4264
- try {
4265
- const turn = await readTurn(directory, slot);
4266
- if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(directory, turn);
4267
- relinquishedTurns.delete(token);
4268
- } catch {
4269
- if (retries > 0) setTimeout(() => settleOwnTurn(directory, slot, token, retries - 1), SETTLE_RETRY_MS).unref();
4270
- }
4271
- })();
4466
+ const pinned = await pinDirectory(directory).catch(() => null);
4467
+ const path = pinned?.path(".") ?? directory;
4468
+ let retries = SETTLE_RETRIES;
4469
+ const attempt = () => {
4470
+ void (async () => {
4471
+ try {
4472
+ const turn = await readTurn(path, slot);
4473
+ if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(path, turn);
4474
+ relinquishedTurns.delete(token);
4475
+ await pinned?.close();
4476
+ } catch {
4477
+ if (retries-- > 0) setTimeout(attempt, SETTLE_RETRY_MS).unref();
4478
+ else await pinned?.close();
4479
+ }
4480
+ })();
4481
+ };
4482
+ attempt();
4272
4483
  }
4273
4484
  async function markTurn(path, token, kind) {
4274
4485
  for (let attempt = 0; ; attempt++) {
@@ -4313,7 +4524,7 @@ async function releaseTurn(profile, turn, liveness) {
4313
4524
  try {
4314
4525
  await markTurn(join(turn.directory, String(turn.slot)), turn.token, "done");
4315
4526
  } catch {
4316
- settleOwnTurn(turn.directory, turn.slot, turn.token);
4527
+ await settleOwnTurn(turn.directory, turn.slot, turn.token);
4317
4528
  return;
4318
4529
  }
4319
4530
  await collectTurns(profile, turn, liveness).catch(() => void 0);