@tinycloud/operations 0.6.1-beta.0 → 0.6.1-beta.10

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.
@@ -717,6 +717,7 @@ import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
717
717
  var DEFAULT_MANIFEST_SPACE = "applications";
718
718
  var SERVICE_SHORT_TO_LONG = Object.freeze({
719
719
  kv: "tinycloud.kv",
720
+ space: "tinycloud.space",
720
721
  sql: "tinycloud.sql",
721
722
  duckdb: "tinycloud.duckdb",
722
723
  capabilities: "tinycloud.capabilities",
@@ -3704,15 +3705,16 @@ function canonicalizeCaveats(caveats) {
3704
3705
  // src/state.ts
3705
3706
  import { randomUUID } from "crypto";
3706
3707
  import { AsyncLocalStorage } from "async_hooks";
3707
- import { readFileSync, readlinkSync } from "fs";
3708
+ import { constants as fsConstants, fstatSync, readFileSync, readlinkSync } from "fs";
3708
3709
  import {
3709
3710
  chmod,
3710
3711
  link,
3711
- lstat,
3712
3712
  mkdir,
3713
+ lstat,
3713
3714
  open,
3714
3715
  readFile,
3715
3716
  readdir,
3717
+ realpath,
3716
3718
  rename,
3717
3719
  rmdir,
3718
3720
  rm,
@@ -3742,14 +3744,18 @@ var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIE
3742
3744
  var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
3743
3745
  var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
3744
3746
  function processWideContext(key) {
3745
- var _a4;
3747
+ var _a5;
3746
3748
  const registry = globalThis;
3747
- return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new AsyncLocalStorage());
3749
+ return registry[_a5 = Symbol.for(key)] ?? (registry[_a5] = new AsyncLocalStorage());
3748
3750
  }
3749
3751
  var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
3750
3752
  var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
3753
+ var pinnedProfiles = processWideContext("tinycloud.operations.pinnedProfiles.v2");
3754
+ var pinnedHomes = processWideContext("tinycloud.operations.pinnedHomes.v2");
3751
3755
  var _a2;
3752
- 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());
3753
3759
  var cachedProcessOrigin;
3754
3760
  function processOrigin() {
3755
3761
  if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
@@ -3927,9 +3933,14 @@ function tinycloudHomePath() {
3927
3933
  function profilesPath() {
3928
3934
  return join(tinycloudHomePath(), "profiles");
3929
3935
  }
3930
- function profilePath(profile) {
3936
+ function namedProfilePath(profile) {
3931
3937
  return join(profilesPath(), validateProfileName(profile));
3932
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
+ }
3933
3944
  function profileConfigPath(profile) {
3934
3945
  return join(profilePath(profile), "profile.json");
3935
3946
  }
@@ -3943,10 +3954,12 @@ function profileStoreMetadataPath(profile, store) {
3943
3954
  return `${profileStorePath(profile, store)}.metadata.json`;
3944
3955
  }
3945
3956
  function profileLockPath(profile) {
3946
- return join(profilePath(profile), ".lock");
3957
+ return join(namedProfilePath(profile), ".lock");
3947
3958
  }
3948
3959
  function profileTurnLockPath(profile) {
3949
- 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));
3950
3963
  }
3951
3964
  async function readJson(filePath) {
3952
3965
  try {
@@ -3956,6 +3969,44 @@ async function readJson(filePath) {
3956
3969
  throw error;
3957
3970
  }
3958
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
+ }
3959
4010
  async function writeJsonAtomic(filePath, value) {
3960
4011
  await replaceJson(filePath, value, false);
3961
4012
  }
@@ -3989,50 +4040,204 @@ async function writeFileDurably(path, contents) {
3989
4040
  }
3990
4041
  }
3991
4042
  async function readSession(profile) {
3992
- await readStoreMetadata(profile, "session");
3993
- 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
+ });
3994
4048
  }
3995
4049
  async function readAdditionalDelegations(profile) {
3996
4050
  return (await readProfileStore(profile, "additional-delegations")).records;
3997
4051
  }
3998
4052
  async function readStoreMetadata(profile, store) {
3999
- const metadata = await readJson(profileStoreMetadataPath(profile, store));
4000
- if (metadata === null) return { formatVersion: 1 };
4001
- if (typeof metadata !== "object" || metadata.formatVersion !== 1) {
4002
- throw new TypeError(`Unsupported store format for "${store}".`);
4003
- }
4004
- 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
+ });
4005
4061
  }
4006
4062
  async function readProfileStore(profile, store) {
4007
- const [metadata, raw] = await Promise.all([
4008
- readStoreMetadata(profile, store),
4009
- readJson(profileStorePath(profile, store))
4010
- ]);
4011
- return {
4012
- formatVersion: metadata.formatVersion,
4013
- records: Array.isArray(raw) ? raw : []
4014
- };
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
+ });
4015
4074
  }
4016
- async function withProfileLock(profile, action, options = {}) {
4017
- const normalizedProfile = validateProfileName(profile);
4018
- const lockPath = profileLockPath(normalizedProfile);
4019
- const held = (heldProfileLocks.getStore() ?? []).filter((ownership2) => ownership2.active);
4020
- if (held.some((ownership2) => ownership2.lockPath === lockPath)) return action();
4021
- const acquired = await acquireProfileLock(normalizedProfile, options);
4022
- const ownership = {
4023
- lockPath,
4024
- turnSlot: acquired.turnSlot,
4025
- deletedWhileWaiting: acquired.deletedWhileWaiting,
4026
- active: true
4027
- };
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;
4028
4088
  try {
4029
- 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
+ }
4030
4181
  } finally {
4031
- ownership.active = false;
4032
- await acquired.release();
4033
- if (acquired.deletedWhileWaiting) await rmdir(profilePath(normalizedProfile)).catch(() => void 0);
4182
+ for (const handle of handles.reverse()) await handle.close();
4034
4183
  }
4035
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
+ }
4036
4241
  async function refuseWriteToDeletedProfile(profile) {
4037
4242
  const lockPath = profileLockPath(profile);
4038
4243
  const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
@@ -4065,17 +4270,15 @@ async function acquireProfileLock(profile, options) {
4065
4270
  retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
4066
4271
  };
4067
4272
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
4068
- const lockPath = profileLockPath(profile);
4273
+ const lockPath = join(profilePath(profile), ".lock");
4069
4274
  const turnDirectory = profileTurnLockPath(profile);
4070
4275
  const directory = profilePath(profile);
4276
+ await assertRealProfileDirectory(profile);
4071
4277
  if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
4072
4278
  if (!options.requireProfile) await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
4279
+ await assertRealProfileDirectory(profile);
4073
4280
  await mkdir(dirname(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
4074
- for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
4075
- await chmod(path, PRIVATE_DIR_MODE).catch((error) => {
4076
- if (path !== directory || !isErrno(error, "ENOENT")) throw error;
4077
- });
4078
- }
4281
+ await chmod(directory, PRIVATE_DIR_MODE);
4079
4282
  const liveness = { directory: turnDirectory, held: void 0 };
4080
4283
  const closeAcquisitionLiveness = async () => {
4081
4284
  if (liveness.held) await closeLiveness(liveness.held);
@@ -4095,6 +4298,7 @@ async function acquireProfileLock(profile, options) {
4095
4298
  }
4096
4299
  const deletion = await readJson(join(turnDirectory, "deleted.json")).catch(() => null);
4097
4300
  const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
4301
+ if (deletedWhileWaiting && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
4098
4302
  while (true) {
4099
4303
  const token = randomUUID();
4100
4304
  if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
@@ -4131,8 +4335,8 @@ async function waitOrTimeOut(profile, deadline, detail) {
4131
4335
  await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
4132
4336
  }
4133
4337
  var TURN = /^(?:0|[1-9][0-9]*)$/;
4134
- var _a3;
4135
- 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());
4136
4340
  var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
4137
4341
  var SETTLE_RETRY_MS = 1e3;
4138
4342
  var SETTLE_RETRIES = 300;
@@ -4246,7 +4450,7 @@ async function takeTurn(profile, directory, latest, liveness) {
4246
4450
  if (published) await markTurn(path, token, "done");
4247
4451
  return null;
4248
4452
  } catch (error) {
4249
- if (published) settleOwnTurn(directory, slot, token);
4453
+ if (published) await settleOwnTurn(directory, slot, token);
4250
4454
  throw error;
4251
4455
  }
4252
4456
  }
@@ -4257,17 +4461,25 @@ async function settleAbandonedTurn(directory, abandoned) {
4257
4461
  }
4258
4462
  await markTurn(path, abandoned.token, "done");
4259
4463
  }
4260
- function settleOwnTurn(directory, slot, token, retries = SETTLE_RETRIES) {
4464
+ async function settleOwnTurn(directory, slot, token) {
4261
4465
  relinquishedTurns.add(token);
4262
- void (async () => {
4263
- try {
4264
- const turn = await readTurn(directory, slot);
4265
- if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(directory, turn);
4266
- relinquishedTurns.delete(token);
4267
- } catch {
4268
- if (retries > 0) setTimeout(() => settleOwnTurn(directory, slot, token, retries - 1), SETTLE_RETRY_MS).unref();
4269
- }
4270
- })();
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();
4271
4483
  }
4272
4484
  async function markTurn(path, token, kind) {
4273
4485
  for (let attempt = 0; ; attempt++) {
@@ -4312,7 +4524,7 @@ async function releaseTurn(profile, turn, liveness) {
4312
4524
  try {
4313
4525
  await markTurn(join(turn.directory, String(turn.slot)), turn.token, "done");
4314
4526
  } catch {
4315
- settleOwnTurn(turn.directory, turn.slot, turn.token);
4527
+ await settleOwnTurn(turn.directory, turn.slot, turn.token);
4316
4528
  return;
4317
4529
  }
4318
4530
  await collectTurns(profile, turn, liveness).catch(() => void 0);