feat: update library lifecycle return value
This commit is contained in:
@@ -82,15 +82,16 @@ impl<S> LibraryLifecycle<S> {
|
|||||||
|
|
||||||
/// Increment the reference count.
|
/// Increment the reference count.
|
||||||
///
|
///
|
||||||
/// On the `0 -> 1` transition the supplied `init` closure runs and its produced `S` is stored.
|
/// On the `0 -> 1` transition the supplied `init` closure runs and its produced `S` is stored;
|
||||||
/// If `init` fails, neither the count nor the state is changed and an error is returned.
|
/// the call returns `Ok(true)`. If `init` fails, neither the count nor the state is changed and
|
||||||
|
/// an error is returned.
|
||||||
///
|
///
|
||||||
/// On any other call (count already `> 0`) the supplied `init` is **dropped without being run**
|
/// On any other call (count already `> 0`) the supplied `init` is **dropped without being run**,
|
||||||
/// and the count is simply incremented.
|
/// the count is simply incremented, and the call returns `Ok(false)`.
|
||||||
///
|
///
|
||||||
/// The init closure runs under the write lock and must not re-enter this lifecycle. Its error
|
/// The init closure runs under the write lock and must not re-enter this lifecycle. Its error
|
||||||
/// type `E` is type-erased.
|
/// type `E` is type-erased.
|
||||||
pub fn startup<E>(&self, init: impl FnOnce() -> Result<S, E>) -> Result<(), Error>
|
pub fn startup<E>(&self, init: impl FnOnce() -> Result<S, E>) -> Result<bool, Error>
|
||||||
where
|
where
|
||||||
E: std::error::Error + Send + Sync + 'static,
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@@ -99,22 +100,24 @@ impl<S> LibraryLifecycle<S> {
|
|||||||
let s = init().map_err(|e| Error::Init(Box::new(e)))?;
|
let s = init().map_err(|e| Error::Init(Box::new(e)))?;
|
||||||
inner.state = Some(s);
|
inner.state = Some(s);
|
||||||
inner.count = 1;
|
inner.count = 1;
|
||||||
|
Ok(true)
|
||||||
} else {
|
} else {
|
||||||
inner.count += 1;
|
inner.count += 1;
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrement the reference count.
|
/// Decrement the reference count.
|
||||||
///
|
///
|
||||||
/// On the `-> 0` transition the stored `S` is taken out and handed to the supplied `destroy`
|
/// On the `-> 0` transition the stored `S` is taken out and handed to the supplied `destroy`
|
||||||
/// closure, which must never fail (destructor semantics). On any other call (count stays `> 0`)
|
/// closure, which must never fail (destructor semantics), and the call returns `Ok(true)`. On
|
||||||
/// the supplied `destroy` is **dropped without being run** and the count is simply decremented.
|
/// any other call (count stays `> 0`) the supplied `destroy` is **dropped without being run**,
|
||||||
|
/// the count is simply decremented, and the call returns `Ok(false)`.
|
||||||
///
|
///
|
||||||
/// Returns an error if the count was already zero.
|
/// Returns an error if the count was already zero.
|
||||||
///
|
///
|
||||||
/// The destroy closure runs under the write lock and must not re-enter this lifecycle.
|
/// The destroy closure runs under the write lock and must not re-enter this lifecycle.
|
||||||
pub fn shutdown(&self, destroy: impl FnOnce(S)) -> Result<(), Error> {
|
pub fn shutdown(&self, destroy: impl FnOnce(S)) -> Result<bool, Error> {
|
||||||
let mut inner = self.inner.write().expect("unexpected poison lock");
|
let mut inner = self.inner.write().expect("unexpected poison lock");
|
||||||
if inner.count == 0 {
|
if inner.count == 0 {
|
||||||
return Err(Error::Underflow);
|
return Err(Error::Underflow);
|
||||||
@@ -124,8 +127,10 @@ impl<S> LibraryLifecycle<S> {
|
|||||||
// Invariant: state is Some whenever count > 0.
|
// Invariant: state is Some whenever count > 0.
|
||||||
let s = inner.state.take().expect("state present while count > 0");
|
let s = inner.state.take().expect("state present while count > 0");
|
||||||
destroy(s);
|
destroy(s);
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the stored state by shared reference under a read lock.
|
/// Access the stored state by shared reference under a read lock.
|
||||||
|
|||||||
@@ -16,24 +16,28 @@ struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Coverage: a single paired startup/shutdown runs init once and destroy once, destroy receives the
|
/// Coverage: a single paired startup/shutdown runs init once and destroy once, destroy receives the
|
||||||
/// state produced by init, and the count returns to zero.
|
/// state produced by init, both report that they ran the closure, and the count returns to zero.
|
||||||
#[test]
|
#[test]
|
||||||
fn paired_startup_shutdown() {
|
fn paired_startup_shutdown() {
|
||||||
let inits = AtomicUsize::new(0);
|
let inits = AtomicUsize::new(0);
|
||||||
let destroys = AtomicUsize::new(0);
|
let destroys = AtomicUsize::new(0);
|
||||||
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
|
||||||
lc.startup(|| -> Result<State, TestErr> {
|
let ran_init = lc
|
||||||
inits.fetch_add(1, Ordering::SeqCst);
|
.startup(|| -> Result<State, TestErr> {
|
||||||
Ok(State { value: 42 })
|
inits.fetch_add(1, Ordering::SeqCst);
|
||||||
})
|
Ok(State { value: 42 })
|
||||||
.unwrap();
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(ran_init);
|
||||||
|
|
||||||
lc.shutdown(|s| {
|
let ran_destroy = lc
|
||||||
destroys.fetch_add(1, Ordering::SeqCst);
|
.shutdown(|s| {
|
||||||
assert_eq!(s.value, 42);
|
destroys.fetch_add(1, Ordering::SeqCst);
|
||||||
})
|
assert_eq!(s.value, 42);
|
||||||
.unwrap();
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(ran_destroy);
|
||||||
|
|
||||||
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
||||||
assert_eq!(destroys.load(Ordering::SeqCst), 1);
|
assert_eq!(destroys.load(Ordering::SeqCst), 1);
|
||||||
@@ -42,29 +46,35 @@ fn paired_startup_shutdown() {
|
|||||||
|
|
||||||
/// Coverage: multiple startups followed by matching shutdowns run init and destroy only on the
|
/// Coverage: multiple startups followed by matching shutdowns run init and destroy only on the
|
||||||
/// boundary transitions (once each); the init/destroy closures passed on non-transition calls are
|
/// boundary transitions (once each); the init/destroy closures passed on non-transition calls are
|
||||||
/// dropped without being run.
|
/// dropped without being run, and the bool return reflects exactly which calls ran a closure.
|
||||||
#[test]
|
#[test]
|
||||||
fn multiple_startups_shutdowns_run_only_on_boundaries() {
|
fn multiple_startups_shutdowns_run_only_on_boundaries() {
|
||||||
let inits = AtomicUsize::new(0);
|
let inits = AtomicUsize::new(0);
|
||||||
let destroys = AtomicUsize::new(0);
|
let destroys = AtomicUsize::new(0);
|
||||||
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
|
||||||
for _ in 0..3 {
|
let startup_ran: Vec<bool> = (0..3)
|
||||||
lc.startup(|| -> Result<State, TestErr> {
|
.map(|_| {
|
||||||
inits.fetch_add(1, Ordering::SeqCst);
|
lc.startup(|| -> Result<State, TestErr> {
|
||||||
Ok(State { value: 7 })
|
inits.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(State { value: 7 })
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
})
|
})
|
||||||
.unwrap();
|
.collect();
|
||||||
}
|
assert_eq!(startup_ran, vec![true, false, false]);
|
||||||
assert_eq!(lc.count(), 3);
|
assert_eq!(lc.count(), 3);
|
||||||
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
||||||
|
|
||||||
for _ in 0..3 {
|
let shutdown_ran: Vec<bool> = (0..3)
|
||||||
lc.shutdown(|_s| {
|
.map(|_| {
|
||||||
destroys.fetch_add(1, Ordering::SeqCst);
|
lc.shutdown(|_s| {
|
||||||
|
destroys.fetch_add(1, Ordering::SeqCst);
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
})
|
})
|
||||||
.unwrap();
|
.collect();
|
||||||
}
|
assert_eq!(shutdown_ran, vec![false, false, true]);
|
||||||
|
|
||||||
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
||||||
assert_eq!(destroys.load(Ordering::SeqCst), 1);
|
assert_eq!(destroys.load(Ordering::SeqCst), 1);
|
||||||
@@ -79,15 +89,19 @@ fn reinitialization_runs_init_and_destroy_again() {
|
|||||||
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
lc.startup(|| -> Result<State, TestErr> {
|
let ran_init = lc
|
||||||
inits.fetch_add(1, Ordering::SeqCst);
|
.startup(|| -> Result<State, TestErr> {
|
||||||
Ok(State { value: 1 })
|
inits.fetch_add(1, Ordering::SeqCst);
|
||||||
})
|
Ok(State { value: 1 })
|
||||||
.unwrap();
|
})
|
||||||
lc.shutdown(|_s| {
|
.unwrap();
|
||||||
destroys.fetch_add(1, Ordering::SeqCst);
|
assert!(ran_init);
|
||||||
})
|
let ran_destroy = lc
|
||||||
.unwrap();
|
.shutdown(|_s| {
|
||||||
|
destroys.fetch_add(1, Ordering::SeqCst);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(ran_destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(inits.load(Ordering::SeqCst), 2);
|
assert_eq!(inits.load(Ordering::SeqCst), 2);
|
||||||
|
|||||||
Reference in New Issue
Block a user