Skip to content

Commit 60f598c

Browse files
committed
safe point
Signed-off-by: TXXT <[email protected]>
1 parent 9c6b283 commit 60f598c

File tree

1 file changed

+142
-90
lines changed

1 file changed

+142
-90
lines changed

src/lib.rs

Lines changed: 142 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ use std::env::VarError;
230230
use std::fmt::Debug;
231231
use std::str::FromStr;
232232
use std::sync::atomic::{AtomicUsize, Ordering};
233-
use std::sync::{Arc, Condvar, Mutex, MutexGuard, RwLock, TryLockError};
233+
use std::sync::{Arc, Condvar, Mutex, RwLock, TryLockError};
234234
use std::time::{Duration, Instant};
235235
use std::{env, thread};
236236

@@ -421,7 +421,7 @@ impl FromStr for Action {
421421
}
422422

423423
#[cfg_attr(feature = "cargo-clippy", allow(clippy::mutex_atomic))]
424-
#[derive(Debug)]
424+
#[derive(Debug, Default)]
425425
struct FailPoint {
426426
pause: Mutex<bool>,
427427
pause_notifier: Condvar,
@@ -509,98 +509,115 @@ impl FailPoint {
509509
/// Registry with failpoints configuration.
510510
type Registry = HashMap<String, Arc<FailPoint>>;
511511

512-
#[derive(Debug, Default)]
513-
struct FailPointRegistry {
512+
/// Fail point registry. Each thread should be bound to exact one registry.
513+
/// Threads bound to the same registry share the same failpoints configuration.
514+
#[derive(Debug, Default, Clone)]
515+
pub struct FailPointRegistry {
514516
// TODO: remove rwlock or store *mut FailPoint
515-
registry: RwLock<Registry>,
517+
registry: Arc<RwLock<Registry>>,
516518
}
517519

518-
lazy_static::lazy_static! {
519-
static ref REGISTRY: FailPointRegistry = FailPointRegistry::default();
520-
static ref SCENARIO: Mutex<&'static FailPointRegistry> = Mutex::new(&REGISTRY);
520+
/// Generate a new failpoint registry. The new registry will inherit the
521+
/// global failpoints configuration.
522+
pub fn new_fail_group() -> FailPointRegistry {
523+
let registry = REGISTRY_GLOBAL.registry.read().unwrap().clone();
524+
FailPointRegistry {
525+
registry: Arc::new(RwLock::new(registry)),
526+
}
521527
}
522528

523-
/// Test scenario with configured fail points.
524-
#[derive(Debug)]
525-
pub struct FailScenario<'a> {
526-
scenario_guard: MutexGuard<'a, &'static FailPointRegistry>,
527-
}
529+
impl FailPointRegistry {
530+
/// Register the current thread to this failpoints registry.
531+
pub fn register_current(&self) {
532+
let id = thread::current().id();
533+
REGISTRY_GROUP
534+
.write()
535+
.unwrap()
536+
.insert(id, self.registry.clone());
537+
}
528538

529-
impl<'a> FailScenario<'a> {
530-
/// Set up the system for a fail points scenario.
531-
///
532-
/// Configures all fail points specified in the `FAILPOINTS` environment variable.
533-
/// It does not otherwise change any existing fail point configuration.
534-
///
535-
/// The format of `FAILPOINTS` is `failpoint=actions;...`, where
536-
/// `failpoint` is the name of the fail point. For more information
537-
/// about fail point actions see the [`cfg`](fn.cfg.html) function and
538-
/// the [`fail_point`](macro.fail_point.html) macro.
539-
///
540-
/// `FAILPOINTS` may configure fail points that are not actually defined. In
541-
/// this case the configuration has no effect.
542-
///
543-
/// This function should generally be called prior to running a test with fail
544-
/// points, and afterward paired with [`teardown`](#method.teardown).
545-
///
546-
/// # Panics
547-
///
548-
/// Panics if an action is not formatted correctly.
549-
pub fn setup() -> Self {
550-
// Cleanup first, in case of previous failed/panic'ed test scenarios.
551-
let scenario_guard = SCENARIO.lock().unwrap_or_else(|e| e.into_inner());
552-
let mut registry = scenario_guard.registry.write().unwrap();
553-
Self::cleanup(&mut registry);
554-
555-
let failpoints = match env::var("FAILPOINTS") {
556-
Ok(s) => s,
557-
Err(VarError::NotPresent) => return Self { scenario_guard },
558-
Err(e) => panic!("invalid failpoints: {:?}", e),
559-
};
560-
for mut cfg in failpoints.trim().split(';') {
561-
cfg = cfg.trim();
562-
if cfg.is_empty() {
563-
continue;
564-
}
565-
let (name, order) = partition(cfg, '=');
566-
match order {
567-
None => panic!("invalid failpoint: {:?}", cfg),
568-
Some(order) => {
569-
if let Err(e) = set(&mut registry, name.to_owned(), order) {
570-
panic!("unable to configure failpoint \"{}\": {}", name, e);
571-
}
572-
}
573-
}
574-
}
575-
Self { scenario_guard }
539+
/// Deregister the current thread to this failpoints registry.
540+
pub fn deregister_current(&self) {
541+
let id = thread::current().id();
542+
REGISTRY_GROUP.write().unwrap().remove(&id);
576543
}
577544

578-
/// Tear down the fail point system.
579-
///
580-
/// Clears the configuration of all fail points. Any paused fail
581-
/// points will be notified before they are deactivated.
582-
///
583-
/// This function should generally be called after running a test with fail points.
584-
/// Calling `teardown` without previously calling `setup` results in a no-op.
585-
pub fn teardown(self) {
586-
drop(self)
545+
/// Clean registered fail points in this registry.
546+
pub fn cleanup(&self) {
547+
let mut registry = self.registry.write().unwrap();
548+
cleanup(&mut registry);
587549
}
550+
}
588551

589-
/// Clean all registered fail points.
590-
fn cleanup(registry: &mut std::sync::RwLockWriteGuard<'a, Registry>) {
591-
for p in registry.values() {
592-
// wake up all pause failpoint.
593-
p.set_actions("", vec![]);
552+
lazy_static::lazy_static! {
553+
static ref REGISTRY_GROUP: RwLock<HashMap<thread::ThreadId, Arc<RwLock<Registry>>>> = Default::default();
554+
static ref REGISTRY_GLOBAL: FailPointRegistry = Default::default();
555+
}
556+
557+
/// Set up the system for a fail points scenario.
558+
///
559+
/// Configures all fail points specified in the `FAILPOINTS` environment variable.
560+
/// It does not otherwise change any existing fail point configuration.
561+
///
562+
/// The format of `FAILPOINTS` is `failpoint=actions;...`, where
563+
/// `failpoint` is the name of the fail point. For more information
564+
/// about fail point actions see the [`cfg`](fn.cfg.html) function and
565+
/// the [`fail_point`](macro.fail_point.html) macro.
566+
///
567+
/// `FAILPOINTS` may configure fail points that are not actually defined. In
568+
/// this case the configuration has no effect.
569+
///
570+
/// This function should generally be called prior to running a test with fail
571+
/// points, and afterward paired with [`teardown`](#method.teardown).
572+
///
573+
/// # Panics
574+
///
575+
/// Panics if an action is not formatted correctly.
576+
pub fn setup() {
577+
let mut registry = REGISTRY_GLOBAL.registry.write().unwrap();
578+
cleanup(&mut registry);
579+
580+
let failpoints = match env::var("FAILPOINTS") {
581+
Ok(s) => s,
582+
Err(VarError::NotPresent) => return,
583+
Err(e) => panic!("invalid failpoints: {:?}", e),
584+
};
585+
for mut cfg in failpoints.trim().split(';') {
586+
cfg = cfg.trim();
587+
if cfg.is_empty() {
588+
continue;
589+
}
590+
let (name, order) = partition(cfg, '=');
591+
match order {
592+
None => panic!("invalid failpoint: {:?}", cfg),
593+
Some(order) => {
594+
if let Err(e) = set(&mut registry, name.to_owned(), order) {
595+
panic!("unable to configure failpoint \"{}\": {}", name, e);
596+
}
597+
}
594598
}
595-
registry.clear();
596599
}
597600
}
598601

599-
impl<'a> Drop for FailScenario<'a> {
600-
fn drop(&mut self) {
601-
let mut registry = self.scenario_guard.registry.write().unwrap();
602-
Self::cleanup(&mut registry)
602+
/// Tear down the fail point system.
603+
///
604+
/// Clears the configuration of all fail points. Any paused fail
605+
/// points will be notified before they are deactivated.
606+
///
607+
/// This function should generally be called after running a test with fail points.
608+
/// Calling `teardown` without previously calling `setup` results in a no-op.
609+
pub fn teardown() {
610+
let mut registry = REGISTRY_GLOBAL.registry.write().unwrap();
611+
cleanup(&mut registry);
612+
}
613+
614+
/// Clean all registered fail points.
615+
fn cleanup(registry: &mut std::sync::RwLockWriteGuard<Registry>) {
616+
for p in registry.values() {
617+
// wake up all pause failpoint.
618+
p.set_actions("", vec![]);
603619
}
620+
registry.clear();
604621
}
605622

606623
/// Returns whether code generation for failpoints is enabled.
@@ -616,7 +633,15 @@ pub const fn has_failpoints() -> bool {
616633
///
617634
/// Return a vector of `(name, actions)` pairs.
618635
pub fn list() -> Vec<(String, String)> {
619-
let registry = REGISTRY.registry.read().unwrap();
636+
let id = thread::current().id();
637+
let group = REGISTRY_GROUP.read().unwrap();
638+
639+
let registry = group
640+
.get(&id)
641+
.unwrap_or(&REGISTRY_GLOBAL.registry)
642+
.read()
643+
.unwrap();
644+
620645
registry
621646
.iter()
622647
.map(|(name, fp)| (name.to_string(), fp.actions_str.read().unwrap().clone()))
@@ -625,8 +650,16 @@ pub fn list() -> Vec<(String, String)> {
625650

626651
#[doc(hidden)]
627652
pub fn eval<R, F: FnOnce(Option<String>) -> R>(name: &str, f: F) -> Option<R> {
653+
let id = thread::current().id();
654+
let group = REGISTRY_GROUP.read().unwrap();
655+
656+
let registry = group
657+
.get(&id)
658+
.unwrap_or(&REGISTRY_GLOBAL.registry)
659+
.read()
660+
.unwrap();
661+
628662
let p = {
629-
let registry = REGISTRY.registry.read().unwrap();
630663
match registry.get(name) {
631664
None => return None,
632665
Some(p) => p.clone(),
@@ -666,7 +699,14 @@ pub fn eval<R, F: FnOnce(Option<String>) -> R>(name: &str, f: F) -> Option<R> {
666699
/// A call to `cfg` with a particular fail point name overwrites any existing actions for
667700
/// that fail point, including those set via the `FAILPOINTS` environment variable.
668701
pub fn cfg<S: Into<String>>(name: S, actions: &str) -> Result<(), String> {
669-
let mut registry = REGISTRY.registry.write().unwrap();
702+
let id = thread::current().id();
703+
let group = REGISTRY_GROUP.read().unwrap();
704+
let mut registry = group
705+
.get(&id)
706+
.unwrap_or(&REGISTRY_GLOBAL.registry)
707+
.write()
708+
.unwrap();
709+
670710
set(&mut registry, name.into(), actions)
671711
}
672712

@@ -679,7 +719,14 @@ where
679719
S: Into<String>,
680720
F: Fn() + Send + Sync + 'static,
681721
{
682-
let mut registry = REGISTRY.registry.write().unwrap();
722+
let id = thread::current().id();
723+
let group = REGISTRY_GROUP.read().unwrap();
724+
let mut registry = group
725+
.get(&id)
726+
.unwrap_or(&REGISTRY_GLOBAL.registry)
727+
.write()
728+
.unwrap();
729+
683730
let p = registry
684731
.entry(name.into())
685732
.or_insert_with(|| Arc::new(FailPoint::new()));
@@ -691,9 +738,17 @@ where
691738

692739
/// Remove a fail point.
693740
///
694-
/// If the fail point doesn't exist, nothing will happen.
741+
/// If the fail point doesn't exist, it will try to delete the corresponding
742+
/// action in the `REGISTRY_GLOBAL`.
695743
pub fn remove<S: AsRef<str>>(name: S) {
696-
let mut registry = REGISTRY.registry.write().unwrap();
744+
let id = thread::current().id();
745+
let group = REGISTRY_GROUP.read().unwrap();
746+
let mut registry = group
747+
.get(&id)
748+
.unwrap_or(&REGISTRY_GLOBAL.registry)
749+
.write()
750+
.unwrap();
751+
697752
if let Some(p) = registry.remove(name.as_ref()) {
698753
// wake up all pause failpoint.
699754
p.set_actions("", vec![]);
@@ -990,10 +1045,7 @@ mod tests {
9901045
}
9911046
}
9921047

993-
// This case should be tested as integration case, but when calling `teardown` other cases
994-
// like `test_pause` maybe also affected, so it's better keep it here.
9951048
#[test]
996-
#[cfg_attr(not(feature = "failpoints"), ignore)]
9971049
fn test_setup_and_teardown() {
9981050
let f1 = || {
9991051
fail_point!("setup_and_teardown1", |_| 1);
@@ -1007,7 +1059,7 @@ mod tests {
10071059
"FAILPOINTS",
10081060
"setup_and_teardown1=return;setup_and_teardown2=pause;",
10091061
);
1010-
let scenario = FailScenario::setup();
1062+
setup();
10111063
assert_eq!(f1(), 1);
10121064

10131065
let (tx, rx) = mpsc::channel();
@@ -1016,7 +1068,7 @@ mod tests {
10161068
});
10171069
assert!(rx.recv_timeout(Duration::from_millis(500)).is_err());
10181070

1019-
scenario.teardown();
1071+
teardown();
10201072
assert_eq!(rx.recv_timeout(Duration::from_millis(500)).unwrap(), 0);
10211073
assert_eq!(f1(), 0);
10221074
}

0 commit comments

Comments
 (0)