Browser Memory, Sentinel Worker, Django Admin inspectdb

Intro

Recently at work we had to debug a web application that sometimes got stuck and froze the browser page, but without access to the computer on which it was running, nor any idea on when it happened (it had only happen a few times, without any clue on what could cause it). This is a computationally heavy application, with almost no direct interaction from the user.

Spoiler alert: we never found out what caused the bug, but it also never repeated itself again.

Browser memory

We already did a few rounds of optimization on our code, moving most of the computation to a web worker and caching a lot of rendering, since keeping it in the main thread caused a huge loss of FPS, and most of the rendering part would repeat after a while.

We did not know if the error was caused by the main thread being totally blocked, or from some memory leak that could occupy all the available RAM allocated for our application.

We already knew that in the case of a blocked main thread (easy to test with an infinite while loop) the page will go unresponsive. We did not know if a memory leak would cause something similar, or something else. We decided to test it with a simple script run straight in the REPL of the debug menu of Chrome.

The script would simply create huge nested arrays of random values and add nesting levels one after the other, and as we quickly found out it would reach in no time the maximum RAM allowed to the browser, in my case 16gb, and at this point the browser will:

Since this is not what happened to us, we discarded the memory leak possibility, and went on to investigate the computations.

Sentinel Worker

We now needed to have a way to know when the browser would get frozen, but this cannot be done normally with javascript: since normal scripts run in the main thread just like rendering and IO, when the main thread would get stuck in some long calculation (or infinite loop), no additional script could run to notify us that something was wrong.

We decided to write use a worker as a sentinel: once the page was loaded, a worker was spawned, and to window.requestsAnimationFrame we attached a callback that would ping the worker.

Since the worker runs on a different thread than the main one, it would simply keep checking that the distance between pings was under a specific threshold. If no ping would arrive after some time (a second is already alarming) it would mean that the main thread was stuck somehow, and it wasn’t able to reach the point where it was supposed to render the current frame.

While not necessary, it’s also useful for us to keep track of the current FPS and of the most important values of the state of our application at each ping. This could be useful for us to understand what could have gone wrong. All this information would be sent to Sentry, our bug tracker, with a fetch request made from the worker when the FPS went too low for too much time.

We thought of some way to do more from the worker, like force a refresh of the page, or ‘unblock’ the main thread, but we never did find a way to send back the info, also because every communication between the two threads (sentinel and main thread) is handled by events that would never be handled in the main thread, since it’s still blocked.

Django admin auto generated models

On another project, we needed to have a simple interface to allow some of us (also not devs) to edit a simple database with a few tables connected to our main Node server. A really cool tool I’ve used in the past is ActiveAdmin, but I also remembered DjangoAdmin to be quite good.

We decided to use the latter for a few reasons:

What we finally did was simply to setup a base Django installation that used 2 databases: one for the Django tables, and it was a simple sqlite database. Another connection would be the one to the database we needed to inspect and edit.

Then we simply run the inspectdb task from manage.py, giving the second database name as argument, and it generated Django models using the database schema. This was almost everything we needed, we only generated DjangoAdmin models from the normal ones, and the Django Admin easily allowed us to edit any field of any table.

The only caveat we needed to solve was to apply the same encryption (bcrypt) to a password field that we used with the other server.

What is really cool of inspectdb is that allowed us to create a docker image of a ‘plug and play’ backoffice, since it does not need to know anything about the structure of the database it will be connected to, and it will simply read it from the database schema itself and autogenerate everything that Django needs to work.