@tinycloud/operations 0.6.1-beta.2 → 0.6.1-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/coverage.json +1 -0
- package/dist/artifacts.cjs +263 -56
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.d.cts +54 -54
- package/dist/artifacts.d.ts +54 -54
- package/dist/artifacts.js +266 -58
- package/dist/artifacts.js.map +1 -1
- package/dist/cli-runtime.cjs +284 -69
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.js +287 -71
- package/dist/cli-runtime.js.map +1 -1
- package/dist/delegation-binding.cjs +268 -58
- package/dist/delegation-binding.cjs.map +1 -1
- package/dist/delegation-binding.js +271 -60
- package/dist/delegation-binding.js.map +1 -1
- package/dist/index.cjs +284 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +287 -71
- package/dist/index.js.map +1 -1
- package/dist/profile.cjs +160 -10
- package/dist/profile.cjs.map +1 -1
- package/dist/profile.js +163 -12
- package/dist/profile.js.map +1 -1
- package/dist/state.cjs +277 -58
- package/dist/state.cjs.map +1 -1
- package/dist/state.d.cts +12 -1
- package/dist/state.d.ts +12 -1
- package/dist/state.js +276 -60
- package/dist/state.js.map +1 -1
- package/package.json +4 -4
package/dist/artifacts.js
CHANGED
|
@@ -382,15 +382,16 @@ function canonicalizeCaveats(caveats) {
|
|
|
382
382
|
// src/state.ts
|
|
383
383
|
import { randomUUID } from "crypto";
|
|
384
384
|
import { AsyncLocalStorage } from "async_hooks";
|
|
385
|
-
import { readFileSync, readlinkSync } from "fs";
|
|
385
|
+
import { constants as fsConstants, fstatSync, readFileSync, readlinkSync } from "fs";
|
|
386
386
|
import {
|
|
387
387
|
chmod,
|
|
388
388
|
link,
|
|
389
|
-
lstat,
|
|
390
389
|
mkdir,
|
|
390
|
+
lstat,
|
|
391
391
|
open,
|
|
392
392
|
readFile,
|
|
393
393
|
readdir,
|
|
394
|
+
realpath,
|
|
394
395
|
rename,
|
|
395
396
|
rmdir,
|
|
396
397
|
rm,
|
|
@@ -420,14 +421,18 @@ var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIE
|
|
|
420
421
|
var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
|
|
421
422
|
var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
|
|
422
423
|
function processWideContext(key) {
|
|
423
|
-
var
|
|
424
|
+
var _a4;
|
|
424
425
|
const registry = globalThis;
|
|
425
|
-
return registry[
|
|
426
|
+
return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new AsyncLocalStorage());
|
|
426
427
|
}
|
|
427
428
|
var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
|
|
428
429
|
var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
|
|
430
|
+
var pinnedProfiles = processWideContext("tinycloud.operations.pinnedProfiles.v2");
|
|
431
|
+
var pinnedHomes = processWideContext("tinycloud.operations.pinnedHomes.v2");
|
|
429
432
|
var _a;
|
|
430
|
-
var
|
|
433
|
+
var canonicalHomes = globalThis[_a = /* @__PURE__ */ Symbol.for("tinycloud.operations.canonicalHomes.v1")] ?? (globalThis[_a] = /* @__PURE__ */ new Map());
|
|
434
|
+
var _a2;
|
|
435
|
+
var processInstanceId = globalThis[_a2 = /* @__PURE__ */ Symbol.for("tinycloud.operations.profileLockInstance.v1")] ?? (globalThis[_a2] = randomUUID());
|
|
431
436
|
var cachedProcessOrigin;
|
|
432
437
|
function processOrigin() {
|
|
433
438
|
if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
|
|
@@ -605,9 +610,14 @@ function tinycloudHomePath() {
|
|
|
605
610
|
function profilesPath() {
|
|
606
611
|
return join(tinycloudHomePath(), "profiles");
|
|
607
612
|
}
|
|
608
|
-
function
|
|
613
|
+
function namedProfilePath(profile) {
|
|
609
614
|
return join(profilesPath(), validateProfileName(profile));
|
|
610
615
|
}
|
|
616
|
+
function profilePath(profile) {
|
|
617
|
+
const named = namedProfilePath(profile);
|
|
618
|
+
const pinned = pinnedProfiles.getStore()?.get(named);
|
|
619
|
+
return pinned ? livePinnedPath(profile, pinned) : named;
|
|
620
|
+
}
|
|
611
621
|
function profileConfigPath(profile) {
|
|
612
622
|
return join(profilePath(profile), "profile.json");
|
|
613
623
|
}
|
|
@@ -618,10 +628,12 @@ function profileStoreMetadataPath(profile, store) {
|
|
|
618
628
|
return `${profileStorePath(profile, store)}.metadata.json`;
|
|
619
629
|
}
|
|
620
630
|
function profileLockPath(profile) {
|
|
621
|
-
return join(
|
|
631
|
+
return join(namedProfilePath(profile), ".lock");
|
|
622
632
|
}
|
|
623
633
|
function profileTurnLockPath(profile) {
|
|
624
|
-
|
|
634
|
+
const home = tinycloudHomePath();
|
|
635
|
+
const pinned = pinnedHomes.getStore()?.get(home);
|
|
636
|
+
return join(pinned ? livePinnedPath(profile, pinned) : home, "profile-locks", validateProfileName(profile));
|
|
625
637
|
}
|
|
626
638
|
async function readJson(filePath) {
|
|
627
639
|
try {
|
|
@@ -631,6 +643,44 @@ async function readJson(filePath) {
|
|
|
631
643
|
throw error;
|
|
632
644
|
}
|
|
633
645
|
}
|
|
646
|
+
async function readProfileJson(profile, filePath) {
|
|
647
|
+
const before = process.platform !== "linux" ? await lstat(filePath, { bigint: true }).catch((error) => {
|
|
648
|
+
if (error.code === "ENOENT") return null;
|
|
649
|
+
throw error;
|
|
650
|
+
}) : null;
|
|
651
|
+
if (process.platform !== "linux" && !before) return null;
|
|
652
|
+
if (before && !before.isFile()) throw new ProfileSymlinkError(profile);
|
|
653
|
+
const dev = before && exactFileId(before.dev);
|
|
654
|
+
const ino = before && exactFileId(before.ino);
|
|
655
|
+
try {
|
|
656
|
+
const handle = await open(filePath, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
|
|
657
|
+
try {
|
|
658
|
+
if (before) {
|
|
659
|
+
const opened = await handle.stat({ bigint: true });
|
|
660
|
+
if (!opened.isFile() || dev === null || ino === null || dev !== exactFileId(opened.dev) || ino !== exactFileId(opened.ino)) {
|
|
661
|
+
throw new ProfileSymlinkError(profile, "changed while opening its file");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const contents = await handle.readFile("utf8");
|
|
665
|
+
if (before) {
|
|
666
|
+
const named = await lstat(filePath, { bigint: true }).catch((error) => {
|
|
667
|
+
if (error.code === "ENOENT") return null;
|
|
668
|
+
throw error;
|
|
669
|
+
});
|
|
670
|
+
if (!named?.isFile() || dev !== exactFileId(named.dev) || ino !== exactFileId(named.ino)) {
|
|
671
|
+
throw new ProfileSymlinkError(profile, "changed while reading its file");
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return JSON.parse(contents);
|
|
675
|
+
} finally {
|
|
676
|
+
await handle.close();
|
|
677
|
+
}
|
|
678
|
+
} catch (error) {
|
|
679
|
+
if (isErrno(error, "ENOENT")) return null;
|
|
680
|
+
if (isErrno(error, "ELOOP") || isErrno(error, "ENOTDIR")) throw new ProfileSymlinkError(profile);
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
634
684
|
async function writeJsonAtomic(filePath, value) {
|
|
635
685
|
await replaceJson(filePath, value, false);
|
|
636
686
|
}
|
|
@@ -667,43 +717,194 @@ async function readAuthRequests(profile) {
|
|
|
667
717
|
return (await readProfileStore(profile, "auth-requests")).records;
|
|
668
718
|
}
|
|
669
719
|
async function readStoreMetadata(profile, store) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
720
|
+
return withPinnedProfileDirectory(profile, async (directory) => {
|
|
721
|
+
const metadata = directory ? await readProfileJson(profile, profileStoreMetadataPath(profile, store)) : null;
|
|
722
|
+
if (metadata === null) return { formatVersion: 1 };
|
|
723
|
+
if (typeof metadata !== "object" || metadata.formatVersion !== 1) {
|
|
724
|
+
throw new TypeError(`Unsupported store format for "${store}".`);
|
|
725
|
+
}
|
|
726
|
+
return metadata;
|
|
727
|
+
});
|
|
676
728
|
}
|
|
677
729
|
async function readProfileStore(profile, store) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
730
|
+
return withPinnedProfileDirectory(profile, async (directory) => {
|
|
731
|
+
if (!directory) return { formatVersion: 1, records: [] };
|
|
732
|
+
const [metadata, raw] = await Promise.all([
|
|
733
|
+
readStoreMetadata(profile, store),
|
|
734
|
+
readProfileJson(profile, profileStorePath(profile, store))
|
|
735
|
+
]);
|
|
736
|
+
return {
|
|
737
|
+
formatVersion: metadata.formatVersion,
|
|
738
|
+
records: Array.isArray(raw) ? raw : []
|
|
739
|
+
};
|
|
740
|
+
});
|
|
686
741
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
742
|
+
var ProfileSymlinkError = class extends Error {
|
|
743
|
+
constructor(profile, detail = "must be a real directory, not a symlink or another file type") {
|
|
744
|
+
super(`Profile "${profile}" ${detail}.`);
|
|
745
|
+
this.code = "PROFILE_SYMLINK";
|
|
746
|
+
this.name = "ProfileSymlinkError";
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
function exactFileId(value) {
|
|
750
|
+
if (typeof value === "bigint") return value >= 0n ? value : null;
|
|
751
|
+
return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : null;
|
|
752
|
+
}
|
|
753
|
+
function isPinnedLive(pinned) {
|
|
754
|
+
if (!pinned.active) return false;
|
|
699
755
|
try {
|
|
700
|
-
|
|
756
|
+
const stats = fstatSync(pinned.fd, { bigint: true });
|
|
757
|
+
return exactFileId(stats.dev) === pinned.dev && exactFileId(stats.ino) === pinned.ino;
|
|
758
|
+
} catch {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function livePinnedPath(profile, pinned) {
|
|
763
|
+
if (isPinnedLive(pinned)) return pinned.path;
|
|
764
|
+
throw new ProfileSymlinkError(profile, "its pinned directory is no longer active or changed during use");
|
|
765
|
+
}
|
|
766
|
+
async function withPinnedProfileDirectory(profile, action, options = {}) {
|
|
767
|
+
const named = namedProfilePath(profile);
|
|
768
|
+
const existing = pinnedProfiles.getStore()?.get(named);
|
|
769
|
+
if (existing) return action(livePinnedPath(profile, existing));
|
|
770
|
+
if (process.platform !== "linux") {
|
|
771
|
+
if (options.create) await mkdir(named, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
772
|
+
const stats = await lstat(named).catch((error) => {
|
|
773
|
+
if (error.code === "ENOENT") return null;
|
|
774
|
+
throw error;
|
|
775
|
+
});
|
|
776
|
+
if (!stats) return action(null);
|
|
777
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) throw new ProfileSymlinkError(profile);
|
|
778
|
+
return action(named);
|
|
779
|
+
}
|
|
780
|
+
const home = tinycloudHomePath();
|
|
781
|
+
const handles = [];
|
|
782
|
+
const fdPath = (fd) => `/proc/self/fd/${fd}`;
|
|
783
|
+
const flags = fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW;
|
|
784
|
+
try {
|
|
785
|
+
if (options.create) await mkdir(home, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
786
|
+
const resolved = canonicalHomes.get(home) ?? await realpath(home).catch((error) => {
|
|
787
|
+
if (error.code === "ENOENT" && !options.create) return null;
|
|
788
|
+
if (error.code === "ELOOP" || error.code === "ENOTDIR") throw new ProfileSymlinkError(profile);
|
|
789
|
+
throw error;
|
|
790
|
+
});
|
|
791
|
+
if (!resolved) return await action(null);
|
|
792
|
+
const root = canonicalHomes.get(home) ?? resolved;
|
|
793
|
+
canonicalHomes.set(home, root);
|
|
794
|
+
const segments = [root, "profiles", validateProfileName(profile)];
|
|
795
|
+
for (let index = 0; index < segments.length; index++) {
|
|
796
|
+
const path = handles.length ? join(fdPath(handles[handles.length - 1].fd), segments[index]) : segments[index];
|
|
797
|
+
if (options.create && handles.length) {
|
|
798
|
+
await mkdir(path, { mode: PRIVATE_DIR_MODE }).catch((error) => {
|
|
799
|
+
if (error.code !== "EEXIST") throw error;
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
const handle = await open(path, index === 1 ? flags & ~fsConstants.O_NOFOLLOW : flags).catch((error) => {
|
|
803
|
+
if (error.code === "ENOENT" && !options.create) return null;
|
|
804
|
+
if (error.code === "ELOOP" || error.code === "ENOTDIR") throw new ProfileSymlinkError(profile);
|
|
805
|
+
throw error;
|
|
806
|
+
});
|
|
807
|
+
if (!handle) return await action(null);
|
|
808
|
+
handles.push(handle);
|
|
809
|
+
if (options.create) await handle.chmod(PRIVATE_DIR_MODE);
|
|
810
|
+
}
|
|
811
|
+
const opened = handles[2];
|
|
812
|
+
const [actual, namedStats] = await Promise.all([opened.stat({ bigint: true }), lstat(named, { bigint: true })]);
|
|
813
|
+
const dev = exactFileId(actual.dev);
|
|
814
|
+
const ino = exactFileId(actual.ino);
|
|
815
|
+
if (!namedStats.isDirectory() || dev === null || ino === null || dev !== exactFileId(namedStats.dev) || ino !== exactFileId(namedStats.ino)) {
|
|
816
|
+
throw new ProfileSymlinkError(profile, "changed while opening its directory");
|
|
817
|
+
}
|
|
818
|
+
const directory = fdPath(opened.fd);
|
|
819
|
+
const pinned = {
|
|
820
|
+
path: directory,
|
|
821
|
+
fd: opened.fd,
|
|
822
|
+
dev,
|
|
823
|
+
ino,
|
|
824
|
+
active: true
|
|
825
|
+
};
|
|
826
|
+
const homeHandle = handles[0];
|
|
827
|
+
const homeStats = await homeHandle.stat({ bigint: true });
|
|
828
|
+
const homeDev = exactFileId(homeStats.dev);
|
|
829
|
+
const homeIno = exactFileId(homeStats.ino);
|
|
830
|
+
if (homeDev === null || homeIno === null) throw new ProfileSymlinkError(profile, "has an unverified home directory identity");
|
|
831
|
+
const homePinned = {
|
|
832
|
+
path: fdPath(homeHandle.fd),
|
|
833
|
+
fd: homeHandle.fd,
|
|
834
|
+
dev: homeDev,
|
|
835
|
+
ino: homeIno,
|
|
836
|
+
active: true
|
|
837
|
+
};
|
|
838
|
+
const context = new Map(pinnedProfiles.getStore());
|
|
839
|
+
context.set(named, pinned);
|
|
840
|
+
const homes = new Map(pinnedHomes.getStore());
|
|
841
|
+
homes.set(home, homePinned);
|
|
842
|
+
try {
|
|
843
|
+
return await pinnedHomes.run(homes, () => pinnedProfiles.run(context, () => action(directory)));
|
|
844
|
+
} finally {
|
|
845
|
+
pinned.active = false;
|
|
846
|
+
homePinned.active = false;
|
|
847
|
+
}
|
|
701
848
|
} finally {
|
|
702
|
-
|
|
703
|
-
await acquired.release();
|
|
704
|
-
if (acquired.deletedWhileWaiting) await rmdir(profilePath(normalizedProfile)).catch(() => void 0);
|
|
849
|
+
for (const handle of handles.reverse()) await handle.close();
|
|
705
850
|
}
|
|
706
851
|
}
|
|
852
|
+
async function assertRealProfileDirectory(profile) {
|
|
853
|
+
const named = namedProfilePath(profile);
|
|
854
|
+
const pinned = pinnedProfiles.getStore()?.get(named);
|
|
855
|
+
if (pinned) {
|
|
856
|
+
livePinnedPath(profile, pinned);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const stats = await lstat(named).catch((error) => {
|
|
860
|
+
if (error.code === "ENOENT") return null;
|
|
861
|
+
throw error;
|
|
862
|
+
});
|
|
863
|
+
if (stats && (!stats.isDirectory() || stats.isSymbolicLink())) throw new ProfileSymlinkError(profile);
|
|
864
|
+
}
|
|
865
|
+
async function withProfileLock(profile, action, options = {}) {
|
|
866
|
+
const normalizedProfile = validateProfileName(profile);
|
|
867
|
+
const lockPath = profileLockPath(normalizedProfile);
|
|
868
|
+
const held = (heldProfileLocks.getStore() ?? []).filter((ownership) => ownership.active);
|
|
869
|
+
if (held.some((ownership) => ownership.lockPath === lockPath)) {
|
|
870
|
+
await assertRealProfileDirectory(normalizedProfile);
|
|
871
|
+
return action();
|
|
872
|
+
}
|
|
873
|
+
const named = namedProfilePath(normalizedProfile);
|
|
874
|
+
const inherited = pinnedProfiles.getStore()?.get(named);
|
|
875
|
+
if (inherited && !isPinnedLive(inherited)) {
|
|
876
|
+
const profiles = new Map(pinnedProfiles.getStore());
|
|
877
|
+
profiles.delete(named);
|
|
878
|
+
const homes = new Map(pinnedHomes.getStore());
|
|
879
|
+
homes.delete(tinycloudHomePath());
|
|
880
|
+
return pinnedHomes.run(homes, () => pinnedProfiles.run(profiles, () => withProfileLock(normalizedProfile, action, options)));
|
|
881
|
+
}
|
|
882
|
+
return withPinnedProfileDirectory(normalizedProfile, async (directory) => {
|
|
883
|
+
if (!directory) throw new ProfileDeletedError(normalizedProfile);
|
|
884
|
+
const acquired = await acquireProfileLock(normalizedProfile, options);
|
|
885
|
+
const ownership = {
|
|
886
|
+
lockPath,
|
|
887
|
+
turnSlot: acquired.turnSlot,
|
|
888
|
+
deletedWhileWaiting: acquired.deletedWhileWaiting,
|
|
889
|
+
active: true
|
|
890
|
+
};
|
|
891
|
+
try {
|
|
892
|
+
return await heldProfileLocks.run([...held, ownership], action);
|
|
893
|
+
} finally {
|
|
894
|
+
ownership.active = false;
|
|
895
|
+
await acquired.release();
|
|
896
|
+
if (ownership.deletedProfile || acquired.deletedWhileWaiting) {
|
|
897
|
+
const current = await lstat(namedProfilePath(normalizedProfile), { bigint: true }).catch(() => null);
|
|
898
|
+
const original = await stat(directory, { bigint: true }).catch(() => null);
|
|
899
|
+
const currentDev = current && exactFileId(current.dev);
|
|
900
|
+
const currentIno = current && exactFileId(current.ino);
|
|
901
|
+
if (currentDev !== null && currentIno !== null && original && currentDev === exactFileId(original.dev) && currentIno === exactFileId(original.ino)) {
|
|
902
|
+
await rmdir(namedProfilePath(normalizedProfile)).catch(() => void 0);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}, { create: !options.requireProfile });
|
|
907
|
+
}
|
|
707
908
|
async function refuseWriteToDeletedProfile(profile) {
|
|
708
909
|
const lockPath = profileLockPath(profile);
|
|
709
910
|
const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
|
|
@@ -736,17 +937,15 @@ async function acquireProfileLock(profile, options) {
|
|
|
736
937
|
retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
|
|
737
938
|
};
|
|
738
939
|
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
|
|
739
|
-
const lockPath =
|
|
940
|
+
const lockPath = join(profilePath(profile), ".lock");
|
|
740
941
|
const turnDirectory = profileTurnLockPath(profile);
|
|
741
942
|
const directory = profilePath(profile);
|
|
943
|
+
await assertRealProfileDirectory(profile);
|
|
742
944
|
if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
743
945
|
if (!options.requireProfile) await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
946
|
+
await assertRealProfileDirectory(profile);
|
|
744
947
|
await mkdir(dirname(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
745
|
-
|
|
746
|
-
await chmod(path, PRIVATE_DIR_MODE).catch((error) => {
|
|
747
|
-
if (path !== directory || !isErrno(error, "ENOENT")) throw error;
|
|
748
|
-
});
|
|
749
|
-
}
|
|
948
|
+
await chmod(directory, PRIVATE_DIR_MODE);
|
|
750
949
|
const liveness = { directory: turnDirectory, held: void 0 };
|
|
751
950
|
const closeAcquisitionLiveness = async () => {
|
|
752
951
|
if (liveness.held) await closeLiveness(liveness.held);
|
|
@@ -766,6 +965,7 @@ async function acquireProfileLock(profile, options) {
|
|
|
766
965
|
}
|
|
767
966
|
const deletion = await readJson(join(turnDirectory, "deleted.json")).catch(() => null);
|
|
768
967
|
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
968
|
+
if (deletedWhileWaiting && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
769
969
|
while (true) {
|
|
770
970
|
const token = randomUUID();
|
|
771
971
|
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
|
|
@@ -802,8 +1002,8 @@ async function waitOrTimeOut(profile, deadline, detail) {
|
|
|
802
1002
|
await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
|
|
803
1003
|
}
|
|
804
1004
|
var TURN = /^(?:0|[1-9][0-9]*)$/;
|
|
805
|
-
var
|
|
806
|
-
var relinquishedTurns = globalThis[
|
|
1005
|
+
var _a3;
|
|
1006
|
+
var relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
|
|
807
1007
|
var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
|
|
808
1008
|
var SETTLE_RETRY_MS = 1e3;
|
|
809
1009
|
var SETTLE_RETRIES = 300;
|
|
@@ -917,7 +1117,7 @@ async function takeTurn(profile, directory, latest, liveness) {
|
|
|
917
1117
|
if (published) await markTurn(path, token, "done");
|
|
918
1118
|
return null;
|
|
919
1119
|
} catch (error) {
|
|
920
|
-
if (published) settleOwnTurn(directory, slot, token);
|
|
1120
|
+
if (published) await settleOwnTurn(directory, slot, token);
|
|
921
1121
|
throw error;
|
|
922
1122
|
}
|
|
923
1123
|
}
|
|
@@ -928,17 +1128,25 @@ async function settleAbandonedTurn(directory, abandoned) {
|
|
|
928
1128
|
}
|
|
929
1129
|
await markTurn(path, abandoned.token, "done");
|
|
930
1130
|
}
|
|
931
|
-
function settleOwnTurn(directory, slot, token
|
|
1131
|
+
async function settleOwnTurn(directory, slot, token) {
|
|
932
1132
|
relinquishedTurns.add(token);
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1133
|
+
const pinned = await pinDirectory(directory).catch(() => null);
|
|
1134
|
+
const path = pinned?.path(".") ?? directory;
|
|
1135
|
+
let retries = SETTLE_RETRIES;
|
|
1136
|
+
const attempt = () => {
|
|
1137
|
+
void (async () => {
|
|
1138
|
+
try {
|
|
1139
|
+
const turn = await readTurn(path, slot);
|
|
1140
|
+
if (turn !== null && turn !== "damaged" && turn.token === token && !turn.done) await settleAbandonedTurn(path, turn);
|
|
1141
|
+
relinquishedTurns.delete(token);
|
|
1142
|
+
await pinned?.close();
|
|
1143
|
+
} catch {
|
|
1144
|
+
if (retries-- > 0) setTimeout(attempt, SETTLE_RETRY_MS).unref();
|
|
1145
|
+
else await pinned?.close();
|
|
1146
|
+
}
|
|
1147
|
+
})();
|
|
1148
|
+
};
|
|
1149
|
+
attempt();
|
|
942
1150
|
}
|
|
943
1151
|
async function markTurn(path, token, kind) {
|
|
944
1152
|
for (let attempt = 0; ; attempt++) {
|
|
@@ -983,7 +1191,7 @@ async function releaseTurn(profile, turn, liveness) {
|
|
|
983
1191
|
try {
|
|
984
1192
|
await markTurn(join(turn.directory, String(turn.slot)), turn.token, "done");
|
|
985
1193
|
} catch {
|
|
986
|
-
settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
1194
|
+
await settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
987
1195
|
return;
|
|
988
1196
|
}
|
|
989
1197
|
await collectTurns(profile, turn, liveness).catch(() => void 0);
|