[PR #2817] fix: support chunked Docker container messages from newt #36697

Open
opened 2026-06-18 21:58:22 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/fosrl/pangolin/pull/2817
Author: @jaydeep-pipaliya
Created: 4/9/2026
Status: 🔄 Open

Base: main ← Head: fix/chunk-docker-container-messages


📝 Commits (2)

  • f1ae17e fix: support chunked Docker container messages from newt
  • 5f37315 fix: robust chunk reassembly with batchId, validation, and typed accumulator

📊 Changes

1 file changed (+110 additions, -9 deletions)

View changed files

📝 server/routers/newt/handleSocketMessages.ts (+110 -9)

📄 Description

What does this PR do?

Fixes #2117

When more than ~20 Docker containers are running, the WebSocket message containing the full container list can exceed frame/buffer limits imposed by intermediary proxies (Traefik, nginx, Cloudflare tunnels, etc.), causing the Docker Container View in Pangolin to show nothing — even though newt successfully sends the data.

Root cause analysis

The issue is not in the ws library or node-cache — both handle large payloads fine. The problem is the network path between newt and pangolin:

newt (Go) → gorilla/websocket → [Traefik / reverse proxy] → ws (Node.js) → pangolin

gorilla/websocket.WriteJSON serializes the full container list (55+ containers × 1-5KB each = 55-275KB) into a single WebSocket text frame. When this frame passes through Traefik or another reverse proxy, it can be:

  • Silently truncated — proxy buffers the frame, hits an internal limit, drops it
  • Connection reset — proxy closes the connection on oversized frame
  • Corrupted — partial frame arrives, JSON.parse fails silently in the try/catch

The user's logs confirm this: pangolin logged Docker containers for Newt: 55 on one occasion but the container view was empty — meaning the cache write at line 57 either never executed or the data was already corrupted by that point.

Solution: chunked message protocol

Protocol design

┌─────────┐                              ┌──────────┐
│  newt    │                              │ pangolin │
│  (Go)   │                              │  (TS)    │
└────┬────┘                              └────┬─────┘
     │  ≤15 containers: single message        │
     │──────────────────────────────────────>│  (backward compatible)
     │                                        │
     │  >15 containers: chunked               │
     │  chunk 0/4 (batch=a1b2c3)             │
     │──────────────────────────────────────>│  accumulate in cache
     │  chunk 1/4 (batch=a1b2c3)             │
     │──────────────────────────────────────>│  accumulate
     │  chunk 2/4 (batch=a1b2c3)             │
     │──────────────────────────────────────>│  accumulate
     │  chunk 3/4 (batch=a1b2c3)             │
     │──────────────────────────────────────>│  all chunks received → process
     │                                        │

Each chunk message has the same type (newt/socket/containers) with additional fields:

{
  "containers": [...],       // subset of containers for this chunk
  "chunkIndex": 0,           // 0-based index of this chunk
  "totalChunks": 4,          // total number of chunks in this batch
  "batchId": "a1b2c3d4"     // unique ID for this batch
}

Why batchId?

Without it, two concurrent sends (e.g., a manual fetch + a Docker event) would interleave chunks and corrupt the accumulated data. The batchId ensures:

  • Each batch is tracked independently
  • A newer batch automatically supersedes any in-progress older batch
  • No silent data corruption from race conditions

Why chunk size of 15?

Each Docker container with full metadata (labels, ports, networks, env) serializes to 1-5KB of JSON. 15 containers ≈ 15-75KB per message — well within typical proxy frame limits (most default to 64KB-1MB). The threshold of 15 was chosen to stay safely under common defaults while minimizing the number of round trips.

Changes in this PR (pangolin server)

server/routers/newt/handleSocketMessages.ts:

  • ChunkAccumulator typed interface for cache data (replaces inline object)
  • Non-chunked messages pass through unchanged (backward compatible with older newt)
  • Validates chunkIndex, totalChunks, batchId before processing — rejects malformed chunks
  • Accumulates chunks in cache keyed by {newtId}:dockerContainersChunks
  • New batchId supersedes any in-progress batch (handles concurrent sends)
  • 120s TTL on partial chunks prevents stale data accumulation
  • Processes complete list only after all chunks arrive

Companion PR

Newt (Go): https://github.com/fosrl/newt/pull/304

  • sendContainerList() helper splits lists >15 into chunks with batchId
  • Small lists (≤15) sent as single message — zero behavior change for most users
  • go build passes cleanly

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/fosrl/pangolin/pull/2817 **Author:** [@jaydeep-pipaliya](https://github.com/jaydeep-pipaliya) **Created:** 4/9/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/chunk-docker-container-messages` --- ### 📝 Commits (2) - [`f1ae17e`](https://github.com/fosrl/pangolin/commit/f1ae17e3b1abe4c615d4f6f5f9c33674046c08aa) fix: support chunked Docker container messages from newt - [`5f37315`](https://github.com/fosrl/pangolin/commit/5f37315c5319d05ff53e8929b37629f596f02ba6) fix: robust chunk reassembly with batchId, validation, and typed accumulator ### 📊 Changes **1 file changed** (+110 additions, -9 deletions) <details> <summary>View changed files</summary> 📝 `server/routers/newt/handleSocketMessages.ts` (+110 -9) </details> ### 📄 Description ## What does this PR do? Fixes #2117 When more than ~20 Docker containers are running, the WebSocket message containing the full container list can exceed frame/buffer limits imposed by intermediary proxies (Traefik, nginx, Cloudflare tunnels, etc.), causing the Docker Container View in Pangolin to show nothing — even though newt successfully sends the data. ## Root cause analysis The issue is **not** in the `ws` library or `node-cache` — both handle large payloads fine. The problem is the network path between newt and pangolin: ``` newt (Go) → gorilla/websocket → [Traefik / reverse proxy] → ws (Node.js) → pangolin ``` `gorilla/websocket.WriteJSON` serializes the full container list (55+ containers × 1-5KB each = 55-275KB) into a single WebSocket text frame. When this frame passes through Traefik or another reverse proxy, it can be: - **Silently truncated** — proxy buffers the frame, hits an internal limit, drops it - **Connection reset** — proxy closes the connection on oversized frame - **Corrupted** — partial frame arrives, `JSON.parse` fails silently in the `try/catch` The user's logs confirm this: pangolin logged `Docker containers for Newt: 55` on one occasion but the container view was empty — meaning the cache write at line 57 either never executed or the data was already corrupted by that point. ## Solution: chunked message protocol ### Protocol design ``` ┌─────────┐ ┌──────────┐ │ newt │ │ pangolin │ │ (Go) │ │ (TS) │ └────┬────┘ └────┬─────┘ │ ≤15 containers: single message │ │──────────────────────────────────────>│ (backward compatible) │ │ │ >15 containers: chunked │ │ chunk 0/4 (batch=a1b2c3) │ │──────────────────────────────────────>│ accumulate in cache │ chunk 1/4 (batch=a1b2c3) │ │──────────────────────────────────────>│ accumulate │ chunk 2/4 (batch=a1b2c3) │ │──────────────────────────────────────>│ accumulate │ chunk 3/4 (batch=a1b2c3) │ │──────────────────────────────────────>│ all chunks received → process │ │ ``` Each chunk message has the same type (`newt/socket/containers`) with additional fields: ```json { "containers": [...], // subset of containers for this chunk "chunkIndex": 0, // 0-based index of this chunk "totalChunks": 4, // total number of chunks in this batch "batchId": "a1b2c3d4" // unique ID for this batch } ``` ### Why `batchId`? Without it, two concurrent sends (e.g., a manual fetch + a Docker event) would interleave chunks and corrupt the accumulated data. The `batchId` ensures: - Each batch is tracked independently - A newer batch automatically supersedes any in-progress older batch - No silent data corruption from race conditions ### Why chunk size of 15? Each Docker container with full metadata (labels, ports, networks, env) serializes to 1-5KB of JSON. 15 containers ≈ 15-75KB per message — well within typical proxy frame limits (most default to 64KB-1MB). The threshold of 15 was chosen to stay safely under common defaults while minimizing the number of round trips. ## Changes in this PR (pangolin server) **`server/routers/newt/handleSocketMessages.ts`**: - `ChunkAccumulator` typed interface for cache data (replaces inline object) - Non-chunked messages pass through unchanged (backward compatible with older newt) - Validates `chunkIndex`, `totalChunks`, `batchId` before processing — rejects malformed chunks - Accumulates chunks in cache keyed by `{newtId}:dockerContainersChunks` - New `batchId` supersedes any in-progress batch (handles concurrent sends) - 120s TTL on partial chunks prevents stale data accumulation - Processes complete list only after all chunks arrive ## Companion PR **Newt (Go):** https://github.com/fosrl/newt/pull/304 - `sendContainerList()` helper splits lists >15 into chunks with `batchId` - Small lists (≤15) sent as single message — zero behavior change for most users - `go build` passes cleanly --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-06-18 21:58:22 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/pangolin#36697