A race condition is a situation in which two or more parts of a software are reading or writing shared data, and the final result depends on which process finishes or starts first. Race conditions happen when the order of execution is not guaranteed; that's why it's called a "race". Initially, the term applied to threads, but it's now used more broadly to describe any situation where the final state of a system depends on the order of operations.
The classic example is a networked application where two clients are trying to update the same record in a database. If the clients don't lock the record before updating it, the final state of the record will depend on which client updates it last.
Race conditions are complicated to debug, as they are often hard to reproduce.
Racing conditions can happen because:
- Two processes require different loading times. For example, if during development, a file took 200ms to load, but some users have huge files that take 2 seconds to load; the next step might start before the file is loaded, and that bug would only happen for some people.
- Different architectures starting processes in a different order. For example, some systems might order files differently, leading to files being loaded in an unexpected order and leading to results that can't be easily reproduced.
- Other software or hardware issues that might cause a delay in the execution. For example, a programmer will typically only have a few programs running at once, but a user might have many programs running, causing a delay in the execution of some functionality.
- and so on.
Because of the elusive and non-deterministic behavior of race condition, they're sometimes amusingly called "heisenbugs" after the Heisenberg Uncertainty Principle in physics.
While not strictly correct, the term can also be used for deterministic, reproducible bugs, but that still go against your expectations. For example, if you try to access a node that doesn't exist yet in GDScript:
func _init():
var node := get_node("Sprite2D")
Sprite2D doesn't exist yet in the _init()
function; it is only accessible in the _ready()
function. In this case, the error is because of a programmer mistake due to a misunderstanding of the lifecycle of a node. Nonetheless, because the mistake isn't obvious, and hard to predict unless you know Godot well, people will still often call it a "race condition".