I thought a little bit about how a race-detecting std.testing.io would work.
The idea is based around a single-threaded, task-switching implementation and the concept of a total ordering of all operations, which is logically modeled as a single integer. Each integer value represents one of the possible executions of the process with respect to the order of operations. Since the possibility space can be quite large, this integer potentially has many bits.
The key observation is that every async has two possibilities: immediate execution of the task, or delayed execution. Therefore, each async adds one extra bit into this total ordering.
By contrast, asyncConcurrent only has one possibility: delayed execution. So these donāt affect the total ordering (yet).
The next ingredient is when task switching/yielding. This happens due to I/O being incomplete, or due to synchronization primitives such as Io.await or Io.Mutex. Here, there may be nondeterminism introduced by the OS, but there is a queue of tasks, and some subset of them are ready to be switched to. So then you have log2(queue length) number of bits added to the total ordering corresponding to which task is removed from the queue.
Regarding OS nondeterminism, file system, networking, timers, etc can be mocked so that they use the Io interface primitives rather than talking to the OS. This makes them participate in the total ordering deterministically.
Putting this together, we have an integer that we can pass to a testing Io implementation that is capable of representing all possible execution paths through the program. It may be a large number of bits, in which case it will have to be chosen randomly (perhaps by fuzzer) rather than iterating every possibility.
What does this give us? Iām not sure actually. I think itās a key ingredient in ⦠something.
Some kinds of bugs Iād like to catch:
- resource leaks (trivial)
- data races
- race condition logic bugs (accessing memory before/during calculation by a different task)
- deadlocks due to logic bugs of mutex or other synchronization primitive usage