<?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: Joshua Varghese</title>
    <description>The latest articles on DEV Community by Joshua Varghese (@joshuabvarghese).</description>
    <link>https://dev.to/joshuabvarghese</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2391526%2Fb63c44d5-d212-44bd-af5a-7684e5e2f257.png</url>
      <title>DEV Community: Joshua Varghese</title>
      <link>https://dev.to/joshuabvarghese</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joshuabvarghese"/>
    <language>en</language>
    <item>
      <title>How I stopped 100 goroutines from hammering my gRPC server — Loom Part 2</title>
      <dc:creator>Joshua Varghese</dc:creator>
      <pubDate>Sun, 07 Jun 2026 14:16:09 +0000</pubDate>
      <link>https://dev.to/joshuabvarghese/how-i-stopped-100-goroutines-from-hammering-my-grpc-server-loom-part-2-2hb8</link>
      <guid>https://dev.to/joshuabvarghese/how-i-stopped-100-goroutines-from-hammering-my-grpc-server-loom-part-2-2hb8</guid>
      <description>&lt;p&gt;&lt;strong&gt;This is Part 2 of my series building Loom.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
👉 Missed Part 1? &lt;a href="https://dev.to/joshuabvarghese/i-built-a-grpc-debugging-proxy-as-my-first-serious-go-project-heres-what-i-learned-mhp"&gt;Read it here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Today:&lt;/strong&gt; Reflection cache, stampede protection, and the deadlock that kept me up until 11 PM.&lt;/p&gt;


&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;When 50 goroutines all need the same method descriptor at the same time, my naive code made ALL 50 hit the backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ReflectionCache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MethodDescriptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchFromBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// 🔥 50x RPC calls&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Result: 50 identical calls. 50x load. 50x latency. Not good.&lt;/p&gt;


&lt;h2&gt;
  
  
  The fix: singleflight
&lt;/h2&gt;

&lt;p&gt;Go has singleflight in golang.org/x/sync — it ensures only one goroutine fetches, the rest wait for that result.&lt;/p&gt;
&lt;h2&gt;
  
  
  Final code:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"golang.org/x/sync/singleflight"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ReflectionCache&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MethodDescriptor&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;    &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="n"&gt;singleflight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ReflectionCache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MethodDescriptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Fast path: already cached?&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Slow path: single fetch, everyone waits&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchFromBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MethodDescriptor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What changed: 1 backend call instead of 50. All 50 goroutines get the result in ~50ms instead of 2500ms.&lt;/p&gt;


&lt;h2&gt;
  
  
  The embarrassing deadlock
&lt;/h2&gt;

&lt;p&gt;I tried building this myself first. Here's the bug that took 3 hours:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ⚠️ DEADLOCK — Don't do this&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ReflectionCache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MethodDescriptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c"&gt;// ❌ This will run later&lt;/span&gt;

    &lt;span class="c"&gt;// ... check cache ...&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c"&gt;// Manual unlock&lt;/span&gt;
    &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchFromBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    &lt;span class="c"&gt;// Re-lock&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;  &lt;span class="c"&gt;// defer still tries to unlock → panic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Lesson: Don't mix defer and manual lock/unlock. And just use singleflight.&lt;/p&gt;
&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Approach    Backend calls (100 reqs)    Total time&lt;br&gt;
No cache    100 5000ms&lt;br&gt;
Mutex only  1   5000ms&lt;br&gt;
Singleflight    1   ~52ms&lt;br&gt;
96% faster.&lt;/p&gt;


&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;Cache stampedes are real — they'll crush your backend&lt;br&gt;
singleflight is your friend — don't roll your own&lt;br&gt;
Test with -race — it catches deadlocks&lt;br&gt;
Read locks (RLock) for cache hits — saves contention&lt;/p&gt;

&lt;p&gt;Try Loom yourself &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/joshuabvarghese" rel="noopener noreferrer"&gt;
        joshuabvarghese
      &lt;/a&gt; / &lt;a href="https://github.com/joshuabvarghese/Loom" rel="noopener noreferrer"&gt;
        Loom
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      gRPC L7 Debugging Proxy
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Loom&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A gRPC debugging proxy. Point it at your backend, point your client at Loom, and watch every call decoded in a browser tab.&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Your gRPC Client  →  Loom (:9999)  →  Your Backend (:50051)
                          ↓
                    Web Inspector
                  http://localhost:9998
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="https://golang.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dcf5eec8a143fa47f3918e4537cdaf3add79b9d66d884084f26691fa945add8e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f676f2d312e32312b2d626c7565" alt="Go Version"&gt;&lt;/a&gt;
&lt;a href="https://github.com/joshuabvarghese/Loom/LICENCE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;gRPC traffic is binary. Wireshark can't read it. &lt;code&gt;grpcurl&lt;/code&gt; is great for one-off calls but you can't watch a flow. I kept running it over and over trying to understand what was happening between services.&lt;/p&gt;

&lt;p&gt;Loom sits transparently between your client and backend. It uses &lt;a href="https://github.com/grpc/grpc/blob/master/doc/server-reflection.md" rel="noopener noreferrer"&gt;Server Reflection&lt;/a&gt; to decode every frame on the fly — no &lt;code&gt;.proto&lt;/code&gt; files required — and streams the results into a browser UI. You see the JSON payloads, the status codes, how long each call took, and a ready-to-copy &lt;code&gt;grpcurl&lt;/code&gt; command to replay any of them.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;Intercepts &lt;strong&gt;all four gRPC stream types&lt;/strong&gt; — unary, server-streaming, client-streaming, bidi&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Auto-decodes&lt;/strong&gt; using Server Reflection (no proto…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/joshuabvarghese/Loom" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>go</category>
      <category>opensource</category>
      <category>devops</category>
      <category>github</category>
    </item>
    <item>
      <title>I built a gRPC debugging proxy as my first serious Go project – here's what I learned</title>
      <dc:creator>Joshua Varghese</dc:creator>
      <pubDate>Tue, 02 Jun 2026 02:17:46 +0000</pubDate>
      <link>https://dev.to/joshuabvarghese/i-built-a-grpc-debugging-proxy-as-my-first-serious-go-project-heres-what-i-learned-mhp</link>
      <guid>https://dev.to/joshuabvarghese/i-built-a-grpc-debugging-proxy-as-my-first-serious-go-project-heres-what-i-learned-mhp</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built &lt;a href="https://github.com/joshuabvarghese/Loom" rel="noopener noreferrer"&gt;Loom&lt;/a&gt;, a transparent gRPC proxy that decodes traffic in real-time using Server Reflection. No &lt;code&gt;.proto&lt;/code&gt; files needed. It gives you a browser UI to watch, inspect, and replay calls.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/joshuabvarghese" rel="noopener noreferrer"&gt;
        joshuabvarghese
      &lt;/a&gt; / &lt;a href="https://github.com/joshuabvarghese/Loom" rel="noopener noreferrer"&gt;
        Loom
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      gRPC L7 Debugging Proxy
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Loom&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A gRPC debugging proxy. Point it at your backend, point your client at Loom, and watch every call decoded in a browser tab.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Your gRPC Client  →  Loom (:9999)  →  Your Backend (:50051)
                          ↓
                    Web Inspector
                  http://localhost:9998
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="https://golang.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dcf5eec8a143fa47f3918e4537cdaf3add79b9d66d884084f26691fa945add8e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f676f2d312e32312b2d626c7565" alt="Go Version"&gt;&lt;/a&gt;
&lt;a href="https://github.com/joshuabvarghese/Loom/LICENCE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;gRPC traffic is binary. Wireshark can't read it. &lt;code&gt;grpcurl&lt;/code&gt; is great for one-off calls but you can't watch a flow. I kept running it over and over trying to understand what was happening between services.&lt;/p&gt;
&lt;p&gt;Loom sits transparently between your client and backend. It uses &lt;a href="https://github.com/grpc/grpc/blob/master/doc/server-reflection.md" rel="noopener noreferrer"&gt;Server Reflection&lt;/a&gt; to decode every frame on the fly — no &lt;code&gt;.proto&lt;/code&gt; files required — and streams the results into a browser UI. You see the JSON payloads, the status codes, how long each call took, and a ready-to-copy &lt;code&gt;grpcurl&lt;/code&gt; command to replay any of them.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Intercepts &lt;strong&gt;all four gRPC stream types&lt;/strong&gt; — unary, server-streaming, client-streaming, bidi&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-decodes&lt;/strong&gt; using Server Reflection (no proto…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/joshuabvarghese/Loom" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  The problem that wouldn't go away
&lt;/h2&gt;

&lt;p&gt;I was working on a side project with gRPC between two services, and I kept hitting the same wall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The traffic is binary.&lt;/strong&gt; You can't just open DevTools and watch what's happening.&lt;/p&gt;

&lt;p&gt;My workflow became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;grpcurl&lt;/code&gt; over and over&lt;/li&gt;
&lt;li&gt;Copy-paste the same commands&lt;/li&gt;
&lt;li&gt;Try to piece together what went wrong&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was tedious. And I kept thinking — &lt;em&gt;why isn't there just something I can WATCH?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Loom does
&lt;/h2&gt;

&lt;p&gt;Loom sits transparently between your gRPC client and backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Point your client at Loom instead of your backend&lt;/strong&gt;, and it proxies everything through while decoding each frame on the fly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you get:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🔴 &lt;strong&gt;Real-time call inspection&lt;/strong&gt; — every request/response appears in your browser&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;JSON payloads&lt;/strong&gt; — no more squinting at binary&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Status codes &amp;amp; latency&lt;/strong&gt; — at a glance&lt;/li&gt;
&lt;li&gt;📋 &lt;strong&gt;Copyable &lt;code&gt;grpcurl&lt;/code&gt; commands&lt;/strong&gt; — replay any call instantly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No &lt;code&gt;.proto&lt;/code&gt; files needed.&lt;/strong&gt; It uses Server Reflection automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part that surprised me most
&lt;/h2&gt;

&lt;p&gt;I thought the hardest part would be the HTTP/2 plumbing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It wasn't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The thing that took me the longest was the &lt;strong&gt;reflection cache&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's the problem: when multiple goroutines all need the same method descriptor at the same time, the naive approach makes ALL of them hit the backend simultaneously. That's wasteful and slow.&lt;/p&gt;

&lt;p&gt;I needed what's called &lt;strong&gt;stampede protection&lt;/strong&gt; — only one goroutine does the fetch, the rest wait.&lt;/p&gt;

&lt;p&gt;Getting the mutex logic right without deadlocking took me an embarrassing amount of time. Like, staring at the screen at 11 PM embarrassing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But I got there.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Split up the web UI earlier
&lt;/h3&gt;

&lt;p&gt;The web UI is one 40KB file. It works. But if I was starting again, I'd split it up properly.&lt;/p&gt;

&lt;p&gt;I kept telling myself &lt;em&gt;"I'll refactor it later"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Later never came. 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Write tests FIRST for the circuit breaker
&lt;/h3&gt;

&lt;p&gt;I wrote them after implementing it. Found &lt;strong&gt;two edge cases&lt;/strong&gt; I'd missed in the half-open state.&lt;/p&gt;

&lt;p&gt;Lesson learned. Tests first. Always.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned about Go specifically
&lt;/h2&gt;

&lt;p&gt;Coming from other languages, I underestimated how much Go's concurrency model would change my thinking.&lt;/p&gt;

&lt;p&gt;Once I stopped fighting it and just thought in &lt;strong&gt;goroutines and channels&lt;/strong&gt;, everything clicked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The circuit breaker&lt;/li&gt;
&lt;li&gt;The recorder fanning out to three sinks simultaneously&lt;/li&gt;
&lt;li&gt;The SSE hub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it felt &lt;em&gt;natural&lt;/em&gt; in a way it never would have in other languages.&lt;/p&gt;

&lt;p&gt;Go didn't just let me build this — it guided &lt;em&gt;how I thought about the problem&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;MIT licensed. Single binary. &lt;strong&gt;Has a &lt;code&gt;-demo&lt;/code&gt; mode&lt;/strong&gt; that needs nothing to get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run &lt;span class="nt"&gt;-mod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vendor &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-demo&lt;/span&gt;
&lt;span class="c"&gt;# open http://localhost:9998&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd love your feedback&lt;/p&gt;

&lt;p&gt;This is my first real Go project. I'm still learning.&lt;/p&gt;

&lt;p&gt;If you have thoughts on:&lt;/p&gt;

&lt;p&gt;The code architecture&lt;br&gt;
The idea itself&lt;br&gt;
Things I missed&lt;br&gt;
Or just want to say hi&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subscribe to get notified when Part 2 will talk more on Deep dive into the reflection cache &amp;amp; stampede protection when it drops&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>grpc</category>
      <category>showdev</category>
    </item>
    <item>
      <title>HAProxy's Zero-Downtime Reload</title>
      <dc:creator>Joshua Varghese</dc:creator>
      <pubDate>Sat, 09 Nov 2024 12:01:54 +0000</pubDate>
      <link>https://dev.to/joshuabvarghese/haproxys-zero-downtime-reload-312j</link>
      <guid>https://dev.to/joshuabvarghese/haproxys-zero-downtime-reload-312j</guid>
      <description>&lt;h2&gt;
  
  
  HAProxy's Reload Architecture
&lt;/h2&gt;

&lt;p&gt;HAProxy uses a sophisticated socket transfer mechanism between old and new processes. This design choice leads to some interesting trade-offs and benefits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Master CLI Process
&lt;/h2&gt;

&lt;p&gt;HAProxy's architecture includes a master CLI process that orchestrates the reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Example of HAProxy's master-worker socket handling */
static int proc_self_pipe[2];

void master_register_worker(struct worker *w) {
    struct listener *listener;

    list_for_each_entry(listener, &amp;amp;w-&amp;gt;listeners, list) {
        /* Transfer listener sockets to the new worker */
        if (listener-&amp;gt;state == LI_READY) {
            listener_transfer_fd(listener, w);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Socket Transfer Magic
&lt;/h2&gt;

&lt;p&gt;One of the most interesting aspects of HAProxy's implementation is how it handles socket transfers. The process involves several key steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Socket Preparation
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static int listener_transfer_fd(struct listener *l, struct worker *w) {
    struct cmsg_fd_list fdlist;
    struct msghdr msg;
    struct iovec iov[1];
    int ret;

    /* Prepare socket data structure */
    memset(&amp;amp;msg, 0, sizeof(msg));
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    /* Set up control message for FD passing */
    msg.msg_control = &amp;amp;fdlist;
    msg.msg_controllen = sizeof(fdlist);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Graceful Connection Handover
HAProxy ensures existing connections aren't disrupted during the reload:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void perform_soft_reload(void) {
    /* 1. Keep accepting new connections in old process */
    while (nb_running_tasks() &amp;gt; 0) {
        /* 2. Process existing connections */
        process_runnable_tasks();

        /* 3. Check if we can stop */
        if (should_exit()) {
            break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  State Management During Reload
&lt;/h2&gt;

&lt;p&gt;HAProxy's state management during reload is particularly clever. It handles several key aspects:&lt;/p&gt;

&lt;h2&gt;
  
  
  Connection State Preservation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct connection {
    unsigned int flags;     /* Status flags */
    enum obj_type *target;  /* What this connection is about */
    void *ctx;             /* Application specific context */
    /* ... other fields ... */
};

/* During transfer */
static void transfer_connection_state(struct connection *conn) {
    /* Save essential connection data */
    struct conn_state state = {
        .flags = conn-&amp;gt;flags,
        .protocol_state = conn-&amp;gt;ctx,
        .ssl_state = conn-&amp;gt;ssl_ctx
    };

    /* Transfer to new process */
    send_state_to_new_process(&amp;amp;state);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SSL Session Handling
&lt;/h2&gt;

&lt;p&gt;A particularly tricky part is managing SSL sessions during reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static int ssl_session_transfer(SSL *ssl, struct worker *new_worker) {
    unsigned char *session_data;
    unsigned int session_len;

    /* Serialize SSL session */
    session_data = ssl_serialize_session(ssl, &amp;amp;session_len);
    if (!session_data)
        return -1;

    /* Transfer to new process */
    return send_session_to_worker(new_worker, session_data, session_len);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration Validation
&lt;/h2&gt;

&lt;p&gt;Before any reload happens, HAProxy performs extensive configuration validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int check_config_validity(char *file) {
    struct proxy *curproxy = NULL;
    int cfgerr = 0;

    /* Parse and validate configuration */
    for (curproxy = proxy_list; curproxy; curproxy = curproxy-&amp;gt;next) {
        /* Check proxy settings */
        cfgerr += proxy_cfg_check(curproxy);

        /* Validate SSL configurations */
        cfgerr += ssl_cfg_check(curproxy);

        /* Check backend server configurations */
        cfgerr += check_backend_cfg(curproxy);
    }

    return cfgerr;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Health Check Continuity
&lt;/h2&gt;

&lt;p&gt;One often-overlooked aspect is maintaining health check states during reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct check {
    unsigned int status;   /* health check status */
    unsigned int result;   /* test result */
    int code;             /* status code */
    int duration;         /* time it took to get the result */
};

void transfer_health_checks(void) {
    struct server *srv;
    struct check *check;

    /* Iterate through all servers */
    for (srv = servers; srv; srv = srv-&amp;gt;next) {
        check = &amp;amp;srv-&amp;gt;check;

        /* Save health check state */
        if (check-&amp;gt;status &amp;amp; CHK_ST_ENABLED) {
            save_check_state(check);
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;HAProxy's reload mechanism is designed with performance in mind. Here are some key optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimal Memory Overhead: Only essential state is transferred&lt;/li&gt;
&lt;li&gt;Efficient FD Passing: Uses kernel mechanisms for file descriptor transfer&lt;/li&gt;
&lt;li&gt;Progressive Transfer: Connections are handled gradually to avoid spikes&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Understanding Envoy Proxy's Hot Restart Implementation</title>
      <dc:creator>Joshua Varghese</dc:creator>
      <pubDate>Sat, 09 Nov 2024 11:45:16 +0000</pubDate>
      <link>https://dev.to/joshuabvarghese/understanding-envoy-proxys-hot-restart-implementation-a-deep-dive-4a7o</link>
      <guid>https://dev.to/joshuabvarghese/understanding-envoy-proxys-hot-restart-implementation-a-deep-dive-4a7o</guid>
      <description>&lt;p&gt;As modern distributed systems grow in complexity, the ability to update proxy configurations without dropping active connections has become crucial. In this post, I'll break down how Envoy Proxy implements its hot restart mechanism, a feature that allows seamless configuration updates and binary upgrades without disrupting existing connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Hot Restart?
&lt;/h2&gt;

&lt;p&gt;Hot restart (or hot reload) is a mechanism that allows a proxy server to reload its configuration or upgrade its binary while maintaining existing client connections. This is achieved by having the new process take over the listening sockets and existing connections from the old process, ensuring zero connection drops during the transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Envoy's Approach
&lt;/h2&gt;

&lt;p&gt;Envoy implements hot restart through a parent-child process model, where the parent process manages the handover of socket descriptors to the new child process. Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Shared Memory Architecture
Envoy uses shared memory to facilitate communication between the old and new processes. This is implemented in the HotRestartImpl class:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class HotRestartImpl {
private:
  static constexpr uint64_t MAX_STAT_SEGMENTS = 256;
  SharedMemory* shmem_;
  Stats::StatDataAllocator* stats_allocator_;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Socket Passing Process
The hot restart process follows these key steps:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Initialize Shared Memory: The parent process creates a shared memory segment that both processes can access.&lt;br&gt;
Socket Duplication: The parent process duplicates its listening sockets.&lt;br&gt;
Graceful Handover: Traffic is gradually transferred to the new process.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of how Envoy handles socket passing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class HotRestartingChild {
public:
  void initialize(int argc, char** argv) {
    // Request parent's listen sockets
    std::vector&amp;lt;int&amp;gt; fds = parent_.retrieveListenSockets();

    // Initialize new server with inherited sockets
    for (int fd : fds) {
      Server::createListenerFromSocket(fd);
    }

    // Signal ready to parent
    parent_.sendReady();
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;State Transfer
One of the most critical aspects is transferring the state of existing connections:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void HotRestartImpl::drainListeners() {
  // 1. Stop accepting new connections
  for (auto&amp;amp; listener : listeners_) {
    listener-&amp;gt;stopAcceptingConnections();
  }


  // 2. Wait for existing connections to complete
  while (hasActiveConnections()) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  // 3. Signal completion to new process
  notifyNewProcess();
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Implementation Challenges
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;File Descriptor Handling
Envoy needs to carefully manage file descriptors to ensure they're properly transferred and not leaked:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Uses SCM_RIGHTS to pass file descriptors between processes&lt;br&gt;
Maintains a registry of active file descriptors&lt;br&gt;
Implements careful cleanup mechanisms&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connection State Management
The proxy must maintain connection state during the transition:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TCP connection parameters&lt;br&gt;
TLS session information&lt;br&gt;
Protocol-specific state (HTTP/2 streams, etc.)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configuration Compatibility
Envoy ensures that configuration changes are compatible with existing connections:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool HotRestartImpl::validateConfig(
  const envoy::config::bootstrap::v3::Bootstrap&amp;amp; new_config) {
  // Verify that critical fields haven't changed
  // Check listener compatibility
  // Validate cluster configurations
  return isCompatible;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Diving into Envoy's hot restart implementation has been quite the journey! It's fascinating to see how they've tackled the challenge of swapping out a running proxy without dropping connections. The elegant dance between parent and child processes, the careful handling of file descriptors, and the intricate state management all come together to make this possible.&lt;/p&gt;

&lt;p&gt;What really stands out is how much thought went into making the system robust. It's not just about passing sockets around – it's about handling edge cases, ensuring configuration compatibility, and providing fallback options when things don't go as planned.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This is a high-level overview based on Envoy's open-source implementation. For the most up-to-date and detailed information, please refer to the official Envoy documentation and source.[&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/arch_overview" rel="noopener noreferrer"&gt;https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/arch_overview&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
