@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.
- package/coverage.json +1 -0
- package/dist/artifacts.cjs +264 -56
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.js +267 -58
- package/dist/artifacts.js.map +1 -1
- package/dist/cli-runtime.cjs +285 -69
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.js +288 -71
- package/dist/cli-runtime.js.map +1 -1
- package/dist/delegation-binding.cjs +269 -58
- package/dist/delegation-binding.cjs.map +1 -1
- package/dist/delegation-binding.js +272 -60
- package/dist/delegation-binding.js.map +1 -1
- package/dist/index.cjs +285 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +288 -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 +4 -4
|
@@ -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
|
|
3762
|
+
var _a5;
|
|
3762
3763
|
const registry = globalThis;
|
|
3763
|
-
return registry[
|
|
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
|
|
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
|
|
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)(
|
|
3972
|
+
return (0, import_node_path.join)(namedProfilePath(profile), ".lock");
|
|
3963
3973
|
}
|
|
3964
3974
|
function profileTurnLockPath(profile) {
|
|
3965
|
-
|
|
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
|
-
|
|
4009
|
-
|
|
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
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
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
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
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
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
4151
|
-
var relinquishedTurns = globalThis[
|
|
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
|
|
4479
|
+
async function settleOwnTurn(directory, slot, token) {
|
|
4277
4480
|
relinquishedTurns.add(token);
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
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);
|