How to Deal with Race Conditions
"Race conditions" refers to bugs that occur due to the timing or order-of-execution of multiple operations. Here's how to deal with them.
Nov 12th, 2021 8:45am by
Photo by cottonbro from Pexels.
LogDNA sponsored this post.
TOCTOU
“Time-of-check to time-of-use” (TOCTOU) describes a type of race condition that occurs when the state of a resource changes between checking its state and using the result. TOCTOU is usually discussed in the context of filesystem operations, but variations are possible in many areas of the systems we build. The common example of a TOCTOU race condition is checking if a file is accessible and then reading it:
let exists = await fs.access(path_to_file)
if (exists) {
const data = await fs.readFile(path_to_file)
...
}
Atomicity
Normally we talk about “updating atomically” in the context of database systems. Consider the following contrived example:
const user = db.users.findOne(id)
if (user.role === 'admin') {
user.superPowers = true
user.save()
}
Shared State
Mike Del Tito
Mike is an experienced senior developer at LogDNA, with a demonstrated history of designing, delivering and leading teams in the development of web-based software applications.
- Open a write stream for a specific file for all workers to write to.
- Create a record in a database if it doesn’t already exist.
- Memoize the result of a very expensive operation.
Counter Measures
Avoiding race conditions not only requires some thought about what your code is doing, but also about how other parts of the system will use your code. There are no silver bullets here, but in addition to being thoughtful about concurrent design, here are some tips:- Perform database updates atomically. Do not rely on previously queried information about the record you are updating to craft your update query.
- In general, avoid sharing “global” state whenever possible, but especially with concurrency. Think about the implications of simultaneous access to data structures and how that affects the logic of the program or the correctness of the data itself.
- If sharing state among concurrent routines is required, consider introducing a mutex (mutually exclusive) or another locking mechanism to control access to the shared resource. This comes at the expense of complexity, but is sometimes unavoidable.
YOUTUBE.COM/THENEWSTACK
Tech moves fast, don't miss an episode. Subscribe to our YouTube
channel to stream all our podcasts, interviews, demos, and more.