From 1a557751a6e2a3b0c050dbb052b9606b57f48572 Mon Sep 17 00:00:00 2001 From: Woody Lin Date: Thu, 27 Aug 2026 10:41:57 +0800 Subject: [PATCH] fix: stop the Rook Ceph panel from exhausting the connection pool The panel opened six watch streams on mount, one per resource kind, and a browser allows exactly six HTTP/1.1 connections per origin. With the pool saturated, describe, log and terminal never got a connection and hung. Stream only pods and deployments, and poll the four Ceph CRs every 10s. --- .../viewers/rook-ceph-viewer.svelte | 82 ++++++++++++------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/src/lib/components/resource-viewer/viewers/rook-ceph-viewer.svelte b/src/lib/components/resource-viewer/viewers/rook-ceph-viewer.svelte index ca448c7a..6c545177 100644 --- a/src/lib/components/resource-viewer/viewers/rook-ceph-viewer.svelte +++ b/src/lib/components/resource-viewer/viewers/rook-ceph-viewer.svelte @@ -42,6 +42,8 @@ const resourceClient = createClient(ResourceService, transport); const namespace = 'rook-ceph'; + const POLL_INTERVAL_MS = 10000; + const RETRY_INTERVAL_MS = 3000; type RelatedResource = | CephRookIoV1CephCluster @@ -50,37 +52,61 @@ | CephRookIoV1CephObjectStore | AppsV1Deployment | CoreV1Pod; + + type ResourceIdentifier = { group: string; version: string; resource: string }; + // AbortController is used to terminate all watch streams when the component is destroyed const abortController = new AbortController(); const getKey = (o: RelatedResource) => o?.metadata?.uid ?? o?.metadata?.name ?? ''; + + /** Returns the list's resourceVersion, or null when the request failed. */ + async function list( + identifier: ResourceIdentifier, + setObjects: (items: T[]) => void + ): Promise { + try { + const response = await resourceClient.list( + { + cluster, + namespace, + ...identifier + }, + { signal: abortController.signal } + ); + setObjects(response.items.map((item) => item.object as T)); + return response.resourceVersion; + } catch (error) { + if (abortController.signal.aborted) return null; + console.error(`Failed to list ${identifier.resource}:`, error); + return null; + } + } + + async function listAndPoll( + identifier: ResourceIdentifier, + setObjects: (items: T[]) => void + ) { + while (!abortController.signal.aborted) { + const resourceVersion = await list(identifier, setObjects); + await sleep(resourceVersion === null ? RETRY_INTERVAL_MS : POLL_INTERVAL_MS); + } + } + async function listAndWatch( - identifier: { group: string; version: string; resource: string }, + identifier: ResourceIdentifier, setObjects: (items: T[]) => void, updateObject: (updater: (previous: T[]) => T[]) => void ) { while (!abortController.signal.aborted) { - let resourceVersion = ''; - // === 1. List: Get initial snapshot === - try { - const response = await resourceClient.list( - { - cluster, - namespace, - ...identifier - }, - { signal: abortController.signal } - ); - const items = response.items.map((item) => item.object as T); - setObjects(items); - resourceVersion = response.resourceVersion; - } catch (error) { + const listedResourceVersion = await list(identifier, setObjects); + if (listedResourceVersion === null) { if (abortController.signal.aborted) return; - console.error(`Failed to list ${identifier.resource}:`, error); - await sleep(3000); + await sleep(RETRY_INTERVAL_MS); continue; } + let resourceVersion = listedResourceVersion; // === 2. Watch: Stream events starting from this resourceVersion === try { @@ -236,25 +262,21 @@ } onMount(() => { - listAndWatch( + listAndPoll( { group: 'ceph.rook.io', version: 'v1', resource: 'cephclusters' }, - (items) => (cephClusters = items), - (updater) => (cephClusters = updater(cephClusters)) + (items) => (cephClusters = items) ); - listAndWatch( + listAndPoll( { group: 'ceph.rook.io', version: 'v1', resource: 'cephblockpools' }, - (items) => (cephBlockPools = items), - (updater) => (cephBlockPools = updater(cephBlockPools)) + (items) => (cephBlockPools = items) ); - listAndWatch( + listAndPoll( { group: 'ceph.rook.io', version: 'v1', resource: 'cephfilesystems' }, - (items) => (cephFilesystems = items), - (updater) => (cephFilesystems = updater(cephFilesystems)) + (items) => (cephFilesystems = items) ); - listAndWatch( + listAndPoll( { group: 'ceph.rook.io', version: 'v1', resource: 'cephobjectstores' }, - (items) => (cephObjectStores = items), - (updater) => (cephObjectStores = updater(cephObjectStores)) + (items) => (cephObjectStores = items) ); listAndWatch( { group: 'apps', version: 'v1', resource: 'deployments' },