From 58a2c508c60481a03d5a57e2d2ca148ae53ceb3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:03:27 +0000 Subject: [PATCH] Fix race condition in subscription cleanup and add log size limit for memory management Co-authored-by: ajnart <49837342+ajnart@users.noreply.github.com> --- .../api/src/router/docker/docker-router.ts | 64 +++++++++++-------- .../src/docker/docker-logs-modal.tsx | 15 ++++- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/packages/api/src/router/docker/docker-router.ts b/packages/api/src/router/docker/docker-router.ts index dba8d0ad0..213789023 100644 --- a/packages/api/src/router/docker/docker-router.ts +++ b/packages/api/src/router/docker/docker-router.ts @@ -142,37 +142,47 @@ export const dockerRouter = createTRPCRouter({ ) .subscription(({ input }) => { return observable((emit) => { - let cleanup: (() => void) | undefined; + let cleanupFn: (() => void) | undefined; + let isSubscribed = true; - streamContainerLogsAsync( - input.id, - input.tail ?? 200, - (data) => { - emit.next(data); - }, - (err) => { - emit.error( - new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: err.message, - }), + const initializeStream = async () => { + try { + cleanupFn = await streamContainerLogsAsync( + input.id, + input.tail ?? 200, + (data) => { + if (isSubscribed) { + emit.next(data); + } + }, + (err) => { + if (isSubscribed) { + emit.error( + new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: err.message, + }), + ); + } + }, ); - }, - ) - .then((cleanupFn) => { - cleanup = cleanupFn; - }) - .catch((err) => { - emit.error( - new TRPCError({ - code: "NOT_FOUND", - message: err instanceof Error ? err.message : "Container not found", - }), - ); - }); + } catch (err) { + if (isSubscribed) { + emit.error( + new TRPCError({ + code: "NOT_FOUND", + message: err instanceof Error ? err.message : "Container not found", + }), + ); + } + } + }; + + void initializeStream(); return () => { - cleanup?.(); + isSubscribed = false; + cleanupFn?.(); }; }); }), diff --git a/packages/modals-collection/src/docker/docker-logs-modal.tsx b/packages/modals-collection/src/docker/docker-logs-modal.tsx index e75508681..d0924a5dc 100644 --- a/packages/modals-collection/src/docker/docker-logs-modal.tsx +++ b/packages/modals-collection/src/docker/docker-logs-modal.tsx @@ -38,12 +38,25 @@ export const DockerLogsModal = createModal(({ innerPr }, ); + // Maximum log size to prevent memory issues (500KB) + const MAX_LOG_SIZE = 500 * 1024; + // Subscribe to streaming logs clientApi.docker.subscribeLogs.useSubscription( { id: innerProps.id, tail: innerProps.tail ?? 200 }, { onData(data) { - setLogs((prev) => prev + data); + setLogs((prev) => { + const newLogs = prev + data; + // Truncate from the beginning if logs exceed maximum size + if (newLogs.length > MAX_LOG_SIZE) { + const truncated = newLogs.slice(-MAX_LOG_SIZE); + // Try to start from a newline to avoid partial lines + const firstNewline = truncated.indexOf("\n"); + return firstNewline > 0 ? truncated.slice(firstNewline + 1) : truncated; + } + return newLogs; + }); setIsLoading(false); }, onError(err) {