@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
|
@@ -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
|
|
3747
|
+
var _a5;
|
|
3746
3748
|
const registry = globalThis;
|
|
3747
|
-
return registry[
|
|
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
|
|
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
|
|
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(
|
|
3957
|
+
return join(namedProfilePath(profile), ".lock");
|
|
3947
3958
|
}
|
|
3948
3959
|
function profileTurnLockPath(profile) {
|
|
3949
|
-
|
|
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
|
-
|
|
3993
|
-
|
|
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
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
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
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
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
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
4135
|
-
var relinquishedTurns = globalThis[
|
|
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
|
|
4464
|
+
async function settleOwnTurn(directory, slot, token) {
|
|
4261
4465
|
relinquishedTurns.add(token);
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
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);
|