<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ashish Barmaiya</title>
    <description>The latest articles on DEV Community by Ashish Barmaiya (@ashishbarmaiya).</description>
    <link>https://dev.to/ashishbarmaiya</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3599039%2F8c260876-69bb-46eb-b816-1161303af61c.jpg</url>
      <title>DEV Community: Ashish Barmaiya</title>
      <link>https://dev.to/ashishbarmaiya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashishbarmaiya"/>
    <language>en</language>
    <item>
      <title>Why I Rewrote My Reverse Proxy from Node.js to Go</title>
      <dc:creator>Ashish Barmaiya</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:38:54 +0000</pubDate>
      <link>https://dev.to/ashishbarmaiya/why-i-rewrote-my-reverse-proxy-from-nodejs-to-go-f40</link>
      <guid>https://dev.to/ashishbarmaiya/why-i-rewrote-my-reverse-proxy-from-nodejs-to-go-f40</guid>
      <description>&lt;p&gt;If you’ve read my previous posts, you know I spent months forcing Node.js to act like a production-grade API gateway.&lt;/p&gt;

&lt;p&gt;First, I tried building an optimized streaming pipeline, only to discover that &lt;a href="https://dev.to/ashishbarmaiya/why-i-ripped-streampipe-out-of-my-nodejs-api-gateway-2ekl"&gt;unhandled stream errors were quietly destroying my V8 processes.&lt;/a&gt; Then, I tried to scale horizontally using Node’s cluster module, which resulted into a nightmare of &lt;a href="https://dev.to/ashishbarmaiya/surviving-nodejs-clusters-graceful-teardowns-windows-quirks-and-black-box-testing-1aom"&gt;handling cross-platform IPC signals, graceful teardowns, and fighting Windows socket leaks.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I fixed the bugs and stabilized the prototype. But when I started running high-throughput load generators against it, I hit a wall.&lt;/p&gt;

&lt;p&gt;It wasn't a functional bug, rather an architectural wall. Proxies live and die by memory allocations, zero-copy socket streams, raw concurrency overhead, and low-level kernel visibility. The V8 engine and the libuv event loop were no longer assets; they were opaque black boxes standing between my application code and the network stack.&lt;/p&gt;

&lt;p&gt;So I scrapped the Node.js prototype and re-engineered &lt;a href="https://github.com/Ashish-Barmaiya/torus-proxy" rel="noopener noreferrer"&gt;Torus&lt;/a&gt; from scratch as a native Go infrastructure tool.&lt;/p&gt;

&lt;p&gt;This blog documents why Node.js failed at the runtime level, how Go solved it, and the hard benchmark numbers behind the decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Why Node.js Hits an Architectural Wall
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Heavy Compute Starvation Problem (The Event Loop Trap)
&lt;/h3&gt;

&lt;p&gt;Node.js is famous for handling non-blocking I/O efficiently via libuv. If a proxy is just blindly shuffling raw bytes from Client A to Backend B, Node is fast.&lt;/p&gt;

&lt;p&gt;But an API gateway doesn't just shuffle bytes, it has to think.&lt;/p&gt;

&lt;p&gt;Every time Torus intercepts a request, it must parse inbound HTTP headers, match strings against a multi-tenant routing table, evaluate rate-limiting arrays, and verify cryptographic signatures or JWT tokens.&lt;/p&gt;

&lt;p&gt;In Node.js, all user-space compute logic executes sequentially on the single main thread of the event loop. The moment a heavy routing regex calculation or crypto signature check takes 2 to 3 milliseconds to process, the entire network ingestion pipeline for that worker halts. Sockets wait and backpressure builds. Tail latency spikes because the main thread is starved of CPU cycles.&lt;/p&gt;

&lt;p&gt;In Go, this constraint vanishes. Go replaces the event loop with the &lt;strong&gt;GMP scheduler&lt;/strong&gt;-a cooperative, multi-threaded work-stealing engine baked directly into the compiled binary.&lt;/p&gt;

&lt;p&gt;Instead of a single thread managing callbacks, Go multiplexes thousands of lightweight green threads (goroutines) across a pool of actual OS threads allocated to physical CPU cores. When a request hits Torus, it gets its own goroutine. If a specific tenant's routing logic requires heavy CPU computation, the GMP scheduler keeps that specific goroutine running on one core while seamlessly routing new inbound network connections across the remaining cores. Compute overhead is natively distributed without choking the gateway.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Memory Baggage and the Invisible Hand of the GC
&lt;/h3&gt;

&lt;p&gt;When we scale a Node.js proxy via the &lt;code&gt;cluster&lt;/code&gt; module, every worker we spawn is an independent instance of the V8 engine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nodejs/node/issues/34823" rel="noopener noreferrer"&gt;An idle Node.js worker consumes roughly 10 MB to 30 MB of RAM just to keep its internal context, heap layouts, and Just-In-Time (JIT) compiler metadata alive.&lt;/a&gt; Multiply that across a cluster matching your CPU core count, and your baseline memory footprint is hundreds of megabytes before you've routed a single packet.&lt;/p&gt;

&lt;p&gt;Worse than the idle footprint are the Garbage Collection (GC) jitters.&lt;/p&gt;

&lt;p&gt;In Node, every parsed HTTP header string, transient configuration, and intermediate token object is allocated dynamically on the V8 heap as a pointer-heavy structure. Under traffic surges, &lt;a href="https://v8.dev/blog/trash-talk" rel="noopener noreferrer"&gt;the heap fragments, eventually triggering V8’s generational garbage collector.&lt;/a&gt; The resulting &lt;a href="https://v8.dev/blog/concurrent-marking" rel="noopener noreferrer"&gt;Mark-Sweep phase causes tiny, unpredictable "Stop-the-World" pauses.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a standard web app, we won't notice a 15ms GC pause. In an API gateway handling tens of thousands of requests per second, a 15ms pause causes a catastrophic backup on the TCP backlog.&lt;/p&gt;

&lt;p&gt;Go treats memory like a systems language. There is no virtual machine wrapper. An idle Torus instance in Go runs at a lightweight ~3 MB of RAM.&lt;/p&gt;

&lt;p&gt;More importantly, Go provides explicit control over memory layout via value types and structs. Instead of allocating everything on the heap, transient request metadata stays on the stack, and byte slices can be reused across requests using &lt;code&gt;sync.Pool&lt;/code&gt;. &lt;a href="https://go.dev/blog/go119runtime" rel="noopener noreferrer"&gt;By minimizing heap allocations, the Go garbage collector has almost nothing to track, flattening the p99 latency curve under heavy load.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Direct Mapping to OS Primitives
&lt;/h3&gt;

&lt;p&gt;Writing abstraction layers in Node.js to handle graceful shutdowns across Windows and Linux (&lt;code&gt;SIGINT&lt;/code&gt; vs &lt;code&gt;CTRL_BREAK_EVENT&lt;/code&gt;) is painful because Node sits behind multiple layers of runtime abstraction.&lt;/p&gt;

&lt;p&gt;Go compiles directly to a naked machine binary targeted at the host operating system. Its internal &lt;a href="https://dzone.com/articles/go-servers-understanding-epoll-kqueue-netpoll" rel="noopener noreferrer"&gt;netpoller integrates directly with low-level OS primitives-using Linux &lt;code&gt;epoll&lt;/code&gt; or macOS &lt;code&gt;kqueue&lt;/code&gt; under the hood without intermediate runtime marshalling or C++ context-switching boundaries.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When Torus reads from a socket file descriptor in Go, it reads directly into contiguous physical memory byte slices. We don't have to wrap application code in external process managers or build IPC cluster boundaries to utilize multi-core hardware. The language is the runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Architecture Comparison Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Characteristic&lt;/th&gt;
&lt;th&gt;Node.js Architecture&lt;/th&gt;
&lt;th&gt;Go Architecture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-process (&lt;code&gt;node:cluster&lt;/code&gt; + single-threaded V8 event loop)&lt;/td&gt;
&lt;td&gt;Single-process (goroutines + M:N scheduler)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;V8 Garbage Collection (~140–200 MB baseline heap)&lt;/td&gt;
&lt;td&gt;Go Runtime GC (~2.8 MB idle, ~20 MB under load)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;I/O Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;libuv non-blocking event loop&lt;/td&gt;
&lt;td&gt;Go &lt;code&gt;netpoll&lt;/code&gt; (epoll/kqueue abstraction over blocking sockets)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Allocation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dynamic heap objects for parsed headers/tokens&lt;/td&gt;
&lt;td&gt;Stack allocations + &lt;code&gt;sync.Pool&lt;/code&gt; byte-slice recycling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OS Interface&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High-level JavaScript abstractions over C++ wrappers&lt;/td&gt;
&lt;td&gt;Direct system call interaction via compiled machine code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Part 3: The Benchmark Evolution
&lt;/h2&gt;

&lt;p&gt;An architectural migration requires empirical proof. To measure the impact of the rewrite, I set up black-box stress-testing suites on a dual-core test environment. You can read the comprehensive Benchmark Report &lt;a href="https://github.com/Ashish-Barmaiya/torus-proxy/blob/main/docs/benchmarking/reports/Benchmark-001-nodejs-to-go-performance-evaluation.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Node.js Baseline
&lt;/h3&gt;

&lt;p&gt;Using a &lt;code&gt;node:cluster&lt;/code&gt; topology across 4 worker processes with Autocannon pushing 100 concurrent connections, the native Node.js implementation hit its limits early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Baseline Reverse Proxy: 1,647 req/sec (~200 MB memory footprint)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Production Edge (TLS + Redis Rate Limiting): 968 req/sec (Average latency: 103 ms, P99: 488 ms)&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                          [ Autocannon ]
                        (100 Connections)
                                │
                                ▼
                      [ Master Process ]
                                │
              ┌─────────────────┴─────────────────┐
              ▼                                   ▼
      [ Worker 1 (V8) ]                   [ Worker 2 (V8) ]
              │                                   │
              ▼                                   ▼
      (stream.pipeline)                   (stream.pipeline)
              │                                   │
              ▼                                   ▼
     [ Redis Container ]                [ Upstream Node App ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under production-style loads, adding cryptographic operations and a Redis round-trip dropped throughput by 41%, pushing P99 tail latency near half a second.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Harness Refinement (Isolating Runtime Drag)
&lt;/h3&gt;

&lt;p&gt;Benchmarking a proxy can be tricky because the test client and mock backends can easily distort results. To capture Torus’s true performance, I evolved the benchmark across three distinct stages:&lt;/p&gt;

&lt;h4&gt;
  
  
  STAGE 1: Node.js Baseline
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Environment: Windows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Harness: &lt;code&gt;[Autocannon (JS)] ──► [Node.js Torus] ──► [Node.js Mock Backends]&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concurrency: &lt;code&gt;node:cluster&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metrics: &lt;strong&gt;1,647 req/sec&lt;/strong&gt; &lt;em&gt;Throughput&lt;/em&gt; | &lt;strong&gt;103 ms&lt;/strong&gt; &lt;em&gt;Avg Latency&lt;/em&gt; | &lt;strong&gt;488 ms&lt;/strong&gt; &lt;em&gt;p99&lt;/em&gt; | &lt;strong&gt;~200 MB&lt;/strong&gt; &lt;em&gt;Memory&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  STAGE 2: Initial Go Rewrite
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Environment: Windows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Harness: &lt;code&gt;[Autocannon (JS)] ──► [Go Torus] ───────► [Node.js Mock Backends]&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concurrency: &lt;code&gt;Goroutines&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metrics: &lt;strong&gt;6,044 req/sec&lt;/strong&gt; &lt;em&gt;Throughput&lt;/em&gt; | &lt;strong&gt;16.06 ms&lt;/strong&gt; &lt;em&gt;Avg Latency&lt;/em&gt; | &lt;strong&gt;294 ms&lt;/strong&gt; &lt;em&gt;p99&lt;/em&gt; | &lt;strong&gt;~20 MB&lt;/strong&gt; &lt;em&gt;Memory&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  STAGE 3: Refined Benchmark
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Environment: Ubuntu (WSL)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Harness: &lt;code&gt;[wrk (C-native)] ──► [Go Torus] ───────► [Go Native Backends]&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concurrency: &lt;code&gt;Goroutines&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metrics: &lt;strong&gt;17,865 req/sec&lt;/strong&gt; &lt;em&gt;Throughput&lt;/em&gt; | &lt;strong&gt;6.12 ms&lt;/strong&gt; &lt;em&gt;Avg Latency&lt;/em&gt; | &lt;strong&gt;45.32 ms&lt;/strong&gt; &lt;em&gt;p99&lt;/em&gt; | &lt;strong&gt;~20 MB&lt;/strong&gt; &lt;em&gt;Memory&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The jump from &lt;strong&gt;Stage 1&lt;/strong&gt; to &lt;strong&gt;Stage 2&lt;/strong&gt; confirmed that rewriting Torus in Go produced an immediate &lt;strong&gt;3.7× throughput improvement&lt;/strong&gt;. Simply swapping the V8 engine for Go's runtime and replacing &lt;code&gt;node:cluster&lt;/code&gt; with &lt;code&gt;Goroutines&lt;/code&gt; slashed &lt;strong&gt;average latency from 103 ms down to 16.06 ms&lt;/strong&gt;, and crushed the memory footprint &lt;strong&gt;from 200 MB to a stable 20 MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, profiling revealed that the proxy itself was no longer the limiting factor. Torus spent much of its time waiting for the upstream Node.js mock servers to respond, while Autocannon's JavaScript runtime was saturating CPU cores under high concurrency.&lt;/p&gt;

&lt;p&gt;Furthermore, I was still running on Windows, forcing Go to interact with the Windows networking stack instead of utilizing native Linux &lt;code&gt;epoll&lt;/code&gt; system calls, which is where Go's &lt;code&gt;netpoll&lt;/code&gt; truly shines. The benchmark harness and the host OS had become the bottlenecks.&lt;/p&gt;

&lt;p&gt;To eliminate these external constraints, I refined the test environment by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Migrated to Linux:&lt;/strong&gt; I moved the entire testing suite into Ubuntu via WSL (Windows Subsystem for Linux) to allow Go to leverage Linux-native networking primitives.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Swapped the Backends:&lt;/strong&gt; I replaced the Node.js mock applications with lightweight native Go HTTP servers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upgraded the Load Generator:&lt;/strong&gt; I switched from Autocannon to &lt;strong&gt;wrk&lt;/strong&gt;, a multithreaded C-based HTTP benchmarking tool with significantly lower client-side overhead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Isolated the Processes:&lt;/strong&gt; I ran the benchmark client, Torus, and the upstream servers in separate terminal sessions to minimize OS CPU-scheduling contention.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These changes stripped away the JS runtime drag and the Windows networking limitations, producing the final benchmark result of &lt;strong&gt;17,865 req/sec&lt;/strong&gt; and dropping latency even further to &lt;strong&gt;6.12 ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To put that in perspective: when I isolated the internal router and completely removed downstream network socket delays (returning an in-memory response), Torus's routing throughput hit &lt;strong&gt;98,565 req/sec&lt;/strong&gt;. The code wasn't slow; the environment was. Isolating the proxy proved what the Go engine was capable of when it wasn't being choked by JavaScript testing tools and Windows sockets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: The Hard Numbers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Experiment A: Full Production Proxying Pipeline
&lt;/h3&gt;

&lt;p&gt;This test exercises the complete reverse proxy pipeline: HTTP parsing, longest-prefix route matching, round-robin load balancing, TCP connection pooling, header injection, and zero-copy stream forwarding.&lt;/p&gt;

&lt;p&gt;Test Setup: &lt;code&gt;wrk -t2 -c100 -d10s&lt;/code&gt; against native Go mock backends.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Node.js Original&lt;/th&gt;
&lt;th&gt;Go Rewrite (Final)&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,647.00 req/sec&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17,865.81 req/sec&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+984.7% (10.8×)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;103.00 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.12 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−94.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max Tail Latency (P99)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;488.00 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;45.32 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−90.7%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~200 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~20 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10× reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Transfer Rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2.01 MB/sec&lt;/strong&gt; (178,892 total requests)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production Proxy Throughput (req/sec)

Node.js      [████                                           1,647]
Initial Go   [███████████████                                6,044]
Final Go     [███████████████████████████████████           17,865]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under full proxy execution, throughput increased by &lt;strong&gt;10.8×&lt;/strong&gt; while average latency dropped from 103ms down to &lt;strong&gt;6.12ms&lt;/strong&gt;. Memory consumption dropped from 200MB down to a stable &lt;strong&gt;20MB&lt;/strong&gt; under load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment B: Short-Circuit Routing Path
&lt;/h3&gt;

&lt;p&gt;To measure the maximum throughput of Torus's internal router (header parsing, route matching, load balancing logic) independent of downstream network socket delays, I tested a scenario where downstream backends were marked dead. Torus short-circuited and immediately returned an in-memory error response.&lt;/p&gt;

&lt;p&gt;Test Setup: &lt;code&gt;wrk -t2 -c100 -d10s&lt;/code&gt; (No Live Backends).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Initial Go (Autocannon)&lt;/th&gt;
&lt;th&gt;Final Go (wrk)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15,644 req/sec&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98,565.91 req/sec&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.94 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.21 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max Tail Latency (P99)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;78.00 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;86.63 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Transfer Rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15.70 MB/sec&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Removing downstream network I/O allowed the Go routing engine to approach &lt;strong&gt;100,000 req/sec&lt;/strong&gt; on a basic local development machine. This confirms that Torus’s internal execution pipeline adds minimal overhead-downstream network delays account for virtually the entire latency profile of the proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building Torus in Node.js first was a valuable exercise. Node.js remains an exceptional environment for rapid prototyping, forcing developers to handle edge cases, connection pooling, and stream mechanics explicitly. However, selecting an infrastructure runtime requires aligning tools with system requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JavaScript is for Application State; Go is for Networking Primitives.&lt;/strong&gt; If an application manages business logic, database mutations, and complex JSON schemas, Node.js performs well. But when a system primarily shuttles bytes between network cards without mutating payloads, Go's pointer controls, fixed-size types, and native network stack offer a fundamental advantage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Account for Load Client Overhead.&lt;/strong&gt; If a benchmarking tool shares a runtime environment or CPU thread pool with the system under test, metrics will be distorted. Switching to wrk uncovered significant performance reserves masked by client-side event loop contention.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory Control Directly Impacts Latency Stability.&lt;/strong&gt; Dropping baseline memory usage from 200 MB to 20 MB does more than save RAM, it eliminates GC-induced tail latency, stabilizing p99 metrics under sustained traffic spikes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://github.com/Ashish-Barmaiya/torus-proxy/blob/main/docs/benchmarking/reports/Benchmark-001-nodejs-to-go-performance-evaluation.md" rel="noopener noreferrer"&gt;full benchmark&lt;/a&gt; specifications, &lt;a href="https://github.com/Ashish-Barmaiya/torus-proxy/blob/main/docs/engineering/decision-records/ADR-001-rewrite-torus-from-nodejs-to-go.md" rel="noopener noreferrer"&gt;ADR records&lt;/a&gt;, and Go implementation are available in the &lt;a href="https://github.com/Ashish-Barmaiya/torus-proxy" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>node</category>
      <category>performance</category>
      <category>backend</category>
    </item>
    <item>
      <title>Surviving Node.js Clusters: Graceful Teardowns, Windows Quirks, and Black-Box Testing</title>
      <dc:creator>Ashish Barmaiya</dc:creator>
      <pubDate>Tue, 07 Apr 2026 01:40:34 +0000</pubDate>
      <link>https://dev.to/ashishbarmaiya/surviving-nodejs-clusters-graceful-teardowns-windows-quirks-and-black-box-testing-1aom</link>
      <guid>https://dev.to/ashishbarmaiya/surviving-nodejs-clusters-graceful-teardowns-windows-quirks-and-black-box-testing-1aom</guid>
      <description>&lt;p&gt;When I set out to build &lt;strong&gt;Torus&lt;/strong&gt;, a reverse proxy in Node.js, the &lt;code&gt;node:cluster&lt;/code&gt; module looked like the perfect solution to my biggest architectural hurdle. Because Node.js is natively single-threaded, this core module is the standard way to achieve multi-core scaling - it lets a single application spawn multiple worker processes that all share the same server port.&lt;/p&gt;

&lt;p&gt;It solved the problem immediately. But it quietly introduced a serious vulnerability I didn't catch until it was almost too late.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Trap: Blind Resurrection
&lt;/h2&gt;

&lt;p&gt;After scaffolding the cluster, I wrote the standard &lt;code&gt;if (cluster.isPrimary)&lt;/code&gt; block, looped through CPU cores, called &lt;code&gt;cluster.fork()&lt;/code&gt;, and attached an &lt;code&gt;exit&lt;/code&gt; listener to respawn workers on crash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Worker &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; died. Booting a replacement...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the surface, this is exactly right. If a worker blows up with an Out-Of-Memory exception or a rogue regex tears down the V8 engine, the Master catches the exit event and spawns a fresh replacement. Capacity heals itself.&lt;/p&gt;

&lt;p&gt;The problem is that this code is completely blind. It doesn't know &lt;em&gt;why&lt;/em&gt; a worker died. It only knows that a process stopped, and its only response is to immediately restart it.&lt;/p&gt;

&lt;p&gt;This breaks the moment you attempt a routine deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Kubernetes Tug-of-War
&lt;/h3&gt;

&lt;p&gt;Imagine this code running in a Docker container orchestrated by Kubernetes. You push version 1.1 of your proxy. Kubernetes initiates a rolling update and sends a &lt;code&gt;SIGTERM&lt;/code&gt; to your pod: "Finish your active requests and shut down cleanly."&lt;/p&gt;

&lt;p&gt;Your workers receive the signal, drain their active TCP sockets, and exit with code &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the millisecond the first worker exits, your Master's &lt;code&gt;.on('exit')&lt;/code&gt; listener fires. It doesn't see a coordinated graceful shutdown - it sees a dead process, and it immediately forks a replacement.&lt;/p&gt;

&lt;p&gt;While Kubernetes is trying to peacefully drain your pod, your Master is frantically spawning new workers to replace the ones shutting down. You're now locked in a fight with your own infrastructure.&lt;/p&gt;

&lt;p&gt;Eventually Kubernetes runs out of patience, fires a &lt;code&gt;SIGKILL&lt;/code&gt;, and brutally terminates the Master along with every zombie worker it just spawned. Any clients that were mid-stream have their TCP connections severed instantly.&lt;/p&gt;

&lt;p&gt;The "zero-downtime deployment" was a lie.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix: A State-Gated Lifecycle
&lt;/h2&gt;

&lt;p&gt;To fix this, the Master process needs to distinguish between two very different events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An &lt;strong&gt;unexpected crash&lt;/strong&gt; (a V8 segfault, an OOM exception) → resurrect immediately&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An &lt;strong&gt;intentional shutdown&lt;/strong&gt; (a Kubernetes eviction, a &lt;code&gt;SIGTERM&lt;/code&gt;) → stand down and let workers die cleanly&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That requires three things: a &lt;strong&gt;global state lock&lt;/strong&gt;, an &lt;strong&gt;IPC broadcast&lt;/strong&gt;, and a &lt;strong&gt;lifecycle manager with a hard timeout&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Global State Lock
&lt;/h3&gt;

&lt;p&gt;In the Master process, a single boolean flag acts as the guillotine switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isShuttingDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.on('exit')&lt;/code&gt; listener is rewritten to check this flag before doing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isShuttingDown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Worker &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; exited cleanly during shutdown.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All workers stopped. Master exiting with code 0.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Worker &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; crashed. Forking a replacement...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the cluster is shutting down, the Master lets workers die in peace and waits until the pool is empty before exiting cleanly. If the flag is &lt;code&gt;false&lt;/code&gt;, a worker crashed unexpectedly and gets resurrected immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Teardown Broadcast via IPC
&lt;/h3&gt;

&lt;p&gt;When the OS sends &lt;code&gt;SIGTERM&lt;/code&gt;, the Master intercepts it, flips the lock, and broadcasts a &lt;code&gt;SHUTDOWN&lt;/code&gt; command to every worker over Node's native IPC channel - rather than killing them instantly and dropping active payloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initiateClusterTeardown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isShuttingDown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;isShuttingDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Master received termination signal. Broadcasting shutdown to workers...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHUTDOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;initiateClusterTeardown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;initiateClusterTeardown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The LifecycleManager and the 10-Second Guillotine
&lt;/h3&gt;

&lt;p&gt;Inside each worker, the &lt;code&gt;SHUTDOWN&lt;/code&gt; message is handled by a &lt;code&gt;LifecycleManager&lt;/code&gt; singleton - a single object that owns the worker's entire teardown sequence and prevents the race conditions that come from ad-hoc async cleanup code.&lt;/p&gt;

&lt;p&gt;When it receives the command, it executes a strict sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stop the bleeding&lt;/strong&gt; - immediately reject new incoming TCP connections&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Destroy idle sockets&lt;/strong&gt; - aggressively close idle Keep-Alive connections to free OS file descriptors&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Drain active payloads&lt;/strong&gt; - wait for in-progress streams to finish naturally&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 has a fatal edge case: a slow or malicious client trickling 1 byte per second will keep &lt;code&gt;server.close()&lt;/code&gt; waiting indefinitely, hanging the deployment pipeline forever.&lt;/p&gt;

&lt;p&gt;The LifecycleManager solves this with a hard 10-second timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside LifecycleManager.executeTeardown()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drainPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A teardown task failed during shutdown.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeoutPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Shutdown timed out after &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SHUTDOWN_TIMEOUT_MS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SHUTDOWN_TIMEOUT_MS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unref&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// unref() prevents the timer from keeping the event loop alive&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;race&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;drainPromise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeoutPromise&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All systems cleanly drained. Exiting process gracefully.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Graceful shutdown aborted. Forcing exit.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker handles the IPC message like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHUTDOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Lifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeTeardown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IPC_SHUTDOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place: if a worker segfaults, it heals in milliseconds. If Kubernetes sends &lt;code&gt;SIGTERM&lt;/code&gt;, the proxy drains active connections cleanly, brutally severs any hanging connections after 10 seconds, and exits with code &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The theoretical architecture was solid. Proving it worked was a different problem entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Testing Nightmare: Why Jest Can't Test This
&lt;/h2&gt;

&lt;p&gt;Standard Jest unit tests are fundamentally incapable of testing a multi-process cluster. Jest runs inside a single Node.js process. Calling &lt;code&gt;cluster.fork()&lt;/code&gt; or &lt;code&gt;process.exit()&lt;/code&gt; from inside a test will either crash the test runner or leave orphan processes silently eating RAM in the background.&lt;/p&gt;

&lt;p&gt;To prove Torus could survive a crash and execute a graceful teardown, I abandoned unit testing entirely and built a &lt;strong&gt;black-box integration test&lt;/strong&gt;. The test treats the compiled proxy as a hostile external artifact: it boots it, wiretaps its output, attacks it, and evaluates how it responds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Boot the Cluster in Isolation
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;child_process.spawn&lt;/code&gt;, the test starts the compiled binary exactly as production would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entryPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../dist/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;masterProcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--env-file=.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entryPoint&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ipc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ipc&lt;/code&gt; channel in &lt;code&gt;stdio&lt;/code&gt; is important - it's how the test sends commands to the Master later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Wiretap stdout and Assassinate a Worker
&lt;/h3&gt;

&lt;p&gt;Because the proxy runs in an isolated background process, internal variables aren't accessible. Instead, the test intercepts raw stdout. When a worker logs that it's ready, the test extracts its PID and sends it a &lt;code&gt;SIGKILL&lt;/code&gt; - bypassing any graceful shutdown handler entirely, simulating a fatal OOM crash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;masterProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;targetWorkerPid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;listening for Secure HTTPS traffic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Worker&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;+listening/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;targetWorkerPid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetWorkerPid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGKILL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Instant, uninterceptable death&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SIGKILL&lt;/code&gt; cannot be caught by any signal handler. This is the correct way to simulate catastrophic process death.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Verify Resurrection and Trigger Teardown
&lt;/h3&gt;

&lt;p&gt;The test continues monitoring stdout. Once it sees the Master log a resurrection, it locks a boolean and sends a shutdown command via IPC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetWorkerPid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crashed. Forking a replacement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;workerResurrected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;workerResurrected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;masterProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TEST_SHUTDOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Give the cluster 500ms to stabilize before triggering teardown&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: The Final Verdict
&lt;/h3&gt;

&lt;p&gt;When the last worker exits, the Master closes. The test uses the &lt;code&gt;close&lt;/code&gt; event as its final assertion gate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;masterProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workerResurrected&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Cluster self-healed after crash&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shutdownInitiated&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// IPC teardown broadcast worked&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// Master exited cleanly&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test was flawless. It spawned the cluster, assassinated the worker, verified the resurrection, and cleanly exited.&lt;/p&gt;

&lt;p&gt;But notice that &lt;code&gt;TEST_SHUTDOWN&lt;/code&gt; command in Step 3? I didn't write it that way originally. Originally, I just told the test to send a standard SIGINT to the Master. But when I ran it, my Windows machine silently choked to death.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Windows Problem: POSIX Signals Are a Lie
&lt;/h2&gt;

&lt;p&gt;The black-box test spawned the cluster, killed a worker, and watched the Master resurrect it. The final step was to prove the &lt;code&gt;LifecycleManager&lt;/code&gt; could execute a zero-downtime graceful shutdown.&lt;/p&gt;

&lt;p&gt;To simulate a Kubernetes pod eviction or a developer hitting Ctrl+C, the integration test needed to send a termination signal to the Master process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;masterProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran the test. It failed instantly.&lt;/p&gt;

&lt;p&gt;The Master didn't gracefully drain the TCP sockets. It didn't trigger the 10-second timeout. It just died on the spot. The logs went completely silent.&lt;/p&gt;

&lt;p&gt;On Linux and macOS, sending a termination signal to the Master process works perfectly. &lt;code&gt;SIGINT&lt;/code&gt; is a native POSIX signal that politely knocks on the process's door and allows the Node.js event loop to execute its &lt;code&gt;.on('SIGINT')&lt;/code&gt; handler.&lt;/p&gt;

&lt;p&gt;Windows doesn't have native POSIX signals.&lt;/p&gt;

&lt;p&gt;When a Node.js process programmatically calls &lt;code&gt;.kill()&lt;/code&gt; with &lt;code&gt;SIGINT&lt;/code&gt; on Windows, the OS can't route it through the event loop. Instead, it bypasses the signal handler entirely and terminates the target process immediately. My Master process wasn't failing to drain its sockets - Windows was killing it before the &lt;code&gt;LifecycleManager&lt;/code&gt; could even start.&lt;/p&gt;

&lt;p&gt;The fix was to bypass the OS signal layer entirely. I added a dedicated IPC backdoor directly in the Master's message handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Testing Backdoor for Windows OS limitations&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TEST_SHUTDOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;initiateClusterTeardown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TEST_SHUTDOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the test, &lt;code&gt;.kill("SIGINT")&lt;/code&gt; became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;masterProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TEST_SHUTDOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sends a plain JSON payload over the IPC channel - no OS signal routing required. The Master receives it, flips the &lt;code&gt;isShuttingDown&lt;/code&gt; flag, broadcasts teardown to the workers, and the LifecycleManager executes the full drain sequence.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CI/CD Trap: Environment Drift
&lt;/h2&gt;

&lt;p&gt;With the Windows fix in place, the local test suite passed cleanly. I pushed to GitHub and waited for the green checkmark.&lt;/p&gt;

&lt;p&gt;The pipeline spun for 15 seconds and failed silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnosing the Silence
&lt;/h3&gt;

&lt;p&gt;When the black-box test's wiretap never hears the expected log line, it just sits there until Jest's timeout guillotine drops. To find out what was actually happening in the CI runner, I temporarily dumped raw stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;masterProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Raw output — for CI diagnostics only&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the next run, the pipeline printed the truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ENOENT: no such file or directory, open '/home/runner/work/torus-proxy/certs/key.pem'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Missing Certificates
&lt;/h3&gt;

&lt;p&gt;TLS certificates belong in &lt;code&gt;.gitignore&lt;/code&gt;. On my local machine they existed. On GitHub Actions' naked Ubuntu runner, they didn't. Workers hit the TLS configuration block, threw an &lt;code&gt;ENOENT&lt;/code&gt;, and died before ever binding to a port.&lt;/p&gt;

&lt;p&gt;Because the proxy runs as a real detached process, &lt;code&gt;jest.mock('fs')&lt;/code&gt; isn't an option - the isolated binary needs real files on disk. The fix was to generate dummy self-signed certificates in the CI pipeline before the test runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate Dummy TLS Certificates&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;mkdir -p certs&lt;/span&gt;
    &lt;span class="s"&gt;openssl req -nodes -new -x509 \&lt;/span&gt;
      &lt;span class="s"&gt;-keyout certs/key.pem \&lt;/span&gt;
      &lt;span class="s"&gt;-out certs/cert.pem \&lt;/span&gt;
      &lt;span class="s"&gt;-days 365 \&lt;/span&gt;
      &lt;span class="s"&gt;-subj "/CN=localhost"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Missing Environment Variables
&lt;/h3&gt;

&lt;p&gt;The pipeline failed again. This time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: JWT Secret is required to boot JWT Authenticator.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file was also gitignored. Rather than writing a fake &lt;code&gt;.env&lt;/code&gt; to the runner disk, I injected the dummy secret directly into the &lt;code&gt;spawn&lt;/code&gt; environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;envPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodeArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;envPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--env-file=.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entryPoint&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;entryPoint&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nx"&gt;masterProcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nodeArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ipc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dummy_test_secret_for_ci_pipeline_only&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I pushed the final commit. The pipeline generated the certificates, injected the secret, booted the Master, assassinated a worker, watched it resurrect, triggered teardown, drained sockets, and exited with code &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Green checkmark.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Trust Nothing
&lt;/h2&gt;

&lt;p&gt;The standard Node.js cluster tutorials are dangerously incomplete. Blindly resurrecting workers on every &lt;code&gt;exit&lt;/code&gt; event creates a system that will fight your deployment pipelines and drop active TCP connections mid-stream.&lt;/p&gt;

&lt;p&gt;Building something production-grade requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deterministic state machines&lt;/strong&gt; - not reflexive code that acts without knowing why things happened&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structured teardown sequences&lt;/strong&gt; - not ad-hoc async cleanup that races itself&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skepticism about the OS&lt;/strong&gt; - signals behave differently across platforms in ways the documentation doesn't always make clear&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Black-box integration tests&lt;/strong&gt; - not unit tests that mock away the exact failure modes you're trying to protect against&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CI environment parity&lt;/strong&gt; - assume the runner has nothing your &lt;code&gt;.gitignore&lt;/code&gt; excluded&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cluster module is powerful. But its standard usage pattern gives you the illusion of resilience, not the real thing.&lt;/p&gt;

&lt;p&gt;If you'd like to see the full implementation, the source code for Torus Proxy is on &lt;a href="https://github.com/Ashish-Barmaiya/torus-proxy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>devops</category>
      <category>backend</category>
      <category>testing</category>
    </item>
    <item>
      <title>Why I Ripped stream.pipe() Out of My Node.js API Gateway</title>
      <dc:creator>Ashish Barmaiya</dc:creator>
      <pubDate>Fri, 27 Mar 2026 16:09:13 +0000</pubDate>
      <link>https://dev.to/ashishbarmaiya/why-i-ripped-streampipe-out-of-my-nodejs-api-gateway-2ekl</link>
      <guid>https://dev.to/ashishbarmaiya/why-i-ripped-streampipe-out-of-my-nodejs-api-gateway-2ekl</guid>
      <description>&lt;p&gt;When I started building Torus, a multi-core Layer 7 Edge API Gateway from scratch in Node.js, I handled incoming network requests the way I had always seen it done in standard web applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;forwardToBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It worked perfectly for lightweight tests. But as I started pushing concurrent loads and larger payloads through the proxy, my server began to choke. CPU usage spiked to 100%, the event loop lagged, and memory consumption grew uncontrollably until the process crashed.&lt;/p&gt;

&lt;p&gt;I had fallen into a classic architectural trap: I was dragging raw TCP payload bytes directly into the V8 JavaScript engine's memory heap.&lt;/p&gt;

&lt;p&gt;Because the V8 heap has a strict memory limit, pulling massive payloads into user-space memory forces the Node.js Garbage Collector (GC) to work overtime. The GC halts the single-threaded event loop to clean up the allocated memory, effectively stalling every other active network connection in the proxy.&lt;/p&gt;

&lt;p&gt;I learned a fundamental rule of proxy engineering the hard way: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Proxies shouldn't read data; they should just move it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To build a production-grade gateway, I realized I had to bypass the V8 heap entirely. I needed to keep the data in raw C++ memory blocks and move it to the Operating System level. But as I refactored my routing logic to achieve this, I stumbled into a silent, catastrophic flaw in the standard Node.js stream API that brought my entire test suite to a halt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Evolution: Bypassing V8 with &lt;code&gt;.pipe()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The architectural fix required a fundamental shift in how I viewed the data. I had to stop treating payloads as static variables and start treating them as flowing water.&lt;/p&gt;

&lt;p&gt;I didn't need to load an entire 50MB file into memory before forwarding it. I only needed to hold a few kilobytes in a temporary buffer, flush it to the destination, and reuse that memory space.&lt;/p&gt;

&lt;p&gt;In Node.js, this is exactly what the &lt;code&gt;node:stream&lt;/code&gt; module and &lt;code&gt;Buffer&lt;/code&gt; objects are designed for.&lt;/p&gt;

&lt;p&gt;A Buffer in Node.js allocates memory outside the V8 JavaScript engine. It utilizes raw C++ memory blocks mapped directly to the OS. By keeping the network chunks as raw Buffers, the payload never enters the JavaScript heap. Because it never enters the heap, the V8 Garbage Collector completely ignores it, leaving the event loop free to handle other connections.&lt;/p&gt;

&lt;p&gt;To wire this up, I utilized the native &lt;code&gt;.pipe()&lt;/code&gt; method to connect the readable client stream to the writable backend stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;

&lt;span class="c1"&gt;// Connects the incoming ReadableStream directly to the outgoing WritableStream&lt;/span&gt;
&lt;span class="nx"&gt;clientReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxyReq&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single line of code acts as an OS-level plumbing system. It takes the incoming TCP stream, reads the raw C++ buffers, automatically manages the backpressure (ensuring a fast client doesn't overwhelm a slow backend connection), and pushes the bytes directly out to the routing pool.&lt;/p&gt;

&lt;p&gt;My CPU usage plummeted. The memory footprint stayed flat, regardless of how large the incoming payloads were. It felt like I had solved the scaling problem entirely.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;.pipe()&lt;/code&gt; was hiding a massive, silent vulnerability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plot Twist: The Silent Socket Leak
&lt;/h2&gt;

&lt;p&gt;I thought I had engineered the perfect solution. The proxy was fast, the CPU was idle, and the memory footprint stayed completely flat, regardless of payload size.&lt;/p&gt;

&lt;p&gt;Then I ran my integration test suite.&lt;/p&gt;

&lt;p&gt;All the assertions passed. I got the green checkmarks. But the terminal just froze. Jest refused to exit, eventually spitting out that infuriating warning: "Jest did not exit one second after the test run has completed."&lt;/p&gt;

&lt;p&gt;My initial reaction was to treat it like a standard web app bug. I meticulously checked my teardown logic, making sure &lt;code&gt;proxyServer.close()&lt;/code&gt; was being called and my Redis clients were fully disconnected. I ran the tests again. It still hung.&lt;/p&gt;

&lt;p&gt;I had to drop down to the OS level to understand what was actually happening. The Node.js event loop is mathematically programmed to never exit as long as there is an active I/O handle (like a &lt;code&gt;net.Socket&lt;/code&gt;) in its queue. Something was keeping a socket alive.&lt;/p&gt;

&lt;p&gt;The culprit was &lt;code&gt;.pipe().&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When my Jest test fired a dummy request through the proxy and then disconnected, the client-side socket closed gracefully. But I learned a lesson about Node.js streams: &lt;code&gt;.pipe()&lt;/code&gt; blindly pushes data; it does not propagate lifecycle events.&lt;/p&gt;

&lt;p&gt;When the client dropped, &lt;code&gt;.pipe()&lt;/code&gt; did not send an error or close event to the destination stream. It left the backend connection completely open. The proxy was sitting there holding a dead connection to the backend, waiting for network bytes that would never arrive.&lt;/p&gt;

&lt;p&gt;I had built a machine that generated half-open sockets. In a production environment, this would silently exhaust the Operating System's File Descriptors (FDs). Every dropped client connection would permanently lock up an FD until the OS hit its limit and violently crash the Node process with an &lt;code&gt;EMFILE&lt;/code&gt; error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: stream.pipeline()
&lt;/h2&gt;

&lt;p&gt;The Node.js core maintainers knew &lt;code&gt;.pipe()&lt;/code&gt; was dangerously naive for production infrastructure. That is exactly why they introduced &lt;code&gt;stream.pipeline()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of blindly shoving data from one socket to another, &lt;code&gt;.pipeline()&lt;/code&gt; acts as a unified state machine that monitors the entire stream chain. It pushes the responsibility of socket teardown back to the Node.js core networking stack where it belongs.&lt;/p&gt;

&lt;p&gt;If any stream in the pipeline fails, throws an error, or abruptly closes (like a client dropping off with an &lt;code&gt;ECONNRESET&lt;/code&gt;), &lt;code&gt;.pipeline()&lt;/code&gt; automatically intercepts it. It destroys all other connected streams in that specific chain and bubbles up a single error for you to catch.&lt;/p&gt;

&lt;p&gt;I removed every instance of &lt;code&gt;.pipe()&lt;/code&gt; and the dozens of lines of manual &lt;code&gt;.on('error')&lt;/code&gt; spaghetti I had written. Because raw TCP proxying requires bidirectional data flow, I replaced it with two parallel, sequential pipelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TypeScript&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;backendSocket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;backendSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientSocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// If either side drops, the pipeline throws, and we clean up natively.&lt;/span&gt;
  &lt;span class="nx"&gt;clientSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;backendSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The moment I swapped to this architecture and ran my integration suite, the terminal didn't hang. Jest executed all 18 network tests and exited flawlessly in 2.7 seconds. The event loop was instantly cleared. The silent socket leak was completely eradicated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fercx9tbcdrgcidzs0u7m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fercx9tbcdrgcidzs0u7m.png" alt="Terminal screenshot showing 18 Jest network tests passing flawlessly in 2.7 seconds."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Trust Nothing, Understand Everything
&lt;/h2&gt;

&lt;p&gt;Building a multi-core Edge Gateway from scratch taught me that I cannot blindly trust high-level abstractions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stream.pipe()&lt;/code&gt; looks elegant in a standard web tutorial, but in the trenches of raw TCP networking, it is a massive liability. If you are building infrastructure that handles thousands of concurrent connections, you must understand the Operating System-level lifecycle of your File Descriptors and your sockets. If you don't, your system will slowly bleed to death under load, and logs won't even tell you why.&lt;/p&gt;

&lt;p&gt;If you want to see the exact implementation of this bidirectional TCP routing, you can check out the raw source code for &lt;a href="https://github.com/Ashish-Barmaiya/torus-proxy" rel="noopener noreferrer"&gt;Torus Proxy on my GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>backend</category>
      <category>performance</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
