The Case of the Absurdly Large Numbers
One of the engineers on my team at Etsy, who is working on getting our clickstream events into protobuf, noticed that we were getting some values that were too large for protobuf’s largest integer, uint64. E.g: 55340232221128660000.
This value was coming from our frontend performance metrics-logging code, and is supposed to represent the sum of the encodedBodySizes of all the JavaScript or CSS assets on the page. But given that 55340232221128660000 is around 50,000 petabytes, it’s probably not right. There had to be a bug somewhere.
We were only seeing this on a small percentage of visitors to the site—less than 0.1%. I looked at the web browsers logging these huge values and couldn’t see any obvious pattern.
My first thought was that we had code somewhere that was concatenating numbers as strings. In JavaScript, if you start with a string and add numbers to it, you get concatenated numbers. E.g.:
> "" + 123 + 456
< '123456'
But I didn’t see anyway that could happen in our code.
Looking at the different values we were getting, I saw a lot of similar-but-not-quite equal values. For example:
18446744073709552000
18446744073709556000
18446744073709654000
36893488147419220000
36893488147419310000
55340232221128660000
55340232221128750000
55340232221128770000
73786976294838210000
92233720368547760000
The fact that they all end in zeros was curious, but I realized that was because they were overflowing JavaScript’s max safe integer.
I started googling some of the numbers and got a hit. I thought it was very odd that one of our seemingly random numbers was showing up on a StackOverflow post. Then I googled “18446744073709551616”. Any guesses what that is?
264
36893488147419220000? Approximately 2 * 264
55340232221128660000 =~ 3 * 264
etc.
I realized what was probably happening was when we were adding up all the resources on the page, some of them were returning 264-1 (the maximum 64-bit unsigned integer), but some were normal, so that explained the variation.
Now encodedBodySize is an unsigned long long
, so my first thought was there is a browser bug that was casting a -1 into the unsigned long long, which would give us 264-1.
I started digging through browser source code. I found that in WebKit, the default value for responseBodyBytesReceived
is 264-1, although that value should be caught and 0 returned instead.
Then I went spelunking in the Chromium source, and found this commit, from less than two weeks ago. (Although the bug was reported back in May 2022.) Sure enough, it was a default value of -1 getting cast to an unsigned value. Boom! Mystery solved.
I pushed a fix to our JavaScript to catch these bogus values and hopefully our protos will live happily ever after. Gone are my dreams of finding and fixing a major browser bug, but I’m happy that I can move on with my life.