Archaeology of a Bug: What 'Removed Arc<Mutex> Connection' Means
We have all written a commit message that sounds technical and precise but is a confession.
Mine was this: Removed use of arcmutex connection and recreating connection each time to use pooled connection. Thirty-odd words that manage to be accurate and almost entirely dishonest about what actually happened.
This is what that commit message doesn't say.
It doesn't say that I spent several days convinced the problem was somewhere else entirely. It doesn't mention the part where I was fairly confident I understood how the connection was being shared across threads, right up until the moment I didn't. It certainly doesn't capture the silence when you finally read the code and realise the Arc<Mutex> wrapper wasn't protecting shared state — it was just making the problem harder to find.
What it says instead is: here is a technical description of a change I made. Which is true. And also a bit like describing a house fire as "Changed the kitchen layout."
The bug itself was straightforward, which is the annoying kind. I'd wrapped a database connection in Arc<Mutex<Connection>> — sharing it across tasks — under the impression that this was the sensible, concurrent thing to do. You've got multiple async tasks, you've got one connection, you wrap it so they can take turns. That's what the Mutex is for.
The problem is that a database connection isn't really something you want to share that way. Connection pools exist precisely because creating a new connection is expensive but holding one open across every concurrent task is worse. What I'd built was something that looked like responsible resource management but was actually a bottleneck dressed up in Rust's type system. The Arc<Mutex> wasn't solving a concurrency problem. It was a concurrency problem wearing a solution's hat.
The moment I actually understood this, I could see it everywhere. Every place the lock was held slightly too long. Every task queuing up waiting for its turn with a single connection that didn't need to be shared in the first place.
But that is not the worst part. The worst part is that the code was never sharing a connection anway. Every time it did a call to get a DB connection to created a new one. The Arc<Mutex> was just a red herring. It was a distraction that made the real problem harder to find, because I was looking for a problem in the wrong place. I had a model of how the code worked that was wrong, and it took me ages to see that.
What I keep thinking about is how long I held the wrong mental model.
There's a particular kind of confidence that comes from having written something that compiles and passes tests and appears to work. You start to treat that confidence as understanding. The code runs, so my assumptions are right. AI and Rust makes this worse, because AI is there to please you and the type checker is demanding about correctness that when something compiles you feel itmust be correct. Like the compiler would tell you if you were wrong.
It won't, though. It'll tell you if your types are wrong. Your understanding is still entirely your problem.
The fix, once I saw it, took about an hour. Pull in a proper connection pool, remove the Arc<Mutex>, let the pool handle what pools are designed to handle. The commit message I wrote described the what with accuracy. But the why — the actual story, which is that I'd had misunderstood resource sharing and spent days not seeing it — that's not in there anywhere.
I've been thinking about this since I wrote the post about terrible commit messages — the 'no idea of the changes' one. That post was about honesty, sort of. About the gap between how we say we work and how we actually work. But there's a different kind of dishonesty in the commits that sound precise. The ones that deploy technical language to describe a change while quietly omitting the embarrassing part. Removed use of arcmutex connection is a much more respectable-looking message than finally understood what a connection pool is actually for.
I don't think this is vanity exactly. It's more that commit messages have a specific audience — future you, or colleagues, or anyone who runs git blame in six months — and we write for that audience in a way that papers over the stupidity. Which maybe makes sense. The history doesn't need to document every wrong turn.
But I do wonder sometimes what we'd learn if we wrote the second message instead. Not for the repo. Just for ourselves.
The bug is fixed. The connection pool works. The code is, genuinely, better. None of that is in question.
I just think there's something worth sitting with in the gap between what a commit message says happened and what actually happened. Most of the real archaeology isn't in the diff. It's in everything you quietly understood better on the way to writing it.
