This library provides concurrency primitives through two versions of the same library with the same API surface:
This makes adding support for concurrency testing through loom much easier. Because loom concurrency primitives occasionally have slightly different APIs compared to the standard library, wrapper types are occasionally used to provide a unified API surface.
Concurrency tests can only be run on host, and so any library or binary that needs concurrency testing must also compile and run on host. You may need to mock Fuchsia-specific systems, disable Fuchisa-specific code, and disable Fuchsia-specific dependencies.
If your target contains unsafe code, then running on host will also allow you to run your tests through MIRI to detect Undefined Behavior. MIRI can detect Undefined Behavior even if it wouldn't trigger a sanitizer like ASAN or UBSAN, and provides detailed backtraces to aid in debugging and fixing unsoundness.
Let's say you have an existing library target like this:
rustc_library("foo") {
edition = ".."
sources = [ .. ]
deps = [ .. ]
}
Which uses concurrency primitives like these:
use std::sync::{Arc, Mutex};
use std::sync::atomic::AtomicUsize;
use std::sync::mpsc::{Sender, Receiver, channel};
You can build the host-specific version of your library by adding the host toolchain to the end of the target label (e.g. //src/lib/foo(//build/toolchain:host_x64)).
We're going to have two versions of the target which we build: a production one that uses the standard library concurrency primitives, and a testing one that uses loom concurrency primitives. You can move your common build configuration into a shared object, and then add separate fuchsia-loom dependencies for each:
_common = {
edition = ".."
sources = [ .. ]
deps = [ .. ]
}
rustc_library("foo") {
forward_variables_from(_common, "*")
deps += [ "//sdk/lib/fuchsia-loom" ]
}
if (is_host) {
rustc_library("foo_loom) {
forward_variables_from(_common, "*")
configs += [ ":loom" ]
deps += [ "//sdk/lib/fuchisa-loom:loom" ]
testonly = true
}
}
config("loom") {
# The loom crate documentation recommends compiling with optimizations since
# the number of iterations can be large enough to make tests unreasonably slow
# otherwise.
configs = [ "//build/config:optimize_moderate" ]
}
You'll need to replace your concurrency primitives with ones from fuchsia-loom:
Arc: use fuchisa_loom::sync::ArcMutex: use fuchsia_loom::sync::MutexAtomic{Usize, Bool, ..}: use fuchsia_loom::sync::atomic::Atomic{..}UnsafeCell: use fuchsia_loom::cell::UnsafeCell and replace get() with .with() or .with_mut() depending on whether you need a mutable pointer.AtomicWaker: use fuchsia_loom::future::AtomicWakerunreachable_unchecked(): use fuchsia_loom::hint::unreachable_uncheckedSome of the API surfaces for these primitives differ from the standard library API surfaces, so you may have to change some of your code to adapt.
Concurrency tests can be added to a tests/loom.rs file. You'll need to make a loom::model::Builder and call check(..) on some test code that exercises your concurrent code. Some basic tips:
preemption_bound field on a loom::model::Builder.spawn(..) calls with loom::thread::spawn(loom::future::block_on(..)). This effectively transforms each task into a separate “thread”, though they’re not literally modeled as threads in loom.loom::thread::spawn, then you won’t actually verify anything!The last thing you need to do is actually write a test target for your concurrency tests:
if (is_host) {
# ...
rustc_test("foo_loom_tests") {
edition = ".."
source_root = "tests/loom.rs"
sources = [ "tests/loom.rs" ]
deps = [ ":foo_loom" ]
}
}
You can add this to your tests group as usual, just make sure to specify that it uses the host toolchain:
group("tests") {
testonly = true
deps = [ ":foo_loom_tests($host_toolchain)" ]
}