@tinycloud/operations 0.6.1-beta.3 → 0.6.1-beta.30
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.
- package/coverage.json +1 -0
- package/dist/artifacts.cjs +263 -56
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.js +266 -58
- package/dist/artifacts.js.map +1 -1
- package/dist/cli-runtime.cjs +284 -69
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.js +287 -71
- package/dist/cli-runtime.js.map +1 -1
- package/dist/delegation-binding.cjs +268 -58
- package/dist/delegation-binding.cjs.map +1 -1
- package/dist/delegation-binding.js +271 -60
- package/dist/delegation-binding.js.map +1 -1
- package/dist/index.cjs +284 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +287 -71
- package/dist/index.js.map +1 -1
- package/dist/profile.cjs +160 -10
- package/dist/profile.cjs.map +1 -1
- package/dist/profile.js +163 -12
- package/dist/profile.js.map +1 -1
- package/dist/state.cjs +277 -58
- package/dist/state.cjs.map +1 -1
- package/dist/state.d.cts +12 -1
- package/dist/state.d.ts +12 -1
- package/dist/state.js +276 -60
- package/dist/state.js.map +1 -1
- package/package.json +5 -5
|
@@ -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
|
|
3747
|
+
var _a5;
|
|
3747
3748
|
const registry = globalThis;
|
|
3748
|
-
return registry[
|
|
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
|
|
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
|
|
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(
|
|
3957
|
+
return join(namedProfilePath(profile), ".lock");
|
|
3948
3958
|
}
|
|
3949
3959
|
function profileTurnLockPath(profile) {
|
|
3950
|
-
|
|
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
|
-
|
|
3994
|
-
|
|
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
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
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
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
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
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
4136
|
-
var relinquishedTurns = globalThis[
|
|
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
|
|
4464
|
+
async function settleOwnTurn(directory, slot, token) {
|
|
4262
4465
|
relinquishedTurns.add(token);
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
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);
|