Thread-safe Code Through TDD
Today I am going to write about something I came across recently: making some already existing code thread-safe.
What does thread-safe mean? 🤓
Looking on Google we can find many similar definitions:
Thread-safety or thread-safe code in Java refers to code which can safely be used or shared in concurrent or multi-threading environment and they will behave as expected. any code, class, or object which can behave differently from its contract on the concurrent environment is not thread-safe.
So, to put it simply, a piece of code can be defined as thread-safe if is warranted that executing it concurrently or in multiple threads is going to behave as expected.
The project 🔑
I was working on a project recently where I came across this simple code in a repository class:
Analyzing the code, we can see that there is a refreshToken
function that:
- Requests a new token to the backend passing the old one
- Stores the received information into some sort of storage class (in Android the implementation of this class could be the
sharedPreferences
or really anything suitable for this job, it is irrelevant for what we want to understand here)
The API states that to refresh the token correctly we need to invoke it passing the latest token we had received from that same API.
There were tests as well for this that were verifying the classes were called as expected (there were tests for failing scenarios too, I am skipping them here as they are not relevant for the topic in discussion):
The problem 💥
As the project grew, this code started being called from multiple places, potentially at the same time, or indeed, concurrently.
This lead to situations where the refreshToken
function was called twice roughly at the same time, the first was received by the backend first (as you would expect) and the second as the second but the response of the first, especially in bad/slow connectivity situations, was received after the second one was invoked, so the second invocation of the refreshToken
function was still using the old token, generating an error response from the backend.
The solution 💡
After some investigation, I discovered that the refreshToken
function was not thread-safe, so the solution that was pretty obvious: we need to make this thread-safe. This will warranty the order of execution, meaning the function would never be called before a previous one completed.
The TDD approach 🔴 ✅
Making the refreshToken
function thread-safe is quite easy in Kotlin: we can just add the @Syncrhonized
annotation and we are done 🙌
But replicating the scenario were multiple calls were done with an unstable connection is pretty hard, we were actually able to reproduce this bug roughly once every 20 attempts, and those attempts were quite time-consuming.
So I started looking for how to write a test where I could invoke refreshToken
concurrently and have a failing test 🔴
In that way I could have solid and fast feedback to work towards, and once I had it green ✅ I could be sure my solution was working.
Looking at coroutines, I found I could launch concurrent code by doing something like:
val coroutinesDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher()val concurrentCalls = listOf(
async(
context = coroutinesDispatcher,
start = CoroutineStart.LAZY
) { // Code we want to execute concurrently },
async(
context = coroutinesDispatcher,
start = CoroutineStart.LAZY
) { // Code we want to execute concurrently },
etc)
concurrentCalls.awaitAll()
So I came up with the following test:
The setUpConcurrentCalls()
function is just what I showed in the previous example, but with the code I needed to execute concurrently:
If we run this test… it fails! 🔴
In the inOrder
block I verify how the different steps in the refreshToken
should be called, and since the code is not thread-safe (yet), when executing this test storing the token (storeToken(secondReceivedToken)
) of the second invocation concurrent invocation of refreshToken
happens before the first one storeToken(firstReceivedToken)
.
Now we are sure we replicated the bug, and we can start working on the solution.
Implementing the solution 🚀
As I mentioned earlier, we can easily add thread-safety by adding
@Synchronized
and indeed, adding that to the refreshToken
function makes the tests pass! ✅
So the new implementation will be:
As simple as that!
Alternatives solutions 📚
Synchronized block
@Synchronized
is not the only way to go.
Kotlin has a synchronized
block as well, in that case the code will become:
This is particularly useful when we need only a certain portion of the function to be thread-safe. In our specific case we didn’t really need it, and I find the @Synchronized
more elegant and less intrusive so I went with that.
Others
Other data structures that help in implementing thread-safe code, like Locks, Atomic Primitives, Semaphores, etc. We are not covering those in this article, and you can find more info online 🤓
Example project
The small project with the final result of this exercise can be found at https://github.com/Alexs784/thread-safe-tdd-example