From 2235e84a2dbcf14f8360f4eb8089bdc9fab7c7f9 Mon Sep 17 00:00:00 2001 From: Bowen <36908971+BowenXiao1999@users.noreply.github.com> Date: Thu, 15 Dec 2022 12:11:49 +0800 Subject: [PATCH] feat: add loom test for the counter (#6888) Basically same as #6822. But we reuse the Counter to avoid code duplicate. Copy the content here. Use https://github.com/tokio-rs/loom for concurrency test. Maybe can make the correctness more confident. But I'm not so sure how to write the best test. it requires some code change for previous structure, so may need discuss: remove workspacke hack in task_stats_alloc crate. Otherwise there will be package conflict if you run RUSTFLAGS="--cfg loom" cargo test --test loom. Have not investigate deeply but simple remove just works. Refactor &'static AtomicUsize to be NonNull. This is because the AtomicUsize in loom type do not support .as_mut_ptr (described in What to do when loom::AtomicUsize do not implement as_mut_ptr() tokio-rs/loom#298), so we use NonNull as intermediate workaround, that will add some unsafe code in .add() and .sub(). But seems OK. To test the drop, I have to add a flag var AtomicUsize in .sub() to ensure that the value is dropped. Please provide some suggestions if you have better ideas on how to write test. Approved-By: BugenZhao Approved-By: liurenjie1024 Co-Authored-By: BowenXiao1999 <931759898@qq.com> Co-Authored-By: Bowen <36908971+BowenXiao1999@users.noreply.github.com> --- Cargo.lock | 80 +++++++++++++++++++++++- src/utils/task_stats_alloc/Cargo.toml | 5 +- src/utils/task_stats_alloc/src/lib.rs | 36 +++++++---- src/utils/task_stats_alloc/tests/loom.rs | 50 +++++++++++++++ 4 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 src/utils/task_stats_alloc/tests/loom.rs diff --git a/Cargo.lock b/Cargo.lock index b4c578e4c1ad..92d72052d1db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2280,6 +2280,19 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generator" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -3093,6 +3106,22 @@ dependencies = [ "value-bag", ] +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "pin-utils", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.7.6" @@ -6226,6 +6255,12 @@ dependencies = [ "parking_lot 0.12.1", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -6909,8 +6944,8 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" name = "task_stats_alloc" version = "0.1.11" dependencies = [ + "loom", "madsim-tokio", - "workspace-hack", ] [[package]] @@ -7855,6 +7890,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +dependencies = [ + "windows_aarch64_msvc 0.39.0", + "windows_i686_gnu 0.39.0", + "windows_i686_msvc 0.39.0", + "windows_x86_64_gnu 0.39.0", + "windows_x86_64_msvc 0.39.0", +] + [[package]] name = "windows-sys" version = "0.36.1" @@ -7895,6 +7943,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" + [[package]] name = "windows_aarch64_msvc" version = "0.42.0" @@ -7907,6 +7961,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" + [[package]] name = "windows_i686_gnu" version = "0.42.0" @@ -7919,6 +7979,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" + [[package]] name = "windows_i686_msvc" version = "0.42.0" @@ -7931,6 +7997,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" + [[package]] name = "windows_x86_64_gnu" version = "0.42.0" @@ -7949,6 +8021,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" + [[package]] name = "windows_x86_64_msvc" version = "0.42.0" diff --git a/src/utils/task_stats_alloc/Cargo.toml b/src/utils/task_stats_alloc/Cargo.toml index 75d3890b02a1..6e5d22773fb6 100644 --- a/src/utils/task_stats_alloc/Cargo.toml +++ b/src/utils/task_stats_alloc/Cargo.toml @@ -15,6 +15,9 @@ tokio = { version = "0.2", package = "madsim-tokio", features = [ "time", "signal", ] } -workspace-hack = { path = "../../workspace-hack" } [dev-dependencies] + + +[target.'cfg(loom)'.dependencies] +loom = {version = "0.5", features = ["futures", "checkpoint"]} diff --git a/src/utils/task_stats_alloc/src/lib.rs b/src/utils/task_stats_alloc/src/lib.rs index ae928d594c91..84e702ba3428 100644 --- a/src/utils/task_stats_alloc/src/lib.rs +++ b/src/utils/task_stats_alloc/src/lib.rs @@ -18,21 +18,28 @@ use std::alloc::{GlobalAlloc, Layout, System}; use std::future::Future; +use std::ptr::NonNull; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; use tokio::task_local; +/// If you change the code in this struct, pls re-run the `tests/loom.rs` test locally. #[repr(transparent)] #[derive(Clone, Copy, Debug)] -pub struct TaskLocalBytesAllocated(Option<&'static AtomicUsize>); +pub struct TaskLocalBytesAllocated(Option>); impl Default for TaskLocalBytesAllocated { fn default() -> Self { - Self(Some(Box::leak(Box::new_in(0.into(), System)))) + Self(Some( + NonNull::new(Box::into_raw(Box::new_in(0.into(), System))).unwrap(), + )) } } +// Need this otherwise the NonNull is not Send and can not be used in future. +unsafe impl Send for TaskLocalBytesAllocated {} + impl TaskLocalBytesAllocated { pub fn new() -> Self { Self::default() @@ -45,9 +52,10 @@ impl TaskLocalBytesAllocated { /// Adds to the current counter. #[inline(always)] - fn add(&self, val: usize) { + pub fn add(&self, val: usize) { if let Some(bytes) = self.0 { - bytes.fetch_add(val, Ordering::Relaxed); + let bytes_ref = unsafe { bytes.as_ref() }; + bytes_ref.fetch_add(val, Ordering::Relaxed); } } @@ -57,33 +65,37 @@ impl TaskLocalBytesAllocated { /// The caller must ensure that `self` is valid. #[inline(always)] unsafe fn add_unchecked(&self, val: usize) { - self.0.unwrap_unchecked().fetch_add(val, Ordering::Relaxed); + let bytes = self.0.unwrap_unchecked(); + let bytes_ref = unsafe { bytes.as_ref() }; + bytes_ref.fetch_add(val, Ordering::Relaxed); } /// Subtracts from the counter value, and `drop` the counter while the count reaches zero. #[inline(always)] - fn sub(&self, val: usize) { + pub fn sub(&self, val: usize) -> bool { if let Some(bytes) = self.0 { // Use `Relaxed` order as we don't need to sync read/write with other memory addresses. // Accesses to the counter itself are serialized by atomic operations. - let old_bytes = bytes.fetch_sub(val, Ordering::Relaxed); + let bytes_ref = unsafe { bytes.as_ref() }; + let old_bytes = bytes_ref.fetch_sub(val, Ordering::Relaxed); // If the counter reaches zero, delete the counter. Note that we've ensured there's no // zero deltas in `wrap_layout`, so there'll be no more uses of the counter. if old_bytes == val { // No fence here, this is different from ref counter impl in https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html#boost_atomic.usage_examples.example_reference_counters. // As here, T is the exactly Counter and they have same memory address, so there // should not happen out-of-order commit. - unsafe { Box::from_raw_in(bytes.as_mut_ptr(), System) }; + unsafe { Box::from_raw_in(bytes.as_ptr(), System) }; + return true; } } + false } #[inline(always)] pub fn val(&self) -> usize { - self.0 - .as_ref() - .expect("bytes is invalid") - .load(Ordering::Relaxed) + let bytes_ref = self.0.as_ref().expect("bytes is invalid"); + let bytes_ref = unsafe { bytes_ref.as_ref() }; + bytes_ref.load(Ordering::Relaxed) } } diff --git a/src/utils/task_stats_alloc/tests/loom.rs b/src/utils/task_stats_alloc/tests/loom.rs new file mode 100644 index 000000000000..4a3bce446f09 --- /dev/null +++ b/src/utils/task_stats_alloc/tests/loom.rs @@ -0,0 +1,50 @@ +// Copyright 2022 Singularity Data +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(loom)] + +/// Note this test is not running in CI, due to the re-compile time cost. Add it when it is +/// necessary. Run `RUSTFLAGS="--cfg loom" cargo test --test loom` to test. +use loom::sync::Arc; +use loom::thread; +use task_stats_alloc::TaskLocalBytesAllocated; + +#[test] +fn test_to_avoid_double_drop() { + loom::model(|| { + let bytes_num = 3; + let num = Arc::new(TaskLocalBytesAllocated::new()); + + let threads: Vec<_> = (0..bytes_num) + .map(|_| { + let num = num.clone(); + thread::spawn(move || { + num.add(1); + num.sub(1) + }) + }) + .collect(); + + // How many times the bytes have been dropped. + let mut drop_num = 0; + for t in threads { + if t.join().unwrap() { + drop_num += 1; + } + } + + // Ensure the counter is dropped. + assert_eq!(drop_num, 1); + }); +}