Get hands-on training for JIRA Software, Confluence, and more at Atlassian Summit Europe. Register now ›

Or, an exploration of Javascript numeric types.

The Problem

Agnes called me over because she’d spent the last two hours banging her head against CONF-10035. Apparently, when editing a particular page on the Atlassian extranet, going to preview mode caused all the images and links on the page to be broken links. When the page was saved, though, everything worked fine.

Diagnosis

One of the advantages of being the Architect is that by the time you’re called in to help with a problem, someone else has already done the important but boring parts of trouble-shooting. Agnes had already copied the page and all its attachments to her test environment, and was unable to reproduce the bug locally.

So I started thinking to myself. “It’s got to be something weird, or Agnes would have worked it out already. What’s different between the test environment and the extranet?”

A Digression

Confluence’s database objects are all identified by a 64 bit primary key. This was a piece of deliberate overkill that comes down to never, ever wanting to have one of those “we thought 640k was enough!” moments as we explained to some fifteen-year Confluence customer that they just couldn’t fit any more content into their server.

Sane Confluence servers are unlikely to ever make use of all that key-space, but it’s good to have a ludicrously large buffer to play with. On the other hand, when the Great Bit Shortage of 2020 hits, Confluence will have to put up its hand as one of the culprits.

The Atlassian extranet has been running since the earliest days of Confluence development. It has been afflicted with every piece of dodgy alpha code we’ve felt like deploying. Extranet has never been a sane Confluence server. Some time in the depths of history, some bug crept in that bumped the database keys up well above the 32 bit limit, and there they have stayed ever since.

Back to the Problem

This bit of extranet quirkiness has turned out to be quite useful, as it has uncovered a number of bugs over time caused by people forgetting that a contentId won’t fit into an int, especially in places where Java’s static typing doesn’t rescue you from mistakes. Places like the remote API, or Javascript.

Javascript has one numeric type, and according to the standard, it’s represented internally as a 64-bit IEEE-754 floating point number, or what Java would call a double. As anyone who has worked with floating-point arithmetic on computers will know, such numbers are only approximate. According to the comp.lang.javascript FAQ, integers in Javascript start to lose accuracy when they go above 9x1015.

So practically, this means that at some point, the database IDs assigned by Confluence can get high enough that they can no longer be represented accurately as a number in a Javascript. So, as was the case in CONF-10035, the page had Content ID ‘69518087748071312’, but when you clicked on “preview” it was being sent back to the server as ‘69518087748071310’.

(To add to the confusion, Opera handles this number fine, as does Java when you convert between the long and double types)

But That’s Not All

Normally, this wouldn’t be so much of a problem. We found this out ages ago, and have since made sure that all our Javascript APIs pass content IDs around as strings. The complication, though, is that Javascript is weakly typed1. Which means that if you accidentally type…

var contentId = #if ($pageId.toString() == '0') $draft.id #else $pageId #end;

…instead of…

var contentId = #if ($pageId.toString() == '0') "$draft.id" #else "$pageId" #end;

…Javascript will happily treat the variable as a number or as a string, depending on how you decide to use it. And neither of them will be the number you thought of in the first place.

Epilogue

I think my favourite kind of problems are the ones that take hours to find, and one line of code to fix. There’s something very satisfying about it.

A man’s car has been making a very scary grinding-metal noise, so he takes it to the garage. The mechanic walks over, pops the hood, revs the engine a few times, and scratches his chin. Then he walks over to his work-bench and picks up a small rubber mallet. He gives something in the engine a hard whack, and suddenly the grinding noise has gone completely.

The mechanic walks over to the driver. “That’ll be a hundred and twenty bucks, thanks.”

“You’ve got to be kidding! All you did was hit it with a hammer! I’m not paying.”

The mechanic walks back to his bench, grabs a pad and pen, and writes the driver an invoice:

Hitting the engine with a hammer: $0.05
Knowing where to hit it: $119.95

1 Dive into Python has a good summary of the difference between Strong/Weak typing and Static/Dynamic typing, although I’d phrase it differently.

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now