Slowing down your code with Coroutines
I was talking about slowing down code and different ways of achieving it and somebody asked me, “why would you want to slow down your code?”. I jokingly replied that my server is running too fast … but in all seriousness, there are legitimate reasons to slow down code.
Let’s have a look at this login endpoint in Chrome Dev Tools where I’m trying to fill in random email addresses with random passwords …
What we see here is that some responses take ~250ms and other responses take ~130ms.
Based on the response times, an attacker can deduce if somebody is signed up for your service or not - a quick response means only a database lookup was done and nothing was found whereas the slower response indicates that the account does exist and extra steps were taken to validate the password.
This type of attack is called a timing attack - A timing attack is a security exploit that allows an attacker to discover vulnerabilities by studying how long it takes the system to respond to different inputs.
Now let’s slow down the login response so it always takes 2 seconds (for a good user-experience, maybe 2 seconds is a bit much, but bear with me here).
I’ve used the exact same inputs as in the previous screenshot, but as you can see, they’re now all saying exactly two seconds making it impossible to distinguish a valid email from an invalid email just by looking at the timings.
Instinctively, my first thought on how to create such a slowdown function was to measure how long the execution took and then delaying the response by the remainder:
Writing a test case for this, unfortunately, gets tricky since runTest doesn’t play nice with measureTimeMillis. Initially, I had to use runBlocking which meant each test literally took two seconds to run. If you have lots of these endpoints that slow down responses, you will quickly have a mob of grumpy developers with legitimate reasons to slack off:
You’ll also notice that I had to play around with the third parameter in assertEquals
which is the tolerance parameter - in other words, if this runs on a slower build server, that tolerance might be too small and the test will randomly fail - not great!
There are several options to solve the slow tests, but all require using runTest and changing the slowdown function so it measures time differently when inside a test.
Option1: we can use context receivers to inject a TimeSource. In normal code, we have to run everything in a TimeSource.Monotonic scope while creating a custom TimeSource for tests. This is about as messy and boilerplate-heavy as it sounds! I’ll leave this as an exercise for the reader to not clutter the article.
Option2: we launch a delay in parallel with the body. Although this seems elegant, it is a bit heavy.
Option3: we check what type of dispatcher we’re dealing with and if it’s a TestDispatcher
, we measure time differently. This seems like the most optimal solution. (One thing to note, we have to include kotlinx-coroutines-test
as a non-test dependency to get access to TestDispatcher
)
For the test cases, we now get to switch to runTest
instead of runBlocking
which skips delays:
Also we get to check for exactly 2.seconds instead of having to play around with the tolerances which makes the test much more reliable.
Since we’re skipping delays thanks to runTest, we can also expect the tests to finish in milliseconds instead of waiting the full four seconds!
Something as simple as wanting to slow down code turned into a whole article, who would have thought!