Skip to content

Commit ada5b78

Browse files
committed
fix(supervisor): bound the pod-count list server-side and clear its timer
1 parent 535d23a commit ada5b78

2 files changed

Lines changed: 29 additions & 11 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: supervisor
3+
type: improvement
4+
---
5+
6+
Self-hosted Kubernetes deployments now measure the running-task count exactly when deciding whether to pause pulling new work, instead of reading an approximate figure that could differ between reads. The safeguard now engages and releases at the point it is configured to, rather than slightly early or late.

apps/supervisor/src/clients/kubernetes.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,15 @@ export function createPodCountFetcher(
6969
namespace: string,
7070
timeoutMs: number
7171
): () => Promise<number> {
72+
const serverTimeoutSeconds = Math.max(1, Math.floor(timeoutMs / 1000));
73+
7274
return async () => {
7375
const list = await withTimeout(
74-
api.core.listNamespacedPod({ namespace, limit: 1 }),
76+
api.core.listNamespacedPod({
77+
namespace,
78+
limit: 1,
79+
timeoutSeconds: serverTimeoutSeconds,
80+
}),
7581
timeoutMs,
7682
"pod count list"
7783
);
@@ -89,15 +95,21 @@ export function createPodCountFetcher(
8995
};
9096
}
9197

92-
/** Rejects if `promise` outlives `timeoutMs`, so a hung request cannot freeze the caller. */
98+
/**
99+
* withTimeout rejects if `promise` outlives `timeoutMs`, so a hung request cannot
100+
* freeze the caller. A backstop only: the k8s client threads no AbortSignal through
101+
* to fetch, so callers must also bound the request server-side (`timeoutSeconds`),
102+
* or an abandoned request would stay open. The timer is cleared either way.
103+
*/
93104
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, what: string): Promise<T> {
94-
return Promise.race([
95-
promise,
96-
new Promise<never>((_resolve, reject) => {
97-
setTimeout(
98-
() => reject(new Error(`${what} timed out after ${timeoutMs}ms`)),
99-
timeoutMs
100-
).unref();
101-
}),
102-
]);
105+
let timer: NodeJS.Timeout;
106+
const deadline = new Promise<never>((_resolve, reject) => {
107+
timer = setTimeout(
108+
() => reject(new Error(`${what} timed out after ${timeoutMs}ms`)),
109+
timeoutMs
110+
);
111+
timer.unref();
112+
});
113+
114+
return Promise.race([promise, deadline]).finally(() => clearTimeout(timer));
103115
}

0 commit comments

Comments
 (0)