@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.
@@ -747,6 +747,7 @@ var import_sdk_services = require("@tinycloud/sdk-services");
747
747
  var DEFAULT_MANIFEST_SPACE = "applications";
748
748
  var SERVICE_SHORT_TO_LONG = Object.freeze({
749
749
  kv: "tinycloud.kv",
750
+ space: "tinycloud.space",
750
751
  sql: "tinycloud.sql",
751
752
  duckdb: "tinycloud.duckdb",
752
753
  capabilities: "tinycloud.capabilities",
@@ -3758,14 +3759,18 @@ var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIE
3758
3759
  var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
3759
3760
  var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
3760
3761
  function processWideContext(key) {
3761
- var _a4;
3762
+ var _a5;
3762
3763
  const registry = globalThis;
3763
- 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());
3764
3765
  }
3765
3766
  var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
3766
3767
  var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
3768
+ var pinnedProfiles = processWideContext("tinycloud.operations.pinnedProfiles.v2");
3769
+ var pinnedHomes = processWideContext("tinycloud.operations.pinnedHomes.v2");
3767
3770
  var _a2;
3768
- 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)());
3769
3774
  var cachedProcessOrigin;
3770
3775
  function processOrigin() {
3771
3776
  if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
@@ -3943,9 +3948,14 @@ function tinycloudHomePath() {
3943
3948
  function profilesPath() {
3944
3949
  return (0, import_node_path.join)(tinycloudHomePath(), "profiles");
3945
3950
  }
3946
- function profilePath(profile) {
3951
+ function namedProfilePath(profile) {
3947
3952
  return (0, import_node_path.join)(profilesPath(), validateProfileName(profile));
3948
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
+ }
3949
3959
  function profileConfigPath(profile) {
3950
3960
  return (0, import_node_path.join)(profilePath(profile), "profile.json");
3951
3961
  }
@@ -3959,10 +3969,12 @@ function profileStoreMetadataPath(profile, store) {
3959
3969
  return `${profileStorePath(profile, store)}.metadata.json`;
3960
3970
  }
3961
3971
  function profileLockPath(profile) {
3962
- return (0, import_node_path.join)(profilePath(profile), ".lock");
3972
+ return (0, import_node_path.join)(namedProfilePath(profile), ".lock");
3963
3973
  }
3964
3974
  function profileTurnLockPath(profile) {
3965
- 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));
3966
3978
  }
3967
3979
  async function readJson(filePath) {
3968
3980
  try {
@@ -3972,6 +3984,44 @@ async function readJson(filePath) {
3972
3984
  throw error;
3973
3985
  }
3974
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
+ }
3975
4025
  async function writeJsonAtomic(filePath, value) {
3976
4026
  await replaceJson(filePath, value, false);
3977
4027
  }
@@ -4005,49 +4055,203 @@ async function writeFileDurably(path, contents) {
4005
4055
  }
4006
4056
  }
4007
4057
  async function readSession(profile) {
4008
- await readStoreMetadata(profile, "session");
4009
- 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
+ });
4010
4063
  }
4011
4064
  async function readAdditionalDelegations(profile) {
4012
4065
  return (await readProfileStore(profile, "additional-delegations")).records;
4013
4066
  }
4014
4067
  async function readStoreMetadata(profile, store) {
4015
- const metadata = await readJson(profileStoreMetadataPath(profile, store));
4016
- if (metadata === null) return { formatVersion: 1 };
4017
- if (typeof metadata !== "object" || metadata.formatVersion !== 1) {
4018
- throw new TypeError(`Unsupported store format for "${store}".`);
4019
- }
4020
- 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
+ });
4021
4076
  }
4022
4077
  async function readProfileStore(profile, store) {
4023
- const [metadata, raw] = await Promise.all([
4024
- readStoreMetadata(profile, store),
4025
- readJson(profileStorePath(profile, store))
4026
- ]);
4027
- return {
4028
- formatVersion: metadata.formatVersion,
4029
- records: Array.isArray(raw) ? raw : []
4030
- };
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
+ });
4031
4089
  }
4032
- async function withProfileLock(profile, action, options = {}) {
4033
- const normalizedProfile = validateProfileName(profile);
4034
- const lockPath = profileLockPath(normalizedProfile);
4035
- const held = (heldProfileLocks.getStore() ?? []).filter((ownership2) => ownership2.active);
4036
- if (held.some((ownership2) => ownership2.lockPath === lockPath)) return action();
4037
- const acquired = await acquireProfileLock(normalizedProfile, options);
4038
- const ownership = {
4039
- lockPath,
4040
- turnSlot: acquired.turnSlot,
4041
- deletedWhileWaiting: acquired.deletedWhileWaiting,
4042
- active: true
4043
- };
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;
4044
4103
  try {
4045
- 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
+ }
4046
4196
  } finally {
4047
- ownership.active = false;
4048
- await acquired.release();
4049
- 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;
4050
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 });
4051
4255
  }
4052
4256
  async function refuseWriteToDeletedProfile(profile) {
4053
4257
  const lockPath = profileLockPath(profile);
@@ -4081,17 +4285,15 @@ async function acquireProfileLock(profile, options) {
4081
4285
  retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
4082
4286
  };
4083
4287
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
4084
- const lockPath = profileLockPath(profile);
4288
+ const lockPath = (0, import_node_path.join)(profilePath(profile), ".lock");
4085
4289
  const turnDirectory = profileTurnLockPath(profile);
4086
4290
  const directory = profilePath(profile);
4291
+ await assertRealProfileDirectory(profile);
4087
4292
  if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
4088
4293
  if (!options.requireProfile) await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
4294
+ await assertRealProfileDirectory(profile);
4089
4295
  await (0, import_promises.mkdir)((0, import_node_path.dirname)(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
4090
- for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
4091
- await (0, import_promises.chmod)(path, PRIVATE_DIR_MODE).catch((error) => {
4092
- if (path !== directory || !isErrno(error, "ENOENT")) throw error;
4093
- });
4094
- }
4296
+ await (0, import_promises.chmod)(directory, PRIVATE_DIR_MODE);
4095
4297
  const liveness = { directory: turnDirectory, held: void 0 };
4096
4298
  const closeAcquisitionLiveness = async () => {
4097
4299
  if (liveness.held) await closeLiveness(liveness.held);
@@ -4111,6 +4313,7 @@ async function acquireProfileLock(profile, options) {
4111
4313
  }
4112
4314
  const deletion = await readJson((0, import_node_path.join)(turnDirectory, "deleted.json")).catch(() => null);
4113
4315
  const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
4316
+ if (deletedWhileWaiting && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
4114
4317
  while (true) {
4115
4318
  const token = (0, import_node_crypto.randomUUID)();
4116
4319
  if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
@@ -4147,8 +4350,8 @@ async function waitOrTimeOut(profile, deadline, detail) {
4147
4350
  await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
4148
4351
  }
4149
4352
  var TURN = /^(?:0|[1-9][0-9]*)$/;
4150
- var _a3;
4151
- 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());
4152
4355
  var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
4153
4356
  var SETTLE_RETRY_MS = 1e3;
4154
4357
  var SETTLE_RETRIES = 300;
@@ -4262,7 +4465,7 @@ async function takeTurn(profile, directory, latest, liveness) {
4262
4465
  if (published) await markTurn(path, token, "done");
4263
4466
  return null;
4264
4467
  } catch (error) {
4265
- if (published) settleOwnTurn(directory, slot, token);
4468
+ if (published) await settleOwnTurn(directory, slot, token);
4266
4469
  throw error;
4267
4470
  }
4268
4471
  }
@@ -4273,17 +4476,25 @@ async function settleAbandonedTurn(directory, abandoned) {
4273
4476
  }
4274
4477
  await markTurn(path, abandoned.token, "done");
4275
4478
  }
4276
- function settleOwnTurn(directory, slot, token, retries = SETTLE_RETRIES) {
4479
+ async function settleOwnTurn(directory, slot, token) {
4277
4480
  relinquishedTurns.add(token);
4278
- void (async () => {
4279
- try {
4280
- const turn = await readTurn(directory, slot);
4281
- if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(directory, turn);
4282
- relinquishedTurns.delete(token);
4283
- } catch {
4284
- if (retries > 0) setTimeout(() => settleOwnTurn(directory, slot, token, retries - 1), SETTLE_RETRY_MS).unref();
4285
- }
4286
- })();
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();
4287
4498
  }
4288
4499
  async function markTurn(path, token, kind) {
4289
4500
  for (let attempt = 0; ; attempt++) {
@@ -4328,7 +4539,7 @@ async function releaseTurn(profile, turn, liveness) {
4328
4539
  try {
4329
4540
  await markTurn((0, import_node_path.join)(turn.directory, String(turn.slot)), turn.token, "done");
4330
4541
  } catch {
4331
- settleOwnTurn(turn.directory, turn.slot, turn.token);
4542
+ await settleOwnTurn(turn.directory, turn.slot, turn.token);
4332
4543
  return;
4333
4544
  }
4334
4545
  await collectTurns(profile, turn, liveness).catch(() => void 0);