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.
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:
- Stop execution of any code
- Display the ‘Aw snap!’ error page (on chrome, different browser have different pages)
Since this is not what happened to us, we discarded the memory leak possibility, and went on to investigate the computations.
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:
- It’s written in python, which while it’s not our main language, is better known that ruby.
- It was way faster to implement than in ruby, and with less coding.
- It’s easier to dockerize, and we needed to do it.
manage.py inspectdb, a great feature of Django
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.