I recently tracked down a bug in a TCP handler system where messages from another microservice, sent through a storage queue, weren't reaching the client.
At first, I thought it was a race condition in the TCP handler. Later, I found the real issue: a stale variable in another handler had persisted after a replacement. I was looking at the wrong code path initially, which made the debugging process more interesting.
I identified the root cause through log correlation and code analysis alone, without using a debugger, and then implemented a production-safe fix by replacing the fixed handler with a handler map.
This wasn't the first time I had to debug this microservice. I'd already dealt with:
- A race condition during Accept, where multiple terminals connecting at the same time caused issues, fixed by adding a lock after AcceptTcpClientAsync.
- A memory leak, where handlers kept running in memory because they weren't canceled, fixed with an abort method and linked cancellation tokens using CreateLinkedTokenSource, as I mentioned in my previous "Debugging Skills Part 2" post.
What good debugging really means is not just knowing the tools or the code. It also means staying motivated long enough to go deeper, step back when needed, and challenge your own assumptions.
Sometimes the best thing to do is:
- Leave it and let it work in your brain (walk away for a few minutes).
- Grab a coffee or a quick break.
- Discuss the problem with a peer or even an AI.
That discipline is often what leads you to the real root cause.
The handler map fix solved the issue, but the bigger win was staying focused and persistent enough to trace the problem all the way through.