@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
package/coverage.json
CHANGED
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
{ "command": "share list", "status": "legacy" },
|
|
66
66
|
{ "command": "share revoke <id>", "status": "legacy" },
|
|
67
67
|
{ "command": "share show <id>", "status": "legacy" },
|
|
68
|
+
{ "command": "share versions <artifact-id>", "status": "legacy" },
|
|
68
69
|
{ "command": "node", "status": "legacy" },
|
|
69
70
|
{ "command": "node health", "status": "legacy" },
|
|
70
71
|
{ "command": "node version", "status": "legacy" },
|
package/dist/artifacts.cjs
CHANGED
|
@@ -176,6 +176,7 @@ var import_sdk_services = require("@tinycloud/sdk-services");
|
|
|
176
176
|
var DEFAULT_MANIFEST_SPACE = "applications";
|
|
177
177
|
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
178
178
|
kv: "tinycloud.kv",
|
|
179
|
+
space: "tinycloud.space",
|
|
179
180
|
sql: "tinycloud.sql",
|
|
180
181
|
duckdb: "tinycloud.duckdb",
|
|
181
182
|
capabilities: "tinycloud.capabilities",
|
|
@@ -430,14 +431,18 @@ var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIE
|
|
|
430
431
|
var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
|
|
431
432
|
var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
|
|
432
433
|
function processWideContext(key) {
|
|
433
|
-
var
|
|
434
|
+
var _a4;
|
|
434
435
|
const registry = globalThis;
|
|
435
|
-
return registry[
|
|
436
|
+
return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new import_node_async_hooks.AsyncLocalStorage());
|
|
436
437
|
}
|
|
437
438
|
var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
|
|
438
439
|
var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
|
|
440
|
+
var pinnedProfiles = processWideContext("tinycloud.operations.pinnedProfiles.v2");
|
|
441
|
+
var pinnedHomes = processWideContext("tinycloud.operations.pinnedHomes.v2");
|
|
439
442
|
var _a;
|
|
440
|
-
var
|
|
443
|
+
var canonicalHomes = globalThis[_a = /* @__PURE__ */ Symbol.for("tinycloud.operations.canonicalHomes.v1")] ?? (globalThis[_a] = /* @__PURE__ */ new Map());
|
|
444
|
+
var _a2;
|
|
445
|
+
var processInstanceId = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a2] = (0, import_node_crypto.randomUUID)());
|
|
441
446
|
var cachedProcessOrigin;
|
|
442
447
|
function processOrigin() {
|
|
443
448
|
if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
|
|
@@ -615,9 +620,14 @@ function tinycloudHomePath() {
|
|
|
615
620
|
function profilesPath() {
|
|
616
621
|
return (0, import_node_path.join)(tinycloudHomePath(), "profiles");
|
|
617
622
|
}
|
|
618
|
-
function
|
|
623
|
+
function namedProfilePath(profile) {
|
|
619
624
|
return (0, import_node_path.join)(profilesPath(), validateProfileName(profile));
|
|
620
625
|
}
|
|
626
|
+
function profilePath(profile) {
|
|
627
|
+
const named = namedProfilePath(profile);
|
|
628
|
+
const pinned = pinnedProfiles.getStore()?.get(named);
|
|
629
|
+
return pinned ? livePinnedPath(profile, pinned) : named;
|
|
630
|
+
}
|
|
621
631
|
function profileConfigPath(profile) {
|
|
622
632
|
return (0, import_node_path.join)(profilePath(profile), "profile.json");
|
|
623
633
|
}
|
|
@@ -628,10 +638,12 @@ function profileStoreMetadataPath(profile, store) {
|
|
|
628
638
|
return `${profileStorePath(profile, store)}.metadata.json`;
|
|
629
639
|
}
|
|
630
640
|
function profileLockPath(profile) {
|
|
631
|
-
return (0, import_node_path.join)(
|
|
641
|
+
return (0, import_node_path.join)(namedProfilePath(profile), ".lock");
|
|
632
642
|
}
|
|
633
643
|
function profileTurnLockPath(profile) {
|
|
634
|
-
|
|
644
|
+
const home = tinycloudHomePath();
|
|
645
|
+
const pinned = pinnedHomes.getStore()?.get(home);
|
|
646
|
+
return (0, import_node_path.join)(pinned ? livePinnedPath(profile, pinned) : home, "profile-locks", validateProfileName(profile));
|
|
635
647
|
}
|
|
636
648
|
async function readJson(filePath) {
|
|
637
649
|
try {
|
|
@@ -641,6 +653,44 @@ async function readJson(filePath) {
|
|
|
641
653
|
throw error;
|
|
642
654
|
}
|
|
643
655
|
}
|
|
656
|
+
async function readProfileJson(profile, filePath) {
|
|
657
|
+
const before = process.platform !== "linux" ? await (0, import_promises.lstat)(filePath, { bigint: true }).catch((error) => {
|
|
658
|
+
if (error.code === "ENOENT") return null;
|
|
659
|
+
throw error;
|
|
660
|
+
}) : null;
|
|
661
|
+
if (process.platform !== "linux" && !before) return null;
|
|
662
|
+
if (before && !before.isFile()) throw new ProfileSymlinkError(profile);
|
|
663
|
+
const dev = before && exactFileId(before.dev);
|
|
664
|
+
const ino = before && exactFileId(before.ino);
|
|
665
|
+
try {
|
|
666
|
+
const handle = await (0, import_promises.open)(filePath, import_node_fs.constants.O_RDONLY | (import_node_fs.constants.O_NOFOLLOW ?? 0));
|
|
667
|
+
try {
|
|
668
|
+
if (before) {
|
|
669
|
+
const opened = await handle.stat({ bigint: true });
|
|
670
|
+
if (!opened.isFile() || dev === null || ino === null || dev !== exactFileId(opened.dev) || ino !== exactFileId(opened.ino)) {
|
|
671
|
+
throw new ProfileSymlinkError(profile, "changed while opening its file");
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const contents = await handle.readFile("utf8");
|
|
675
|
+
if (before) {
|
|
676
|
+
const named = await (0, import_promises.lstat)(filePath, { bigint: true }).catch((error) => {
|
|
677
|
+
if (error.code === "ENOENT") return null;
|
|
678
|
+
throw error;
|
|
679
|
+
});
|
|
680
|
+
if (!named?.isFile() || dev !== exactFileId(named.dev) || ino !== exactFileId(named.ino)) {
|
|
681
|
+
throw new ProfileSymlinkError(profile, "changed while reading its file");
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return JSON.parse(contents);
|
|
685
|
+
} finally {
|
|
686
|
+
await handle.close();
|
|
687
|
+
}
|
|
688
|
+
} catch (error) {
|
|
689
|
+
if (isErrno(error, "ENOENT")) return null;
|
|
690
|
+
if (isErrno(error, "ELOOP") || isErrno(error, "ENOTDIR")) throw new ProfileSymlinkError(profile);
|
|
691
|
+
throw error;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
644
694
|
async function writeJsonAtomic(filePath, value) {
|
|
645
695
|
await replaceJson(filePath, value, false);
|
|
646
696
|
}
|
|
@@ -677,43 +727,194 @@ async function readAuthRequests(profile) {
|
|
|
677
727
|
return (await readProfileStore(profile, "auth-requests")).records;
|
|
678
728
|
}
|
|
679
729
|
async function readStoreMetadata(profile, store) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
730
|
+
return withPinnedProfileDirectory(profile, async (directory) => {
|
|
731
|
+
const metadata = directory ? await readProfileJson(profile, profileStoreMetadataPath(profile, store)) : null;
|
|
732
|
+
if (metadata === null) return { formatVersion: 1 };
|
|
733
|
+
if (typeof metadata !== "object" || metadata.formatVersion !== 1) {
|
|
734
|
+
throw new TypeError(`Unsupported store format for "${store}".`);
|
|
735
|
+
}
|
|
736
|
+
return metadata;
|
|
737
|
+
});
|
|
686
738
|
}
|
|
687
739
|
async function readProfileStore(profile, store) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
740
|
+
return withPinnedProfileDirectory(profile, async (directory) => {
|
|
741
|
+
if (!directory) return { formatVersion: 1, records: [] };
|
|
742
|
+
const [metadata, raw] = await Promise.all([
|
|
743
|
+
readStoreMetadata(profile, store),
|
|
744
|
+
readProfileJson(profile, profileStorePath(profile, store))
|
|
745
|
+
]);
|
|
746
|
+
return {
|
|
747
|
+
formatVersion: metadata.formatVersion,
|
|
748
|
+
records: Array.isArray(raw) ? raw : []
|
|
749
|
+
};
|
|
750
|
+
});
|
|
696
751
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
752
|
+
var ProfileSymlinkError = class extends Error {
|
|
753
|
+
constructor(profile, detail = "must be a real directory, not a symlink or another file type") {
|
|
754
|
+
super(`Profile "${profile}" ${detail}.`);
|
|
755
|
+
this.code = "PROFILE_SYMLINK";
|
|
756
|
+
this.name = "ProfileSymlinkError";
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
function exactFileId(value) {
|
|
760
|
+
if (typeof value === "bigint") return value >= 0n ? value : null;
|
|
761
|
+
return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : null;
|
|
762
|
+
}
|
|
763
|
+
function isPinnedLive(pinned) {
|
|
764
|
+
if (!pinned.active) return false;
|
|
709
765
|
try {
|
|
710
|
-
|
|
766
|
+
const stats = (0, import_node_fs.fstatSync)(pinned.fd, { bigint: true });
|
|
767
|
+
return exactFileId(stats.dev) === pinned.dev && exactFileId(stats.ino) === pinned.ino;
|
|
768
|
+
} catch {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function livePinnedPath(profile, pinned) {
|
|
773
|
+
if (isPinnedLive(pinned)) return pinned.path;
|
|
774
|
+
throw new ProfileSymlinkError(profile, "its pinned directory is no longer active or changed during use");
|
|
775
|
+
}
|
|
776
|
+
async function withPinnedProfileDirectory(profile, action, options = {}) {
|
|
777
|
+
const named = namedProfilePath(profile);
|
|
778
|
+
const existing = pinnedProfiles.getStore()?.get(named);
|
|
779
|
+
if (existing) return action(livePinnedPath(profile, existing));
|
|
780
|
+
if (process.platform !== "linux") {
|
|
781
|
+
if (options.create) await (0, import_promises.mkdir)(named, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
782
|
+
const stats = await (0, import_promises.lstat)(named).catch((error) => {
|
|
783
|
+
if (error.code === "ENOENT") return null;
|
|
784
|
+
throw error;
|
|
785
|
+
});
|
|
786
|
+
if (!stats) return action(null);
|
|
787
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) throw new ProfileSymlinkError(profile);
|
|
788
|
+
return action(named);
|
|
789
|
+
}
|
|
790
|
+
const home = tinycloudHomePath();
|
|
791
|
+
const handles = [];
|
|
792
|
+
const fdPath = (fd) => `/proc/self/fd/${fd}`;
|
|
793
|
+
const flags = import_node_fs.constants.O_RDONLY | import_node_fs.constants.O_DIRECTORY | import_node_fs.constants.O_NOFOLLOW;
|
|
794
|
+
try {
|
|
795
|
+
if (options.create) await (0, import_promises.mkdir)(home, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
796
|
+
const resolved = canonicalHomes.get(home) ?? await (0, import_promises.realpath)(home).catch((error) => {
|
|
797
|
+
if (error.code === "ENOENT" && !options.create) return null;
|
|
798
|
+
if (error.code === "ELOOP" || error.code === "ENOTDIR") throw new ProfileSymlinkError(profile);
|
|
799
|
+
throw error;
|
|
800
|
+
});
|
|
801
|
+
if (!resolved) return await action(null);
|
|
802
|
+
const root = canonicalHomes.get(home) ?? resolved;
|
|
803
|
+
canonicalHomes.set(home, root);
|
|
804
|
+
const segments = [root, "profiles", validateProfileName(profile)];
|
|
805
|
+
for (let index = 0; index < segments.length; index++) {
|
|
806
|
+
const path = handles.length ? (0, import_node_path.join)(fdPath(handles[handles.length - 1].fd), segments[index]) : segments[index];
|
|
807
|
+
if (options.create && handles.length) {
|
|
808
|
+
await (0, import_promises.mkdir)(path, { mode: PRIVATE_DIR_MODE }).catch((error) => {
|
|
809
|
+
if (error.code !== "EEXIST") throw error;
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
const handle = await (0, import_promises.open)(path, index === 1 ? flags & ~import_node_fs.constants.O_NOFOLLOW : flags).catch((error) => {
|
|
813
|
+
if (error.code === "ENOENT" && !options.create) return null;
|
|
814
|
+
if (error.code === "ELOOP" || error.code === "ENOTDIR") throw new ProfileSymlinkError(profile);
|
|
815
|
+
throw error;
|
|
816
|
+
});
|
|
817
|
+
if (!handle) return await action(null);
|
|
818
|
+
handles.push(handle);
|
|
819
|
+
if (options.create) await handle.chmod(PRIVATE_DIR_MODE);
|
|
820
|
+
}
|
|
821
|
+
const opened = handles[2];
|
|
822
|
+
const [actual, namedStats] = await Promise.all([opened.stat({ bigint: true }), (0, import_promises.lstat)(named, { bigint: true })]);
|
|
823
|
+
const dev = exactFileId(actual.dev);
|
|
824
|
+
const ino = exactFileId(actual.ino);
|
|
825
|
+
if (!namedStats.isDirectory() || dev === null || ino === null || dev !== exactFileId(namedStats.dev) || ino !== exactFileId(namedStats.ino)) {
|
|
826
|
+
throw new ProfileSymlinkError(profile, "changed while opening its directory");
|
|
827
|
+
}
|
|
828
|
+
const directory = fdPath(opened.fd);
|
|
829
|
+
const pinned = {
|
|
830
|
+
path: directory,
|
|
831
|
+
fd: opened.fd,
|
|
832
|
+
dev,
|
|
833
|
+
ino,
|
|
834
|
+
active: true
|
|
835
|
+
};
|
|
836
|
+
const homeHandle = handles[0];
|
|
837
|
+
const homeStats = await homeHandle.stat({ bigint: true });
|
|
838
|
+
const homeDev = exactFileId(homeStats.dev);
|
|
839
|
+
const homeIno = exactFileId(homeStats.ino);
|
|
840
|
+
if (homeDev === null || homeIno === null) throw new ProfileSymlinkError(profile, "has an unverified home directory identity");
|
|
841
|
+
const homePinned = {
|
|
842
|
+
path: fdPath(homeHandle.fd),
|
|
843
|
+
fd: homeHandle.fd,
|
|
844
|
+
dev: homeDev,
|
|
845
|
+
ino: homeIno,
|
|
846
|
+
active: true
|
|
847
|
+
};
|
|
848
|
+
const context = new Map(pinnedProfiles.getStore());
|
|
849
|
+
context.set(named, pinned);
|
|
850
|
+
const homes = new Map(pinnedHomes.getStore());
|
|
851
|
+
homes.set(home, homePinned);
|
|
852
|
+
try {
|
|
853
|
+
return await pinnedHomes.run(homes, () => pinnedProfiles.run(context, () => action(directory)));
|
|
854
|
+
} finally {
|
|
855
|
+
pinned.active = false;
|
|
856
|
+
homePinned.active = false;
|
|
857
|
+
}
|
|
711
858
|
} finally {
|
|
712
|
-
|
|
713
|
-
await acquired.release();
|
|
714
|
-
if (acquired.deletedWhileWaiting) await (0, import_promises.rmdir)(profilePath(normalizedProfile)).catch(() => void 0);
|
|
859
|
+
for (const handle of handles.reverse()) await handle.close();
|
|
715
860
|
}
|
|
716
861
|
}
|
|
862
|
+
async function assertRealProfileDirectory(profile) {
|
|
863
|
+
const named = namedProfilePath(profile);
|
|
864
|
+
const pinned = pinnedProfiles.getStore()?.get(named);
|
|
865
|
+
if (pinned) {
|
|
866
|
+
livePinnedPath(profile, pinned);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const stats = await (0, import_promises.lstat)(named).catch((error) => {
|
|
870
|
+
if (error.code === "ENOENT") return null;
|
|
871
|
+
throw error;
|
|
872
|
+
});
|
|
873
|
+
if (stats && (!stats.isDirectory() || stats.isSymbolicLink())) throw new ProfileSymlinkError(profile);
|
|
874
|
+
}
|
|
875
|
+
async function withProfileLock(profile, action, options = {}) {
|
|
876
|
+
const normalizedProfile = validateProfileName(profile);
|
|
877
|
+
const lockPath = profileLockPath(normalizedProfile);
|
|
878
|
+
const held = (heldProfileLocks.getStore() ?? []).filter((ownership) => ownership.active);
|
|
879
|
+
if (held.some((ownership) => ownership.lockPath === lockPath)) {
|
|
880
|
+
await assertRealProfileDirectory(normalizedProfile);
|
|
881
|
+
return action();
|
|
882
|
+
}
|
|
883
|
+
const named = namedProfilePath(normalizedProfile);
|
|
884
|
+
const inherited = pinnedProfiles.getStore()?.get(named);
|
|
885
|
+
if (inherited && !isPinnedLive(inherited)) {
|
|
886
|
+
const profiles = new Map(pinnedProfiles.getStore());
|
|
887
|
+
profiles.delete(named);
|
|
888
|
+
const homes = new Map(pinnedHomes.getStore());
|
|
889
|
+
homes.delete(tinycloudHomePath());
|
|
890
|
+
return pinnedHomes.run(homes, () => pinnedProfiles.run(profiles, () => withProfileLock(normalizedProfile, action, options)));
|
|
891
|
+
}
|
|
892
|
+
return withPinnedProfileDirectory(normalizedProfile, async (directory) => {
|
|
893
|
+
if (!directory) throw new ProfileDeletedError(normalizedProfile);
|
|
894
|
+
const acquired = await acquireProfileLock(normalizedProfile, options);
|
|
895
|
+
const ownership = {
|
|
896
|
+
lockPath,
|
|
897
|
+
turnSlot: acquired.turnSlot,
|
|
898
|
+
deletedWhileWaiting: acquired.deletedWhileWaiting,
|
|
899
|
+
active: true
|
|
900
|
+
};
|
|
901
|
+
try {
|
|
902
|
+
return await heldProfileLocks.run([...held, ownership], action);
|
|
903
|
+
} finally {
|
|
904
|
+
ownership.active = false;
|
|
905
|
+
await acquired.release();
|
|
906
|
+
if (ownership.deletedProfile || acquired.deletedWhileWaiting) {
|
|
907
|
+
const current = await (0, import_promises.lstat)(namedProfilePath(normalizedProfile), { bigint: true }).catch(() => null);
|
|
908
|
+
const original = await (0, import_promises.stat)(directory, { bigint: true }).catch(() => null);
|
|
909
|
+
const currentDev = current && exactFileId(current.dev);
|
|
910
|
+
const currentIno = current && exactFileId(current.ino);
|
|
911
|
+
if (currentDev !== null && currentIno !== null && original && currentDev === exactFileId(original.dev) && currentIno === exactFileId(original.ino)) {
|
|
912
|
+
await (0, import_promises.rmdir)(namedProfilePath(normalizedProfile)).catch(() => void 0);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}, { create: !options.requireProfile });
|
|
917
|
+
}
|
|
717
918
|
async function refuseWriteToDeletedProfile(profile) {
|
|
718
919
|
const lockPath = profileLockPath(profile);
|
|
719
920
|
const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
|
|
@@ -746,17 +947,15 @@ async function acquireProfileLock(profile, options) {
|
|
|
746
947
|
retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
|
|
747
948
|
};
|
|
748
949
|
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
|
|
749
|
-
const lockPath =
|
|
950
|
+
const lockPath = (0, import_node_path.join)(profilePath(profile), ".lock");
|
|
750
951
|
const turnDirectory = profileTurnLockPath(profile);
|
|
751
952
|
const directory = profilePath(profile);
|
|
953
|
+
await assertRealProfileDirectory(profile);
|
|
752
954
|
if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
753
955
|
if (!options.requireProfile) await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
956
|
+
await assertRealProfileDirectory(profile);
|
|
754
957
|
await (0, import_promises.mkdir)((0, import_node_path.dirname)(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
755
|
-
|
|
756
|
-
await (0, import_promises.chmod)(path, PRIVATE_DIR_MODE).catch((error) => {
|
|
757
|
-
if (path !== directory || !isErrno(error, "ENOENT")) throw error;
|
|
758
|
-
});
|
|
759
|
-
}
|
|
958
|
+
await (0, import_promises.chmod)(directory, PRIVATE_DIR_MODE);
|
|
760
959
|
const liveness = { directory: turnDirectory, held: void 0 };
|
|
761
960
|
const closeAcquisitionLiveness = async () => {
|
|
762
961
|
if (liveness.held) await closeLiveness(liveness.held);
|
|
@@ -776,6 +975,7 @@ async function acquireProfileLock(profile, options) {
|
|
|
776
975
|
}
|
|
777
976
|
const deletion = await readJson((0, import_node_path.join)(turnDirectory, "deleted.json")).catch(() => null);
|
|
778
977
|
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
978
|
+
if (deletedWhileWaiting && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
779
979
|
while (true) {
|
|
780
980
|
const token = (0, import_node_crypto.randomUUID)();
|
|
781
981
|
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
|
|
@@ -812,8 +1012,8 @@ async function waitOrTimeOut(profile, deadline, detail) {
|
|
|
812
1012
|
await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
|
|
813
1013
|
}
|
|
814
1014
|
var TURN = /^(?:0|[1-9][0-9]*)$/;
|
|
815
|
-
var
|
|
816
|
-
var relinquishedTurns = globalThis[
|
|
1015
|
+
var _a3;
|
|
1016
|
+
var relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
|
|
817
1017
|
var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
|
|
818
1018
|
var SETTLE_RETRY_MS = 1e3;
|
|
819
1019
|
var SETTLE_RETRIES = 300;
|
|
@@ -927,7 +1127,7 @@ async function takeTurn(profile, directory, latest, liveness) {
|
|
|
927
1127
|
if (published) await markTurn(path, token, "done");
|
|
928
1128
|
return null;
|
|
929
1129
|
} catch (error) {
|
|
930
|
-
if (published) settleOwnTurn(directory, slot, token);
|
|
1130
|
+
if (published) await settleOwnTurn(directory, slot, token);
|
|
931
1131
|
throw error;
|
|
932
1132
|
}
|
|
933
1133
|
}
|
|
@@ -938,17 +1138,25 @@ async function settleAbandonedTurn(directory, abandoned) {
|
|
|
938
1138
|
}
|
|
939
1139
|
await markTurn(path, abandoned.token, "done");
|
|
940
1140
|
}
|
|
941
|
-
function settleOwnTurn(directory, slot, token
|
|
1141
|
+
async function settleOwnTurn(directory, slot, token) {
|
|
942
1142
|
relinquishedTurns.add(token);
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1143
|
+
const pinned = await pinDirectory(directory).catch(() => null);
|
|
1144
|
+
const path = pinned?.path(".") ?? directory;
|
|
1145
|
+
let retries = SETTLE_RETRIES;
|
|
1146
|
+
const attempt = () => {
|
|
1147
|
+
void (async () => {
|
|
1148
|
+
try {
|
|
1149
|
+
const turn = await readTurn(path, slot);
|
|
1150
|
+
if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(path, turn);
|
|
1151
|
+
relinquishedTurns.delete(token);
|
|
1152
|
+
await pinned?.close();
|
|
1153
|
+
} catch {
|
|
1154
|
+
if (retries-- > 0) setTimeout(attempt, SETTLE_RETRY_MS).unref();
|
|
1155
|
+
else await pinned?.close();
|
|
1156
|
+
}
|
|
1157
|
+
})();
|
|
1158
|
+
};
|
|
1159
|
+
attempt();
|
|
952
1160
|
}
|
|
953
1161
|
async function markTurn(path, token, kind) {
|
|
954
1162
|
for (let attempt = 0; ; attempt++) {
|
|
@@ -993,7 +1201,7 @@ async function releaseTurn(profile, turn, liveness) {
|
|
|
993
1201
|
try {
|
|
994
1202
|
await markTurn((0, import_node_path.join)(turn.directory, String(turn.slot)), turn.token, "done");
|
|
995
1203
|
} catch {
|
|
996
|
-
settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
1204
|
+
await settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
997
1205
|
return;
|
|
998
1206
|
}
|
|
999
1207
|
await collectTurns(profile, turn, liveness).catch(() => void 0);
|