A fun bug we found in our MMO

debugging Estimated reading time: 3 minutes

Our server went down for no apparent reason. Turns out that it ran out of memory. Luckily we had a core dump and thus a useful stack trace with relevant parameters intact.

This showed that it tried to allocate 500MB of message buffer space in one call. But why? The short answer is that it interpreted it as a safe message type because it arrived on a socket that isn't accessible from the outside world, and the length field of the struct was a tad larger than any length field should be. :)

But how did that message even get there on that socket when it should have only been safe messages? The incoming message had entirely the wrong format, resembling an HTTP get request. We make some HTTP requests from the other server, but to external sites, not to the other internal server. How could it get mixed up?

The first thought was that perhaps the pooled TCP socket class had a bug, where data for one of the batch of sockets gets sent to another accidentally. A quick inspection of the code revealed that we weren't using the pooled sockets for this application, though. What was left to go wrong? Just the socket creation and the remote hostname to address lookup function.

Looking at the latter, we spotted the bug immediately; the lookup function, several years old and predating almost everybody on the team, was filling a static data member and returning a pointer to that. This worked fine while all our networking was in one thread, but more recently extra networking threads have been added for slow, synchronous tasks, such as the HTTP request we saw here, arriving on the other server. With several threads overwriting that structure, and player concurrency rising, it was only a matter of time before a function in one thread got back the address that a function in another thread had asked for. In this case an external HTTP query asked for the IP address of an external website and got back an internal IP address and port, which wasn't too happy to receive that HTTP GET request!

Lessons learned: