@tinycloud/operations 0.6.1-beta.4 → 0.6.1-beta.6
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/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 +268 -59
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.js +271 -61
- 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 +268 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +271 -61
- 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 +2 -2
package/dist/artifacts.cjs
CHANGED
|
@@ -431,14 +431,18 @@ var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIE
|
|
|
431
431
|
var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
|
|
432
432
|
var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
|
|
433
433
|
function processWideContext(key) {
|
|
434
|
-
var
|
|
434
|
+
var _a4;
|
|
435
435
|
const registry = globalThis;
|
|
436
|
-
return registry[
|
|
436
|
+
return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new import_node_async_hooks.AsyncLocalStorage());
|
|
437
437
|
}
|
|
438
438
|
var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
|
|
439
439
|
var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
|
|
440
|
+
var pinnedProfiles = processWideContext("tinycloud.operations.pinnedProfiles.v2");
|
|
441
|
+
var pinnedHomes = processWideContext("tinycloud.operations.pinnedHomes.v2");
|
|
440
442
|
var _a;
|
|
441
|
-
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)());
|
|
442
446
|
var cachedProcessOrigin;
|
|
443
447
|
function processOrigin() {
|
|
444
448
|
if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
|
|
@@ -616,9 +620,14 @@ function tinycloudHomePath() {
|
|
|
616
620
|
function profilesPath() {
|
|
617
621
|
return (0, import_node_path.join)(tinycloudHomePath(), "profiles");
|
|
618
622
|
}
|
|
619
|
-
function
|
|
623
|
+
function namedProfilePath(profile) {
|
|
620
624
|
return (0, import_node_path.join)(profilesPath(), validateProfileName(profile));
|
|
621
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
|
+
}
|
|
622
631
|
function profileConfigPath(profile) {
|
|
623
632
|
return (0, import_node_path.join)(profilePath(profile), "profile.json");
|
|
624
633
|
}
|
|
@@ -629,10 +638,12 @@ function profileStoreMetadataPath(profile, store) {
|
|
|
629
638
|
return `${profileStorePath(profile, store)}.metadata.json`;
|
|
630
639
|
}
|
|
631
640
|
function profileLockPath(profile) {
|
|
632
|
-
return (0, import_node_path.join)(
|
|
641
|
+
return (0, import_node_path.join)(namedProfilePath(profile), ".lock");
|
|
633
642
|
}
|
|
634
643
|
function profileTurnLockPath(profile) {
|
|
635
|
-
|
|
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));
|
|
636
647
|
}
|
|
637
648
|
async function readJson(filePath) {
|
|
638
649
|
try {
|
|
@@ -642,6 +653,44 @@ async function readJson(filePath) {
|
|
|
642
653
|
throw error;
|
|
643
654
|
}
|
|
644
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
|
+
}
|
|
645
694
|
async function writeJsonAtomic(filePath, value) {
|
|
646
695
|
await replaceJson(filePath, value, false);
|
|
647
696
|
}
|
|
@@ -678,43 +727,194 @@ async function readAuthRequests(profile) {
|
|
|
678
727
|
return (await readProfileStore(profile, "auth-requests")).records;
|
|
679
728
|
}
|
|
680
729
|
async function readStoreMetadata(profile, store) {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
+
});
|
|
687
738
|
}
|
|
688
739
|
async function readProfileStore(profile, store) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
+
});
|
|
697
751
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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;
|
|
710
765
|
try {
|
|
711
|
-
|
|
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
|
+
}
|
|
712
858
|
} finally {
|
|
713
|
-
|
|
714
|
-
await acquired.release();
|
|
715
|
-
if (acquired.deletedWhileWaiting) await (0, import_promises.rmdir)(profilePath(normalizedProfile)).catch(() => void 0);
|
|
859
|
+
for (const handle of handles.reverse()) await handle.close();
|
|
716
860
|
}
|
|
717
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
|
+
}
|
|
718
918
|
async function refuseWriteToDeletedProfile(profile) {
|
|
719
919
|
const lockPath = profileLockPath(profile);
|
|
720
920
|
const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
|
|
@@ -747,17 +947,15 @@ async function acquireProfileLock(profile, options) {
|
|
|
747
947
|
retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
|
|
748
948
|
};
|
|
749
949
|
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
|
|
750
|
-
const lockPath =
|
|
950
|
+
const lockPath = (0, import_node_path.join)(profilePath(profile), ".lock");
|
|
751
951
|
const turnDirectory = profileTurnLockPath(profile);
|
|
752
952
|
const directory = profilePath(profile);
|
|
953
|
+
await assertRealProfileDirectory(profile);
|
|
753
954
|
if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
754
955
|
if (!options.requireProfile) await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
956
|
+
await assertRealProfileDirectory(profile);
|
|
755
957
|
await (0, import_promises.mkdir)((0, import_node_path.dirname)(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
756
|
-
|
|
757
|
-
await (0, import_promises.chmod)(path, PRIVATE_DIR_MODE).catch((error) => {
|
|
758
|
-
if (path !== directory || !isErrno(error, "ENOENT")) throw error;
|
|
759
|
-
});
|
|
760
|
-
}
|
|
958
|
+
await (0, import_promises.chmod)(directory, PRIVATE_DIR_MODE);
|
|
761
959
|
const liveness = { directory: turnDirectory, held: void 0 };
|
|
762
960
|
const closeAcquisitionLiveness = async () => {
|
|
763
961
|
if (liveness.held) await closeLiveness(liveness.held);
|
|
@@ -777,6 +975,7 @@ async function acquireProfileLock(profile, options) {
|
|
|
777
975
|
}
|
|
778
976
|
const deletion = await readJson((0, import_node_path.join)(turnDirectory, "deleted.json")).catch(() => null);
|
|
779
977
|
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
978
|
+
if (deletedWhileWaiting && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
780
979
|
while (true) {
|
|
781
980
|
const token = (0, import_node_crypto.randomUUID)();
|
|
782
981
|
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
|
|
@@ -813,8 +1012,8 @@ async function waitOrTimeOut(profile, deadline, detail) {
|
|
|
813
1012
|
await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
|
|
814
1013
|
}
|
|
815
1014
|
var TURN = /^(?:0|[1-9][0-9]*)$/;
|
|
816
|
-
var
|
|
817
|
-
var relinquishedTurns = globalThis[
|
|
1015
|
+
var _a3;
|
|
1016
|
+
var relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
|
|
818
1017
|
var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
|
|
819
1018
|
var SETTLE_RETRY_MS = 1e3;
|
|
820
1019
|
var SETTLE_RETRIES = 300;
|
|
@@ -928,7 +1127,7 @@ async function takeTurn(profile, directory, latest, liveness) {
|
|
|
928
1127
|
if (published) await markTurn(path, token, "done");
|
|
929
1128
|
return null;
|
|
930
1129
|
} catch (error) {
|
|
931
|
-
if (published) settleOwnTurn(directory, slot, token);
|
|
1130
|
+
if (published) await settleOwnTurn(directory, slot, token);
|
|
932
1131
|
throw error;
|
|
933
1132
|
}
|
|
934
1133
|
}
|
|
@@ -939,17 +1138,25 @@ async function settleAbandonedTurn(directory, abandoned) {
|
|
|
939
1138
|
}
|
|
940
1139
|
await markTurn(path, abandoned.token, "done");
|
|
941
1140
|
}
|
|
942
|
-
function settleOwnTurn(directory, slot, token
|
|
1141
|
+
async function settleOwnTurn(directory, slot, token) {
|
|
943
1142
|
relinquishedTurns.add(token);
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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();
|
|
953
1160
|
}
|
|
954
1161
|
async function markTurn(path, token, kind) {
|
|
955
1162
|
for (let attempt = 0; ; attempt++) {
|
|
@@ -994,7 +1201,7 @@ async function releaseTurn(profile, turn, liveness) {
|
|
|
994
1201
|
try {
|
|
995
1202
|
await markTurn((0, import_node_path.join)(turn.directory, String(turn.slot)), turn.token, "done");
|
|
996
1203
|
} catch {
|
|
997
|
-
settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
1204
|
+
await settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
998
1205
|
return;
|
|
999
1206
|
}
|
|
1000
1207
|
await collectTurns(profile, turn, liveness).catch(() => void 0);
|