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

--

--

--

Senior Android Engineer @WeTransfer 🤖 🇻🇪🇮🇹 Paraglider, Runner, Kayaker.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How to use Scaffolding in Ruby on Rails.

SQL Server For Beginners Part-3

Configure BiDirectional MySQL Replication (Master-Master)

Running your Go Application in Firecracker

Create An Interactive Selenium Automation Report With ExtentReports And C#

From Byblos to Barcelona — Meet Serge Harb, Software Engineer at Preply

How to start learning computer programming.

Continuous Integration and Delivery Using AWS and Github

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alessandro Mautone

Alessandro Mautone

Senior Android Engineer @WeTransfer 🤖 🇻🇪🇮🇹 Paraglider, Runner, Kayaker.

More from Medium

Mastering refactoring

Design Patterns in Kotlin: Visitor

10 ideas to improve your Gradle build times [Part III]

Taming Android Unit Test when using different flavors and build types