Properly Testing Concurrent Data Structures
The article explores testing concurrent data structures using the Rust library loom. It demonstrates creating property tests with managed threads to simulate concurrent behavior, emphasizing synchronization challenges and design considerations.
Read original articleThe article discusses testing concurrent data structures, focusing on a Rust library called loom for testing lock-free data structures. It explores testing a concurrent counter implementation for atomicity using property-based testing (PBT) and managed threads. The author demonstrates how to create a property test using managed threads that can pause and unpause, simulating concurrent behavior. The implementation involves coordinating between the main thread and managed threads using thread_local! and Arc, along with mutexes and condition variables for synchronization. The article delves into the challenges of pausing threads between atomic operations and the design considerations for creating a robust testing framework for concurrent data structures. The approach involves creating a shared context for managing thread states and implementing pause and unpause functionalities to control the flow of execution in concurrent scenarios. The article showcases the intricacies of Rust's API for condition variables and provides insights into handling synchronization in concurrent testing environments.
Related
Download Accelerator – Async Rust Edition
This post explores creating a download accelerator with async Rust, emphasizing its advantages over traditional methods. It demonstrates improved file uploads to Amazon S3 and provides code for parallel downloads.
Atomic Operations Composition in Go
The article discusses atomic operations composition in Go, crucial for predictable results in concurrent programming without locks. Examples show both reliable and unpredictable outcomes, cautioning about atomics' limitations compared to mutexes.
The Inconceivable Types of Rust: How to Make Self-Borrows Safe
The article addresses Rust's limitations on self-borrows, proposing solutions like named lifetimes and inconceivable types to improve support for async functions. Enhancing Rust's type system is crucial for advanced features.
Learning C++ Memory Model from a Distributed System's Perspective (2021)
The article explores C++ memory model in distributed systems, emphasizing std::memory_order for synchronization. It covers happens-before relationships, release-acquire ordering, and memory_order_seq_cst for total ordering and synchronization across threads.
The sad state of property-based testing libraries
Property-based testing, originating in the late nineties at Chalmers University, emphasizes test generation over manual writing. Despite lacking advanced features, Quviq QuickCheck excels in stateful and parallel testing, setting industry standards.
It contains what I believe to be the largest set of test cases of the C++/Rust memory model, which is essentially everything I could find in books, the C++ standard, Stack Overflow, blogs, etc. E.g. this is the file for Rust Atomics and Locks by Mara Bos [2]
Loom [3], referenced in the article, is a similar and much more complete library, which enables you to exhaustively test higher level constructs like mutexes and queues. Although it doesn't model the memory model as exhaustively as Temper (I've been meaning to port my test cases over to it).
I was inspired by a Foundation DB testing talk by Will Wilson [4], who is now over at Antithesis [5], building a hypervisor based solution to perform this style of testing on arbitrary Docker containers.
I strongly believe we're going to see much more in this domain in the coming decade. WebAssembly is at the perfect intersection of being a platform that's complete enough to be able to compile arbitrary software to, but simple enough that building something like Antithesis isn't a half decade long project from an elite team that's already shipped a database.
[1] https://github.com/reitzensteinm/temper/tree/main [2] https://github.com/reitzensteinm/temper/blob/main/memlog/tes... [3] https://github.com/tokio-rs/loom [4] https://www.youtube.com/watch?v=4fFDFbi3toc [5] https://antithesis.com/
Shuttle's approach is to be randomized, instead of exhaustive like loom. However, the scheduler does still give probabilistic guarantees about finding bugs. I found that shuttle was faster and scaled to more complicated test scenarios.
Similar to the article, shuttle also lets you save the random seed if a particular schedule causes the test suite to fail. Being able to quickly reproduce failing tests is really important and enables you write explicit test cases for bugs that were previously caught and fixed [3].
[0] https://github.com/kaymanb/todc/tree/main/todc-mem [1] https://github.com/tokio-rs/loom [2] https://github.com/awslabs/shuttle [3] https://github.com/kaymanb/todc/blob/0e2874a70ec8beed8fae773...
Consider a cmpxchg loop where the body computation is not quite trivial, but on real hardware (and with real schedulers) is exceedingly unlikely to be interrupted on a given CPU. Thus, if `n` is the number of CPUs, it has a worst-case `1/n` chance of making progress. But with this testing approach, it instead has a `1/t^p` chance, where `t` is the number of tasks (which may be far larger than the number of CPUs) and `p` is the number of pauses (easily at least 3) within the not-quite-trivial loop body; this is sufficient to turn a working algorithm into a broken one.
OTOH, if you do want the tester to detect soft forward-progress as a bug (because you want hard forward-progress), this doesn't seem to provide useful tools either.
(Regardless this is certainly useful for a lot of concurrency problems).
Is there a reason we couldn't use some kind of async runtime? It seems like you're instrumenting atomic operations to achieve cooperative multitasking. Maybe I need to drink more coffee, but it seems simpler without threads.
I think the same could be achieved by launching two threads and single stepping them with ptrace to "randomly" interleave the execution of their instructions. Something like rr's chaos mode.
Some instructions may not be atomic though, so we would need a way to single step on "atomic microcodes" if that's even possible without emulation?
#[cfg(loom)]
pub(crate) use loom::sync::atomic::AtomicUsize;
#[cfg(not(loom))]
pub(crate) use std::sync::atomic::AtomicUsize;
I wonder if there are any languages that do a better job of letting you use your own scheduler?Related
Download Accelerator – Async Rust Edition
This post explores creating a download accelerator with async Rust, emphasizing its advantages over traditional methods. It demonstrates improved file uploads to Amazon S3 and provides code for parallel downloads.
Atomic Operations Composition in Go
The article discusses atomic operations composition in Go, crucial for predictable results in concurrent programming without locks. Examples show both reliable and unpredictable outcomes, cautioning about atomics' limitations compared to mutexes.
The Inconceivable Types of Rust: How to Make Self-Borrows Safe
The article addresses Rust's limitations on self-borrows, proposing solutions like named lifetimes and inconceivable types to improve support for async functions. Enhancing Rust's type system is crucial for advanced features.
Learning C++ Memory Model from a Distributed System's Perspective (2021)
The article explores C++ memory model in distributed systems, emphasizing std::memory_order for synchronization. It covers happens-before relationships, release-acquire ordering, and memory_order_seq_cst for total ordering and synchronization across threads.
The sad state of property-based testing libraries
Property-based testing, originating in the late nineties at Chalmers University, emphasizes test generation over manual writing. Despite lacking advanced features, Quviq QuickCheck excels in stateful and parallel testing, setting industry standards.