@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/dist/artifacts.js
CHANGED
|
@@ -151,6 +151,7 @@ import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
|
|
|
151
151
|
var DEFAULT_MANIFEST_SPACE = "applications";
|
|
152
152
|
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
153
153
|
kv: "tinycloud.kv",
|
|
154
|
+
space: "tinycloud.space",
|
|
154
155
|
sql: "tinycloud.sql",
|
|
155
156
|
duckdb: "tinycloud.duckdb",
|
|
156
157
|
capabilities: "tinycloud.capabilities",
|
|
@@ -381,15 +382,16 @@ function canonicalizeCaveats(caveats) {
|
|
|
381
382
|
// src/state.ts
|
|
382
383
|
import { randomUUID } from "crypto";
|
|
383
384
|
import { AsyncLocalStorage } from "async_hooks";
|
|
384
|
-
import { readFileSync, readlinkSync } from "fs";
|
|
385
|
+
import { constants as fsConstants, fstatSync, readFileSync, readlinkSync } from "fs";
|
|
385
386
|
import {
|
|
386
387
|
chmod,
|
|
387
388
|
link,
|
|
388
|
-
lstat,
|
|
389
389
|
mkdir,
|
|
390
|
+
lstat,
|
|
390
391
|
open,
|
|
391
392
|
readFile,
|
|
392
393
|
readdir,
|
|
394
|
+
realpath,
|
|
393
395
|
rename,
|
|
394
396
|
rmdir,
|
|
395
397
|
rm,
|
|
@@ -419,14 +421,18 @@ var TEST_LOCK_TURN_SETTLE_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIE
|
|
|
419
421
|
var TEST_LOCK_TURN_COLLECT_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR";
|
|
420
422
|
var TEST_LOCK_LIVENESS_BARRIER_DIR = "TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR";
|
|
421
423
|
function processWideContext(key) {
|
|
422
|
-
var
|
|
424
|
+
var _a4;
|
|
423
425
|
const registry = globalThis;
|
|
424
|
-
return registry[
|
|
426
|
+
return registry[_a4 = Symbol.for(key)] ?? (registry[_a4] = new AsyncLocalStorage());
|
|
425
427
|
}
|
|
426
428
|
var invocationStateRoot = processWideContext("tinycloud.operations.invocationStateRoot.v1");
|
|
427
429
|
var heldProfileLocks = processWideContext("tinycloud.operations.heldProfileLocks.v2");
|
|
430
|
+
var pinnedProfiles = processWideContext("tinycloud.operations.pinnedProfiles.v2");
|
|
431
|
+
var pinnedHomes = processWideContext("tinycloud.operations.pinnedHomes.v2");
|
|
428
432
|
var _a;
|
|
429
|
-
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());
|
|
430
436
|
var cachedProcessOrigin;
|
|
431
437
|
function processOrigin() {
|
|
432
438
|
if (cachedProcessOrigin !== void 0) return cachedProcessOrigin;
|
|
@@ -604,9 +610,14 @@ function tinycloudHomePath() {
|
|
|
604
610
|
function profilesPath() {
|
|
605
611
|
return join(tinycloudHomePath(), "profiles");
|
|
606
612
|
}
|
|
607
|
-
function
|
|
613
|
+
function namedProfilePath(profile) {
|
|
608
614
|
return join(profilesPath(), validateProfileName(profile));
|
|
609
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
|
+
}
|
|
610
621
|
function profileConfigPath(profile) {
|
|
611
622
|
return join(profilePath(profile), "profile.json");
|
|
612
623
|
}
|
|
@@ -617,10 +628,12 @@ function profileStoreMetadataPath(profile, store) {
|
|
|
617
628
|
return `${profileStorePath(profile, store)}.metadata.json`;
|
|
618
629
|
}
|
|
619
630
|
function profileLockPath(profile) {
|
|
620
|
-
return join(
|
|
631
|
+
return join(namedProfilePath(profile), ".lock");
|
|
621
632
|
}
|
|
622
633
|
function profileTurnLockPath(profile) {
|
|
623
|
-
|
|
634
|
+
const home = tinycloudHomePath();
|
|
635
|
+
const pinned = pinnedHomes.getStore()?.get(home);
|
|
636
|
+
return join(pinned ? livePinnedPath(profile, pinned) : home, "profile-locks", validateProfileName(profile));
|
|
624
637
|
}
|
|
625
638
|
async function readJson(filePath) {
|
|
626
639
|
try {
|
|
@@ -630,6 +643,44 @@ async function readJson(filePath) {
|
|
|
630
643
|
throw error;
|
|
631
644
|
}
|
|
632
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
|
+
}
|
|
633
684
|
async function writeJsonAtomic(filePath, value) {
|
|
634
685
|
await replaceJson(filePath, value, false);
|
|
635
686
|
}
|
|
@@ -666,43 +717,194 @@ async function readAuthRequests(profile) {
|
|
|
666
717
|
return (await readProfileStore(profile, "auth-requests")).records;
|
|
667
718
|
}
|
|
668
719
|
async function readStoreMetadata(profile, store) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
+
});
|
|
675
728
|
}
|
|
676
729
|
async function readProfileStore(profile, store) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
+
});
|
|
685
741
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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;
|
|
698
755
|
try {
|
|
699
|
-
|
|
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
|
+
}
|
|
700
848
|
} finally {
|
|
701
|
-
|
|
702
|
-
await acquired.release();
|
|
703
|
-
if (acquired.deletedWhileWaiting) await rmdir(profilePath(normalizedProfile)).catch(() => void 0);
|
|
849
|
+
for (const handle of handles.reverse()) await handle.close();
|
|
704
850
|
}
|
|
705
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
|
+
}
|
|
706
908
|
async function refuseWriteToDeletedProfile(profile) {
|
|
707
909
|
const lockPath = profileLockPath(profile);
|
|
708
910
|
const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);
|
|
@@ -735,17 +937,15 @@ async function acquireProfileLock(profile, options) {
|
|
|
735
937
|
retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS
|
|
736
938
|
};
|
|
737
939
|
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
|
|
738
|
-
const lockPath =
|
|
940
|
+
const lockPath = join(profilePath(profile), ".lock");
|
|
739
941
|
const turnDirectory = profileTurnLockPath(profile);
|
|
740
942
|
const directory = profilePath(profile);
|
|
943
|
+
await assertRealProfileDirectory(profile);
|
|
741
944
|
if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
742
945
|
if (!options.requireProfile) await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
946
|
+
await assertRealProfileDirectory(profile);
|
|
743
947
|
await mkdir(dirname(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
744
|
-
|
|
745
|
-
await chmod(path, PRIVATE_DIR_MODE).catch((error) => {
|
|
746
|
-
if (path !== directory || !isErrno(error, "ENOENT")) throw error;
|
|
747
|
-
});
|
|
748
|
-
}
|
|
948
|
+
await chmod(directory, PRIVATE_DIR_MODE);
|
|
749
949
|
const liveness = { directory: turnDirectory, held: void 0 };
|
|
750
950
|
const closeAcquisitionLiveness = async () => {
|
|
751
951
|
if (liveness.held) await closeLiveness(liveness.held);
|
|
@@ -765,6 +965,7 @@ async function acquireProfileLock(profile, options) {
|
|
|
765
965
|
}
|
|
766
966
|
const deletion = await readJson(join(turnDirectory, "deleted.json")).catch(() => null);
|
|
767
967
|
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
968
|
+
if (deletedWhileWaiting && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
768
969
|
while (true) {
|
|
769
970
|
const token = randomUUID();
|
|
770
971
|
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {
|
|
@@ -801,8 +1002,8 @@ async function waitOrTimeOut(profile, deadline, detail) {
|
|
|
801
1002
|
await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));
|
|
802
1003
|
}
|
|
803
1004
|
var TURN = /^(?:0|[1-9][0-9]*)$/;
|
|
804
|
-
var
|
|
805
|
-
var relinquishedTurns = globalThis[
|
|
1005
|
+
var _a3;
|
|
1006
|
+
var relinquishedTurns = globalThis[_a3 = /* @__PURE__ */ Symbol.for("tinycloud.operations.relinquishedProfileTurns.v1")] ?? (globalThis[_a3] = /* @__PURE__ */ new Set());
|
|
806
1007
|
var MARKER_RETRY_DELAYS_MS = [10, 50, 250];
|
|
807
1008
|
var SETTLE_RETRY_MS = 1e3;
|
|
808
1009
|
var SETTLE_RETRIES = 300;
|
|
@@ -916,7 +1117,7 @@ async function takeTurn(profile, directory, latest, liveness) {
|
|
|
916
1117
|
if (published) await markTurn(path, token, "done");
|
|
917
1118
|
return null;
|
|
918
1119
|
} catch (error) {
|
|
919
|
-
if (published) settleOwnTurn(directory, slot, token);
|
|
1120
|
+
if (published) await settleOwnTurn(directory, slot, token);
|
|
920
1121
|
throw error;
|
|
921
1122
|
}
|
|
922
1123
|
}
|
|
@@ -927,17 +1128,25 @@ async function settleAbandonedTurn(directory, abandoned) {
|
|
|
927
1128
|
}
|
|
928
1129
|
await markTurn(path, abandoned.token, "done");
|
|
929
1130
|
}
|
|
930
|
-
function settleOwnTurn(directory, slot, token
|
|
1131
|
+
async function settleOwnTurn(directory, slot, token) {
|
|
931
1132
|
relinquishedTurns.add(token);
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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();
|
|
941
1150
|
}
|
|
942
1151
|
async function markTurn(path, token, kind) {
|
|
943
1152
|
for (let attempt = 0; ; attempt++) {
|
|
@@ -982,7 +1191,7 @@ async function releaseTurn(profile, turn, liveness) {
|
|
|
982
1191
|
try {
|
|
983
1192
|
await markTurn(join(turn.directory, String(turn.slot)), turn.token, "done");
|
|
984
1193
|
} catch {
|
|
985
|
-
settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
1194
|
+
await settleOwnTurn(turn.directory, turn.slot, turn.token);
|
|
986
1195
|
return;
|
|
987
1196
|
}
|
|
988
1197
|
await collectTurns(profile, turn, liveness).catch(() => void 0);
|