fix: harden onboarding completion

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-14 01:20:27 +02:00
1 parent 576cc0b418
commit 07770e862c
44 files changed
+3003 -1000

No files matched your search

@@ -55,6 +55,7 @@ describe("listDiscoveredContainersAsync", () => {
it("times out a blackholed host without losing another host's result", async () => {
vi.useFakeTimers();
try {
let blackholedSignal: AbortSignal | undefined;
listContainersA.mockResolvedValue([
{
Id: "sonarr",
@@ -65,7 +66,10 @@ describe("listDiscoveredContainersAsync", () => {
},
},
]);
listContainersB.mockReturnValue(new Promise(() => undefined));
listContainersB.mockImplementation(({ abortSignal }: { abortSignal: AbortSignal }) => {
blackholedSignal = abortSignal;
return new Promise(() => undefined);
});
const resultPromise = listDiscoveredContainersAsync({}, 25);
await vi.advanceTimersByTimeAsync(25);
@@ -76,6 +80,8 @@ describe("listDiscoveredContainersAsync", () => {
expect.objectContaining({ host: "good", status: "success" }),
expect.objectContaining({ host: "bad", status: "unavailable", reason: "Docker discovery timed out for bad" }),
]);
expect(blackholedSignal?.aborted).toBe(true);
expect(listContainersB).toHaveBeenCalledWith({ all: false, abortSignal: blackholedSignal });
} finally {
vi.useRealTimers();
}
@@ -9,20 +9,6 @@ import type { DockerDiscoveryHostResult, DockerDiscoveryResult } from "./types";
const logger = createLogger({ module: "dockerDiscovery" });
export const dockerDiscoveryHostTimeoutMs = 5_000;
const withTimeoutAsync = async <T>(promise: Promise<T>, timeoutMs: number, host: string) => {
let timeout: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
promise,
new Promise<never>((_, reject) => {
timeout = setTimeout(() => reject(new Error(`Docker discovery timed out for ${host}`)), timeoutMs);
}),
]);
} finally {
if (timeout) clearTimeout(timeout);
}
};
const reasonFromError = (error: unknown) => {
if (error instanceof Error && error.message.trim()) return error.message;
return "Docker is not reachable";
@@ -35,11 +21,29 @@ export const listDiscoveredContainersAsync = async (
const dockerInstances = DockerSingleton.getInstances();
const settled = await Promise.allSettled(
dockerInstances.map(async ({ instance, host }): Promise<DockerDiscoveryHostResult> => {
const containers = await withTimeoutAsync(instance.listContainers({ all: false }), timeoutMs, host);
const services = containers
.map((container) => parseContainerLabels(container, host, options))
.filter((service) => service !== null);
return { host, status: "success", containers, services };
const controller = new AbortController();
const timeoutError = new Error(`Docker discovery timed out for ${host}`);
let timeout: ReturnType<typeof setTimeout> | undefined;
try {
const containers = await Promise.race([
instance.listContainers({ all: false, abortSignal: controller.signal }),
new Promise<never>((_, reject) => {
timeout = setTimeout(() => {
controller.abort(timeoutError);
reject(timeoutError);
}, timeoutMs);
}),
]);
const services = containers
.map((container) => parseContainerLabels(container, host, options))
.filter((service) => service !== null);
return { host, status: "success", containers, services };
} catch (error) {
if (controller.signal.aborted) throw timeoutError;
throw error;
} finally {
if (timeout) clearTimeout(timeout);
}
}),
);