<?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: Pedro Sakuma Travi</title>
    <description>The latest articles on DEV Community by Pedro Sakuma Travi (@pedrosakuma).</description>
    <link>https://dev.to/pedrosakuma</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%2F4029119%2F244313b9-edbc-420f-8b16-a91ffcef588a.jpg</url>
      <title>DEV Community: Pedro Sakuma Travi</title>
      <link>https://dev.to/pedrosakuma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pedrosakuma"/>
    <language>en</language>
    <item>
      <title>The pool that got slower the more we shared it</title>
      <dc:creator>Pedro Sakuma Travi</dc:creator>
      <pubDate>Tue, 14 Jul 2026 18:08:47 +0000</pubDate>
      <link>https://dev.to/pedrosakuma/the-pool-that-got-slower-the-more-we-shared-it-57oh</link>
      <guid>https://dev.to/pedrosakuma/the-pool-that-got-slower-the-more-we-shared-it-57oh</guid>
      <description>&lt;p&gt;&lt;em&gt;How a 15-minute chat with an LLM found a hidden &lt;code&gt;ArrayPool&amp;lt;byte&amp;gt;.Shared&lt;/code&gt;&lt;br&gt;
contention point that no static analyzer would ever flag — and why the fix&lt;br&gt;
had nothing to do with adding a lock.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The ticket
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"CPU on &lt;code&gt;market-data-gateway&lt;/code&gt; climbed ~20% after we added a broadcast fan-out&lt;br&gt;
path last sprint. No new allocations, no GC pressure, nothing obviously&lt;br&gt;
wrong in the diff. Can someone take a look before we scale out more pods?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nobody had touched anything that &lt;em&gt;looked&lt;/em&gt; like a synchronization primitive.&lt;br&gt;
The diff that shipped the week before only touched a buffer-pooling helper —&lt;br&gt;
swapping a bespoke byte-array cache for the "obviously correct" BCL default,&lt;br&gt;
&lt;code&gt;ArrayPool&amp;lt;byte&amp;gt;.Shared&lt;/code&gt;. That refactor was reviewed and merged specifically&lt;br&gt;
&lt;em&gt;because&lt;/em&gt; it removed custom code in favor of a well-known, heavily optimized&lt;br&gt;
framework type. If anything, it should have been a free win.&lt;/p&gt;

&lt;p&gt;This is the kind of regression static analysis is structurally blind to: no&lt;br&gt;
analyzer rule says "using &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;.Shared&lt;/code&gt; across threads is suspicious."&lt;br&gt;
It's &lt;em&gt;the recommended way&lt;/em&gt; to pool buffers in .NET. The problem isn't in the&lt;br&gt;
code you'd read in a diff — it's in a runtime behavior that only shows up&lt;br&gt;
under a specific, easy-to-miss usage pattern.&lt;/p&gt;
&lt;h2&gt;
  
  
  The investigation, as it actually happened
&lt;/h2&gt;

&lt;p&gt;This is the real value proposition of &lt;a href="https://github.com/pedrosakuma/dotnet-diagnostics" rel="noopener noreferrer"&gt;&lt;code&gt;dotnet-diagnostics-mcp&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
none of what follows required attaching a debugger, adding instrumentation, or&lt;br&gt;
redeploying anything. It's a live conversation with an LLM that has MCP tools&lt;br&gt;
wired to the running process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — orient.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user&amp;gt; Can you check what's going on with pid 41213? CPU looks high.

assistant&amp;gt; Let me look at the process first.
→ inspect_process(view="triage", processId=41213)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"processId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41213&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resolvedProcess"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"market-data-gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"net10.0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CPU 78% (host has 16 cores). No memory growth trend. No recent
               GC pause anomalies. Consider a CPU sample to see where time goes."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"collect_sample(kind=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;cpu&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, processId=41213, durationSeconds=10)"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing screaming "leak," nothing screaming "GC." CPU is just... busy. Time to&lt;br&gt;
see where.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — sample.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant&amp;gt; Taking a 10-second CPU sample.
→ collect_sample(kind="cpu", processId=41213, durationSeconds=10, topN=20)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"processId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41213&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalSamples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18545&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hottest self-time method: System.Threading.Monitor.Enter_Slowpath
              (2933 exclusive, 15.8% of samples). Rank self-time with
              query_snapshot(view=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;top-methods&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;) or walk the call path with
              view=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;call-tree&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"handle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A0CKKWH5VVS9Q68AH3GG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"query_snapshot(handle=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;A0CKKWH5VVS9Q68AH3GG&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, view=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;caller-callee&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, rootMethodFilter=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Monitor.Enter_Slowpath&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the first real surprise. &lt;strong&gt;15.8% of all CPU samples land inside&lt;br&gt;
&lt;code&gt;Monitor.Enter_Slowpath&lt;/code&gt;.&lt;/strong&gt; Nobody wrote a &lt;code&gt;lock&lt;/code&gt; statement in this service.&lt;br&gt;
&lt;code&gt;grep -r "lock ("&lt;/code&gt; across the whole repo returns nothing relevant. So where is&lt;br&gt;
a contended monitor coming from?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — trace the lock back to its call site.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant&amp;gt; There's no explicit lock in this codebase. Let's see who's calling
into the monitor slow path.
→ query_snapshot(handle="A0CKKWH5VVS9Q68AH3GG", view="caller-callee",
                  rootMethodFilter="Monitor.Enter_Slowpath")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"System.Threading.Monitor.Enter_Slowpath(System.Object)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclusiveSamples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2933&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclusivePercent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MarketData.BufferPool.Return(byte[])"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1478&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"percent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.97&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MarketData.Broadcaster+&amp;lt;&amp;gt;c__DisplayClass.&amp;lt;ConsumeLoop&amp;gt;b__0()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"samples"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1455&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"percent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.85&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both callers are &lt;em&gt;our&lt;/em&gt; code — a thin wrapper method calling &lt;code&gt;.Rent()&lt;/code&gt; on one&lt;br&gt;
side, and a per-connection consume loop calling &lt;code&gt;.Return()&lt;/code&gt; on the other. &lt;br&gt;
Not a &lt;code&gt;ThreadPool&lt;/code&gt; internal, not GC. The lock is being taken&lt;br&gt;
&lt;strong&gt;inside &lt;code&gt;ArrayPool&amp;lt;byte&amp;gt;.Shared&lt;/code&gt; itself&lt;/strong&gt;, and it's the &lt;em&gt;pool&lt;/em&gt;, not any&lt;br&gt;
business logic, that's contended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — read the actual lock, not a guess about it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ArrayPool&amp;lt;byte&amp;gt;.Shared&lt;/code&gt;'s internals are in &lt;code&gt;System.Private.CoreLib&lt;/code&gt; — no&lt;br&gt;
source, no PDB for the private implementation type. This is exactly what the&lt;br&gt;
companion server, &lt;a href="https://github.com/pedrosakuma/dotnet-assembly-mcp" rel="noopener noreferrer"&gt;&lt;code&gt;dotnet-assembly-mcp&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
is for: static navigation and on-demand decompilation of compiled assemblies,&lt;br&gt;
including the BCL itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant&amp;gt; Let's not guess. I'll look up the actual pool implementation
in the runtime binary you're running.
→ find_method(mvidOrPath="System.Private.CoreLib.dll", namePattern="^Return$")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5 match(es) for /^Return$/."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"typeFullName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"System.Buffers.SharedArrayPool`1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"methodName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Return"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"void System.Buffers.SharedArrayPool`1.Return(!0[], bool)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"handle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"m:e8c78f6b-...:0x060038A7"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant&amp;gt; Decompiling the real Return method for this exact runtime build.
→ decompile_method(moduleVersionId="e8c78f6b-...", metadataToken="0x060038A7")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;clearArray&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&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="n"&gt;SharedArrayPoolThreadLocalArray&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;array2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t_tlsBuckets&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;InitializeTlsBucketsAndTrimming&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// ... same-thread fast-path slot swap ...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array3&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;flag2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;CreatePerCorePartitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;TryPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array3&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant&amp;gt; And the fallback path that pushes to a per-core partition:
→ find_method(mvidOrPath="System.Private.CoreLib.dll", namePattern="^TryPush$")
→ decompile_method(moduleVersionId="e8c78f6b-...", metadataToken="0x060038B3")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;MethodImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MethodImplOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AggressiveInlining&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;TryPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Array&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enter&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="c1"&gt;// ... push onto a small fixed-size array-backed stack ...&lt;/span&gt;
    &lt;span class="n"&gt;Monitor&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="k"&gt;this&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="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;There it is — &lt;strong&gt;&lt;code&gt;Monitor.Enter(this)&lt;/code&gt;&lt;/strong&gt;, in the flesh, in the exact binary&lt;br&gt;
this process is running: the actual internal type backing&lt;br&gt;
&lt;code&gt;ArrayPool&amp;lt;byte&amp;gt;.Shared&lt;/code&gt; in this runtime build is &lt;code&gt;SharedArrayPool&amp;lt;T&amp;gt;&lt;/code&gt;, and&lt;br&gt;
its per-core fallback is guarded by a plain CLR monitor. This is why the&lt;br&gt;
call-tree pointed straight at &lt;code&gt;Monitor.Enter_Slowpath&lt;/code&gt;: it's a real, ordinary&lt;br&gt;
monitor, contended by every thread whose &lt;code&gt;Return&lt;/code&gt; misses the per-thread&lt;br&gt;
cache.&lt;/p&gt;

&lt;p&gt;To be clear: &lt;code&gt;dotnet/runtime&lt;/code&gt;'s source is public on GitHub — nothing here was&lt;br&gt;
hidden. The point isn't that the source was unreachable, it's the &lt;em&gt;ergonomics&lt;/em&gt;&lt;br&gt;
of the query. Getting the same answer the "normal" way means: know (or guess)&lt;br&gt;
which internal type backs &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;.Shared&lt;/code&gt; today, find the right tag in&lt;br&gt;
&lt;code&gt;dotnet/runtime&lt;/code&gt; that matches the exact SDK/runtime patch this process is&lt;br&gt;
actually running, navigate to the right file, and read enough surrounding&lt;br&gt;
code to trust you're looking at the live behavior and not a since-changed&lt;br&gt;
version. &lt;code&gt;decompile_method(moduleVersionId, metadataToken)&lt;/code&gt; collapses all of&lt;br&gt;
that into one call, scoped to the exact assembly loaded in the exact process&lt;br&gt;
under investigation — no version-matching, no guessing a type name, no&lt;br&gt;
context-switch out of the chat.&lt;/p&gt;

&lt;p&gt;Checking the &lt;em&gt;application&lt;/em&gt; code (only &lt;em&gt;now&lt;/em&gt;, after both tools already&lt;br&gt;
pointed at the exact mechanism, not before):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// One dedicated broadcaster thread — the ONLY thread that ever Rents.&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArrayPool&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FrameSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;FillFromMarketFeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// N per-connection consumer tasks — each may Return a buffer it never Rented.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ArrayPool&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- always a different thread than Rent&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exactly the pattern the tool output implied: &lt;strong&gt;one thread rents, many other&lt;br&gt;
threads return.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this is invisible without live evidence
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ArrayPool&amp;lt;T&amp;gt;.Shared&lt;/code&gt; isn't a naive shared bag — its current .NET 10&lt;br&gt;
implementation, &lt;code&gt;SharedArrayPool&amp;lt;T&amp;gt;&lt;/code&gt;, keeps a &lt;strong&gt;single-slot cache stored&lt;br&gt;
per-thread&lt;/strong&gt; (&lt;code&gt;[ThreadStatic]&lt;/code&gt; buckets): if the &lt;em&gt;same&lt;/em&gt; thread that rented a&lt;br&gt;
buffer also returns it, the operation is a plain field swap, no&lt;br&gt;
synchronization at all. The moment a &lt;em&gt;different&lt;/em&gt; thread returns it, that fast&lt;br&gt;
path can't apply (the slot belongs to another thread), so the buffer falls&lt;br&gt;
through to a small set of &lt;strong&gt;per-core partitions, each guarded by a plain&lt;br&gt;
&lt;code&gt;Monitor&lt;/code&gt;&lt;/strong&gt;. With a handful of cores and dozens of consumer threads all&lt;br&gt;
returning concurrently, those few partitions become a genuine contention&lt;br&gt;
point.&lt;/p&gt;

&lt;p&gt;None of this is a bug in &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;&lt;/code&gt;. It's working exactly as designed —&lt;br&gt;
optimized for the common case (rent-use-return on one thread, e.g. inside a&lt;br&gt;
single request handler), and gracefully degrading, but &lt;em&gt;not silently free&lt;/em&gt;,&lt;br&gt;
for the cross-thread fan-out case. There is no compiler warning, no analyzer&lt;br&gt;
rule, and no code-review heuristic for "you are about to violate an&lt;br&gt;
implicit performance assumption of a BCL type you didn't write." The only way&lt;br&gt;
to know, for certain, what &lt;em&gt;this&lt;/em&gt; process, running &lt;em&gt;this&lt;/em&gt; runtime build, is&lt;br&gt;
actually doing is to watch where the CPU goes and read the exact binary it's&lt;br&gt;
running.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix and verification
&lt;/h2&gt;

&lt;p&gt;The fix isn't "add a lock" (there's nothing to protect) — it's swap the pool&lt;br&gt;
implementation to one designed for the actual ownership pattern: many threads&lt;br&gt;
returning items nobody-in-particular rented, i.e., a plain lock-free queue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CrossThreadBufferPool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentQueue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_pool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;Rent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryDequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&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;&lt;strong&gt;Isolated cost, before/after&lt;/strong&gt; (BenchmarkDotNet, 20,000 cross-thread&lt;br&gt;
&lt;code&gt;Return&lt;/code&gt; calls, buffers pre-rented on a background thread outside the timed&lt;br&gt;
region so only &lt;code&gt;Return&lt;/code&gt; itself is measured):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Mean time (20,000 calls)&lt;/th&gt;
&lt;th&gt;vs. contended baseline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ArrayPool.Shared.Return&lt;/code&gt;, cross-thread (the bug)&lt;/td&gt;
&lt;td&gt;~4.3–5.1 ms&lt;/td&gt;
&lt;td&gt;1x (baseline)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ConcurrentQueue&amp;lt;byte[]&amp;gt;&lt;/code&gt; &lt;code&gt;Return&lt;/code&gt;, cross-thread (the fix)&lt;/td&gt;
&lt;td&gt;~126–146 µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~34x faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ArrayPool.Shared&lt;/code&gt; full Rent+Return, &lt;strong&gt;same thread&lt;/strong&gt; — theoretical best case&lt;/td&gt;
&lt;td&gt;~115–150 µs&lt;/td&gt;
&lt;td&gt;~34–40x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the ceiling: the one scenario with genuinely zero&lt;br&gt;
synchronization, because the per-thread cache slot is always hit. Comparing&lt;br&gt;
it to the &lt;code&gt;ConcurrentQueue&lt;/code&gt; fix shows exactly how much overhead our own fix&lt;br&gt;
still carries: &lt;strong&gt;~126 µs vs. ~115 µs — roughly 10% above the theoretical&lt;br&gt;
best&lt;/strong&gt;, for a data structure built to be used safely by an arbitrary number&lt;br&gt;
of unrelated threads. That's the honest cost of "general-purpose&lt;br&gt;
thread-safe," and it's a rounding error next to the 34x we recovered.&lt;/p&gt;

&lt;p&gt;A single cross-thread &lt;code&gt;Return&lt;/code&gt; call on the &lt;em&gt;unfixed&lt;/em&gt; pool costs more than an&lt;br&gt;
entire same-thread Rent+Return round trip. That's how disproportionate the&lt;br&gt;
fast-path miss is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live re-check after the fix&lt;/strong&gt; — same 2-thread (1 producer / 1 consumer)&lt;br&gt;
topology, same CPU sampler, before touching anything else:&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;
&lt;code&gt;ArrayPool.Shared&lt;/code&gt; (cross-thread)&lt;/th&gt;
&lt;th&gt;Lock-free pool (cross-thread)&lt;/th&gt;
&lt;th&gt;Same-thread control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;~3.2M ops/s&lt;/td&gt;
&lt;td&gt;~6.6M ops/s&lt;/td&gt;
&lt;td&gt;~20M ops/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Monitor.Enter_Slowpath&lt;/code&gt; in CPU sample&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;15.8%&lt;/strong&gt; exclusive&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0%&lt;/strong&gt; — gone entirely&lt;/td&gt;
&lt;td&gt;n/a (no shared pool contention path)&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;assistant&amp;gt; With the lock-free pool under the same load:
→ collect_sample(kind="cpu", processId=41213, durationSeconds=8, topN=20)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hottest self-time method: System.Threading.Thread.Sleep(int32)
              (3972 exclusive, 40.6% of samples). No Monitor frames in the
              top 20 hotspots."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No &lt;code&gt;Monitor.Enter_Slowpath&lt;/code&gt; anywhere in the profile. The contention didn't&lt;br&gt;
move — it disappeared, because the pool that produced it is gone.&lt;/p&gt;
&lt;h2&gt;
  
  
  The obvious follow-up question: is pooling even worth it here?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ArrayPool&amp;lt;T&amp;gt;.Shared&lt;/code&gt; itself exists as an optimization over the naive&lt;br&gt;
baseline — just calling &lt;code&gt;new byte[size]&lt;/code&gt; and letting the GC reclaim it. A&lt;br&gt;
fair objection to everything above: if the &lt;em&gt;shared&lt;/em&gt; pool is this contended&lt;br&gt;
under cross-thread &lt;code&gt;Return&lt;/code&gt;, maybe the honest fix is to drop pooling&lt;br&gt;
entirely, not swap in a different pool. Mean latency might even look&lt;br&gt;
competitive, since a plain allocation has zero synchronization of any kind —&lt;br&gt;
but the expectation is that Gen0/Gen1 GC pressure would show up in the tail&lt;br&gt;
(P99), not the mean.&lt;/p&gt;

&lt;p&gt;We tested it — same 2-thread producer/consumer topology, only the pool&lt;br&gt;
implementation swapped for one that allocates fresh on every &lt;code&gt;Rent&lt;/code&gt; and does&lt;br&gt;
nothing on &lt;code&gt;Return&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;Monitor&lt;/code&gt; contention&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ArrayPool.Shared&lt;/code&gt;, cross-thread&lt;/td&gt;
&lt;td&gt;~3.2M ops/s&lt;/td&gt;
&lt;td&gt;15.8% (Monitor)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock-free queue pool, cross-thread&lt;/td&gt;
&lt;td&gt;~6.6M ops/s&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;No pooling&lt;/strong&gt; (&lt;code&gt;new byte[size]&lt;/code&gt; every call)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~180K ops/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No pooling isn't a tail-latency-only regression — it's &lt;strong&gt;~18x slower on raw&lt;br&gt;
throughput&lt;/strong&gt;, worse than the contended &lt;code&gt;ArrayPool.Shared&lt;/code&gt; baseline we started&lt;br&gt;
from. The reason shows up immediately in a live GC pause capture over the&lt;br&gt;
same window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant&amp;gt; Let's check what removing the pool entirely does to GC.
→ collect_events(kind="gc", processId=41213, durationSeconds=10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"134 collection(s), max pause 69.9ms, total pause 5118.1ms."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant&amp;gt; Breaking that down:
→ query_snapshot(handle="...", view="pauseHistogram")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalCollections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPauseTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00:00:00.0698743"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"buckets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;1ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1-10ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10-100ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100-1000ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=1s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant&amp;gt; And by generation:
→ query_snapshot(handle="...", view="byGeneration")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gen1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"totalPause"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00:00:05.1180759"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"meanPause"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00:00:00.0381945"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"maxPause"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00:00:00.0698743"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;134 gen1 collections in a 10-second window — 13.4 per second, every single&lt;br&gt;
one landing in the 10–100ms bucket — for 5.12 seconds of accumulated pause&lt;br&gt;
time inside those 10 seconds.&lt;/strong&gt; Over half the wall clock is GC pause, not&lt;br&gt;
application throughput. That's not a tail-latency footnote; it dominates the&lt;br&gt;
mean too, once allocation volume is high enough to keep pushing 4KB buffers&lt;br&gt;
into gen0 fast enough to promote and collect on a ~130ms cadence.&lt;/p&gt;

&lt;p&gt;So the honest ranking, worst to best, for this specific cross-thread&lt;br&gt;
fan-out pattern is: &lt;strong&gt;no pooling (worst, by both mean and tail) → shared pool&lt;br&gt;
with cross-thread &lt;code&gt;Return&lt;/code&gt; (contended but bounded) → a pool built for the&lt;br&gt;
actual ownership shape (best on every axis measured)&lt;/strong&gt;. &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;.Shared&lt;/code&gt;&lt;br&gt;
being contended here doesn't mean pooling was the wrong call — it means the&lt;br&gt;
&lt;em&gt;specific&lt;/em&gt; pool's ownership assumption (same-thread Rent/Return) didn't match&lt;br&gt;
this workload's shape. The fix was never "go back to allocating"; it was&lt;br&gt;
"pool it the way this workload actually uses it."&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Static analysis, linters, and code review are excellent at catching &lt;em&gt;shapes&lt;/em&gt;&lt;br&gt;
of bugs — a missing &lt;code&gt;await&lt;/code&gt;, a &lt;code&gt;lock&lt;/code&gt; around the wrong object, an &lt;code&gt;O(n²)&lt;/code&gt;&lt;br&gt;
loop. They cannot catch a case where the code is a textbook-correct call into&lt;br&gt;
a well-designed BCL type, and the only thing wrong is a runtime ownership&lt;br&gt;
pattern that BCL type happens to be sensitive to. You either know this&lt;br&gt;
specific gotcha already, or you find it by watching the process — there is no&lt;br&gt;
third option.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet-diagnostics-mcp&lt;/code&gt; exists so that "watching the process" is a normal&lt;br&gt;
step in an LLM-driven investigation, not a specialized skill gated behind&lt;br&gt;
whoever on the team happens to know &lt;code&gt;dotnet-trace&lt;/code&gt; by heart. Every dynamic&lt;br&gt;
step above — &lt;code&gt;inspect_process&lt;/code&gt;, &lt;code&gt;collect_sample&lt;/code&gt;, &lt;code&gt;query_snapshot&lt;/code&gt; — is a&lt;br&gt;
single MCP tool call an assistant can make on its own, against a real running&lt;br&gt;
.NET process, with no code changes and no redeploy. And when the trail leads&lt;br&gt;
into a BCL type nobody on the team has source for, its companion server,&lt;br&gt;
&lt;a href="https://github.com/pedrosakuma/dotnet-assembly-mcp" rel="noopener noreferrer"&gt;&lt;code&gt;dotnet-assembly-mcp&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
picks up exactly where live tracing stops: real decompiled source of the&lt;br&gt;
exact binary the process is running, not a recollection of how some type&lt;br&gt;
&lt;em&gt;used to&lt;/em&gt; work.&lt;/p&gt;

&lt;p&gt;The reproduction and all numbers in this post are in a small, standalone,&lt;br&gt;
open repro you can run yourself — no proprietary code involved. The&lt;br&gt;
investigation pattern itself (fix unrelated to this repro) is inspired by a&lt;br&gt;
real production change in &lt;a href="https://github.com/pedrosakuma/B3MarketDataPlatform" rel="noopener noreferrer"&gt;&lt;code&gt;B3MarketDataPlatform&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
a public repo, commit &lt;a href="https://github.com/pedrosakuma/B3MarketDataPlatform/commit/ac2dc9f" rel="noopener noreferrer"&gt;&lt;code&gt;ac2dc9f&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;code&gt;dotnet-diagnostics-mcp&lt;/code&gt; is open source: &lt;a href="https://github.com/pedrosakuma/dotnet-diagnostics" rel="noopener noreferrer"&gt;github.com/pedrosakuma/dotnet-diagnostics&lt;/a&gt;.&lt;br&gt;
It ships both an MCP server (for your LLM client of choice) and a standalone&lt;br&gt;
CLI (&lt;code&gt;dotnet-diagnostics-cli&lt;/code&gt;) for scripting the same investigations by hand.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>performance</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
