mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-10-28 23:35:02 -04:00
wrap Mutex and Condvar to handle poison
This centralizes a major source of `.unwrap()` throughout the code, and one that would otherwise grow with upcoming changes. The new error message should be more clear.
This commit is contained in:
parent
2903b680df
commit
0ccc6d0769
@ -7,8 +7,8 @@
|
|||||||
//! Note these types are in a more standard nanosecond-based format, where
|
//! Note these types are in a more standard nanosecond-based format, where
|
||||||
//! [`crate::time`] uses Moonfire's 90 kHz time base.
|
//! [`crate::time`] uses Moonfire's 90 kHz time base.
|
||||||
|
|
||||||
|
use crate::Mutex;
|
||||||
use nix::sys::time::{TimeSpec, TimeValLike as _};
|
use nix::sys::time::{TimeSpec, TimeValLike as _};
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::sync::{mpsc, Arc};
|
use std::sync::{mpsc, Arc};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
pub use std::time::Duration;
|
pub use std::time::Duration;
|
||||||
@ -220,15 +220,15 @@ impl SimulatedClocks {
|
|||||||
|
|
||||||
impl Clocks for SimulatedClocks {
|
impl Clocks for SimulatedClocks {
|
||||||
fn realtime(&self) -> SystemTime {
|
fn realtime(&self) -> SystemTime {
|
||||||
self.0.boot + *self.0.uptime.lock().unwrap()
|
self.0.boot + *self.0.uptime.lock()
|
||||||
}
|
}
|
||||||
fn monotonic(&self) -> Instant {
|
fn monotonic(&self) -> Instant {
|
||||||
Instant(TimeSpec::from(*self.0.uptime.lock().unwrap()))
|
Instant(TimeSpec::from(*self.0.uptime.lock()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advances the clock by the specified amount without actually sleeping.
|
/// Advances the clock by the specified amount without actually sleeping.
|
||||||
fn sleep(&self, how_long: Duration) {
|
fn sleep(&self, how_long: Duration) {
|
||||||
let mut l = self.0.uptime.lock().unwrap();
|
let mut l = self.0.uptime.lock();
|
||||||
*l += how_long;
|
*l += how_long;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,3 +14,64 @@ pub use crate::error::{Error, ErrorBuilder, ErrorKind, ResultExt};
|
|||||||
pub use ahash::RandomState;
|
pub use ahash::RandomState;
|
||||||
pub type FastHashMap<K, V> = std::collections::HashMap<K, V, ahash::RandomState>;
|
pub type FastHashMap<K, V> = std::collections::HashMap<K, V, ahash::RandomState>;
|
||||||
pub type FastHashSet<K> = std::collections::HashSet<K, ahash::RandomState>;
|
pub type FastHashSet<K> = std::collections::HashSet<K, ahash::RandomState>;
|
||||||
|
|
||||||
|
const NOT_POISONED: &str =
|
||||||
|
"not poisoned; this is a consequence of an earlier panic while holding this mutex; see logs.";
|
||||||
|
|
||||||
|
/// [`std::sync::Mutex`] wrapper which always panics on encountering poison.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Mutex<T>(std::sync::Mutex<T>);
|
||||||
|
|
||||||
|
impl<T> Mutex<T> {
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(value: T) -> Self {
|
||||||
|
Mutex(std::sync::Mutex::new(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
#[inline]
|
||||||
|
pub fn lock(&self) -> std::sync::MutexGuard<T> {
|
||||||
|
self.0.lock().expect(NOT_POISONED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
#[inline]
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.0.into_inner().expect(NOT_POISONED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`std::sync::Condvar`] wrapper which always panics on encountering poison.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Condvar(std::sync::Condvar);
|
||||||
|
|
||||||
|
impl Condvar {
|
||||||
|
#[inline]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self(std::sync::Condvar::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
#[inline]
|
||||||
|
pub fn wait_timeout_while<'a, T, F>(
|
||||||
|
&self,
|
||||||
|
guard: std::sync::MutexGuard<'a, T>,
|
||||||
|
dur: std::time::Duration,
|
||||||
|
condition: F,
|
||||||
|
) -> (std::sync::MutexGuard<'a, T>, std::sync::WaitTimeoutResult)
|
||||||
|
where
|
||||||
|
F: FnMut(&mut T) -> bool,
|
||||||
|
{
|
||||||
|
self.0
|
||||||
|
.wait_timeout_while(guard, dur, condition)
|
||||||
|
.expect(NOT_POISONED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Condvar {
|
||||||
|
type Target = std::sync::Condvar;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -15,9 +15,10 @@ use std::pin::Pin;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::{Context, Poll, Waker};
|
use std::task::{Context, Poll, Waker};
|
||||||
|
|
||||||
|
use crate::Condvar;
|
||||||
|
use crate::Mutex;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::sync::{Condvar, Mutex};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ShutdownError;
|
pub struct ShutdownError;
|
||||||
@ -47,7 +48,6 @@ impl Drop for Sender {
|
|||||||
.0
|
.0
|
||||||
.wakers
|
.wakers
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.take()
|
.take()
|
||||||
.expect("only the single Sender takes the slab");
|
.expect("only the single Sender takes the slab");
|
||||||
for w in wakers.drain() {
|
for w in wakers.drain() {
|
||||||
@ -78,7 +78,7 @@ const NO_WAKER: usize = usize::MAX;
|
|||||||
|
|
||||||
impl Receiver {
|
impl Receiver {
|
||||||
pub fn check(&self) -> Result<(), ShutdownError> {
|
pub fn check(&self) -> Result<(), ShutdownError> {
|
||||||
if self.0.wakers.lock().unwrap().is_none() {
|
if self.0.wakers.lock().is_none() {
|
||||||
Err(ShutdownError)
|
Err(ShutdownError)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -107,12 +107,11 @@ impl Receiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_for(&self, timeout: std::time::Duration) -> Result<(), ShutdownError> {
|
pub fn wait_for(&self, timeout: std::time::Duration) -> Result<(), ShutdownError> {
|
||||||
let l = self.0.wakers.lock().unwrap();
|
let l = self.0.wakers.lock();
|
||||||
let result = self
|
let result = self
|
||||||
.0
|
.0
|
||||||
.condvar
|
.condvar
|
||||||
.wait_timeout_while(l, timeout, |wakers| wakers.is_some())
|
.wait_timeout_while(l, timeout, |wakers| wakers.is_some());
|
||||||
.unwrap();
|
|
||||||
if result.1.timed_out() {
|
if result.1.timed_out() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -122,7 +121,7 @@ impl Receiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn poll_impl(inner: &Inner, waker_i: &mut usize, cx: &mut Context<'_>) -> Poll<()> {
|
fn poll_impl(inner: &Inner, waker_i: &mut usize, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
let mut l = inner.wakers.lock().unwrap();
|
let mut l = inner.wakers.lock();
|
||||||
let wakers = match &mut *l {
|
let wakers = match &mut *l {
|
||||||
None => return Poll::Ready(()),
|
None => return Poll::Ready(()),
|
||||||
Some(w) => w,
|
Some(w) => w,
|
||||||
@ -152,7 +151,7 @@ impl Drop for ReceiverRefFuture<'_> {
|
|||||||
if self.waker_i == NO_WAKER {
|
if self.waker_i == NO_WAKER {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut l = self.receiver.0.wakers.lock().unwrap();
|
let mut l = self.receiver.0.wakers.lock();
|
||||||
if let Some(wakers) = &mut *l {
|
if let Some(wakers) = &mut *l {
|
||||||
wakers.remove(self.waker_i);
|
wakers.remove(self.waker_i);
|
||||||
}
|
}
|
||||||
@ -173,7 +172,7 @@ impl Drop for ReceiverFuture {
|
|||||||
if self.waker_i == NO_WAKER {
|
if self.waker_i == NO_WAKER {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut l = self.receiver.wakers.lock().unwrap();
|
let mut l = self.receiver.wakers.lock();
|
||||||
if let Some(wakers) = &mut *l {
|
if let Some(wakers) = &mut *l {
|
||||||
wakers.remove(self.waker_i);
|
wakers.remove(self.waker_i);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ use crate::schema;
|
|||||||
use crate::signal;
|
use crate::signal;
|
||||||
use base::clock::{self, Clocks};
|
use base::clock::{self, Clocks};
|
||||||
use base::strutil::encode_size;
|
use base::strutil::encode_size;
|
||||||
|
use base::Mutex;
|
||||||
use base::{bail, err, Error};
|
use base::{bail, err, Error};
|
||||||
use base::{FastHashMap, FastHashSet};
|
use base::{FastHashMap, FastHashSet};
|
||||||
use hashlink::LinkedHashMap;
|
use hashlink::LinkedHashMap;
|
||||||
@ -52,7 +53,7 @@ use std::path::PathBuf;
|
|||||||
use std::str;
|
use std::str;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::MutexGuard;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use tracing::{error, info, trace};
|
use tracing::{error, info, trace};
|
||||||
@ -562,7 +563,7 @@ impl Stream {
|
|||||||
pub fn days(&self) -> days::Map<days::StreamValue> {
|
pub fn days(&self) -> days::Map<days::StreamValue> {
|
||||||
let mut days = self.committed_days.clone();
|
let mut days = self.committed_days.clone();
|
||||||
for u in &self.uncommitted {
|
for u in &self.uncommitted {
|
||||||
let l = u.lock().unwrap();
|
let l = u.lock();
|
||||||
days.adjust(
|
days.adjust(
|
||||||
l.start..l.start + recording::Duration(i64::from(l.wall_duration_90k)),
|
l.start..l.start + recording::Duration(i64::from(l.wall_duration_90k)),
|
||||||
1,
|
1,
|
||||||
@ -896,7 +897,7 @@ impl LockedDatabase {
|
|||||||
);
|
);
|
||||||
match stream.uncommitted.back() {
|
match stream.uncommitted.back() {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
let l = s.lock().unwrap();
|
let l = s.lock();
|
||||||
r.prev_media_duration =
|
r.prev_media_duration =
|
||||||
l.prev_media_duration + recording::Duration(l.media_duration_90k.into());
|
l.prev_media_duration + recording::Duration(l.media_duration_90k.into());
|
||||||
r.prev_runs = l.prev_runs + if l.run_offset == 0 { 1 } else { 0 };
|
r.prev_runs = l.prev_runs + if l.run_offset == 0 { 1 } else { 0 };
|
||||||
@ -936,7 +937,7 @@ impl LockedDatabase {
|
|||||||
msg("can't sync un-added recording {id}")
|
msg("can't sync un-added recording {id}")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let l = stream.uncommitted[stream.synced_recordings].lock().unwrap();
|
let l = stream.uncommitted[stream.synced_recordings].lock();
|
||||||
let bytes = i64::from(l.sample_file_bytes);
|
let bytes = i64::from(l.sample_file_bytes);
|
||||||
stream.bytes_to_add += bytes;
|
stream.bytes_to_add += bytes;
|
||||||
stream.fs_bytes_to_add += round_up(bytes);
|
stream.fs_bytes_to_add += round_up(bytes);
|
||||||
@ -1014,7 +1015,7 @@ impl LockedDatabase {
|
|||||||
let mut new_duration = 0;
|
let mut new_duration = 0;
|
||||||
let mut new_runs = 0;
|
let mut new_runs = 0;
|
||||||
for i in 0..s.synced_recordings {
|
for i in 0..s.synced_recordings {
|
||||||
let l = s.uncommitted[i].lock().unwrap();
|
let l = s.uncommitted[i].lock();
|
||||||
raw::insert_recording(
|
raw::insert_recording(
|
||||||
&tx,
|
&tx,
|
||||||
o,
|
o,
|
||||||
@ -1141,7 +1142,7 @@ impl LockedDatabase {
|
|||||||
let u = s.uncommitted.pop_front().unwrap();
|
let u = s.uncommitted.pop_front().unwrap();
|
||||||
log.added
|
log.added
|
||||||
.push(CompositeId::new(stream_id, s.cum_recordings));
|
.push(CompositeId::new(stream_id, s.cum_recordings));
|
||||||
let l = u.lock().unwrap();
|
let l = u.lock();
|
||||||
s.cum_recordings += 1;
|
s.cum_recordings += 1;
|
||||||
let wall_dur = recording::Duration(l.wall_duration_90k.into());
|
let wall_dur = recording::Duration(l.wall_duration_90k.into());
|
||||||
let media_dur = recording::Duration(l.media_duration_90k.into());
|
let media_dur = recording::Duration(l.media_duration_90k.into());
|
||||||
@ -1310,7 +1311,7 @@ impl LockedDatabase {
|
|||||||
raw::list_recordings_by_time(&self.conn, stream_id, desired_time.clone(), f)?;
|
raw::list_recordings_by_time(&self.conn, stream_id, desired_time.clone(), f)?;
|
||||||
for (i, u) in s.uncommitted.iter().enumerate() {
|
for (i, u) in s.uncommitted.iter().enumerate() {
|
||||||
let row = {
|
let row = {
|
||||||
let l = u.lock().unwrap();
|
let l = u.lock();
|
||||||
if l.video_samples > 0 {
|
if l.video_samples > 0 {
|
||||||
let end = l.start + recording::Duration(l.wall_duration_90k as i64);
|
let end = l.start + recording::Duration(l.wall_duration_90k as i64);
|
||||||
if l.start > desired_time.end || end < desired_time.start {
|
if l.start > desired_time.end || end < desired_time.start {
|
||||||
@ -1351,7 +1352,7 @@ impl LockedDatabase {
|
|||||||
);
|
);
|
||||||
for i in start..end {
|
for i in start..end {
|
||||||
let row = {
|
let row = {
|
||||||
let l = s.uncommitted[i].lock().unwrap();
|
let l = s.uncommitted[i].lock();
|
||||||
if l.video_samples > 0 {
|
if l.video_samples > 0 {
|
||||||
l.to_list_row(
|
l.to_list_row(
|
||||||
CompositeId::new(stream_id, s.cum_recordings + i as i32),
|
CompositeId::new(stream_id, s.cum_recordings + i as i32),
|
||||||
@ -1489,7 +1490,7 @@ impl LockedDatabase {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let l = s.uncommitted[i as usize].lock().unwrap();
|
let l = s.uncommitted[i as usize].lock();
|
||||||
return f(&RecordingPlayback {
|
return f(&RecordingPlayback {
|
||||||
video_index: &l.video_index,
|
video_index: &l.video_index,
|
||||||
});
|
});
|
||||||
@ -2319,7 +2320,7 @@ impl<C: Clocks + Clone> Drop for Database<C> {
|
|||||||
return; // don't flush while panicking.
|
return; // don't flush while panicking.
|
||||||
}
|
}
|
||||||
if let Some(m) = self.db.take() {
|
if let Some(m) = self.db.take() {
|
||||||
if let Err(e) = m.into_inner().unwrap().flush(&self.clocks, "drop") {
|
if let Err(e) = m.into_inner().flush(&self.clocks, "drop") {
|
||||||
error!(err = %e.chain(), "final database flush failed");
|
error!(err = %e.chain(), "final database flush failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2418,7 +2419,7 @@ impl<C: Clocks + Clone> Database<C> {
|
|||||||
/// operations.
|
/// operations.
|
||||||
pub fn lock(&self) -> DatabaseGuard<C> {
|
pub fn lock(&self) -> DatabaseGuard<C> {
|
||||||
let timer = clock::TimerGuard::new(&self.clocks, acquisition);
|
let timer = clock::TimerGuard::new(&self.clocks, acquisition);
|
||||||
let db = self.db.as_ref().unwrap().lock().unwrap();
|
let db = self.db.as_ref().unwrap().lock();
|
||||||
drop(timer);
|
drop(timer);
|
||||||
let _timer = clock::TimerGuard::<C, &'static str, fn() -> &'static str>::new(
|
let _timer = clock::TimerGuard::<C, &'static str, fn() -> &'static str>::new(
|
||||||
&self.clocks,
|
&self.clocks,
|
||||||
@ -2435,7 +2436,7 @@ impl<C: Clocks + Clone> Database<C> {
|
|||||||
/// This allows verification that a newly opened database is in an acceptable state.
|
/// This allows verification that a newly opened database is in an acceptable state.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn close(mut self) -> rusqlite::Connection {
|
fn close(mut self) -> rusqlite::Connection {
|
||||||
self.db.take().unwrap().into_inner().unwrap().conn
|
self.db.take().unwrap().into_inner().conn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,13 @@ use crate::recording::{self, MAX_RECORDING_WALL_DURATION};
|
|||||||
use base::clock::{self, Clocks};
|
use base::clock::{self, Clocks};
|
||||||
use base::shutdown::ShutdownError;
|
use base::shutdown::ShutdownError;
|
||||||
use base::FastHashMap;
|
use base::FastHashMap;
|
||||||
|
use base::Mutex;
|
||||||
use base::{bail, err, Error};
|
use base::{bail, err, Error};
|
||||||
use std::cmp::{self, Ordering};
|
use std::cmp::{self, Ordering};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::sync::{mpsc, Arc};
|
use std::sync::{mpsc, Arc};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
@ -880,7 +880,7 @@ impl<F: FileWriter> InnerWriter<F> {
|
|||||||
db: &db::Database<C>,
|
db: &db::Database<C>,
|
||||||
stream_id: i32,
|
stream_id: i32,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut l = self.r.lock().unwrap();
|
let mut l = self.r.lock();
|
||||||
|
|
||||||
// design/time.md explains these time manipulations in detail.
|
// design/time.md explains these time manipulations in detail.
|
||||||
let prev_media_duration_90k = l.media_duration_90k;
|
let prev_media_duration_90k = l.media_duration_90k;
|
||||||
@ -969,7 +969,7 @@ impl<F: FileWriter> InnerWriter<F> {
|
|||||||
// This always ends a live segment.
|
// This always ends a live segment.
|
||||||
let wall_duration;
|
let wall_duration;
|
||||||
{
|
{
|
||||||
let mut l = self.r.lock().unwrap();
|
let mut l = self.r.lock();
|
||||||
l.flags = flags;
|
l.flags = flags;
|
||||||
l.local_time_delta = self.local_start - l.start;
|
l.local_time_delta = self.local_start - l.start;
|
||||||
l.sample_file_blake3 = Some(*blake3.as_bytes());
|
l.sample_file_blake3 = Some(*blake3.as_bytes());
|
||||||
@ -1012,11 +1012,11 @@ mod tests {
|
|||||||
use crate::recording;
|
use crate::recording;
|
||||||
use crate::testutil;
|
use crate::testutil;
|
||||||
use base::clock::{Clocks, SimulatedClocks};
|
use base::clock::{Clocks, SimulatedClocks};
|
||||||
|
use base::Mutex;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
|
||||||
use tracing::{trace, warn};
|
use tracing::{trace, warn};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -1039,10 +1039,10 @@ mod tests {
|
|||||||
MockDir(Arc::new(Mutex::new(VecDeque::new())))
|
MockDir(Arc::new(Mutex::new(VecDeque::new())))
|
||||||
}
|
}
|
||||||
fn expect(&self, action: MockDirAction) {
|
fn expect(&self, action: MockDirAction) {
|
||||||
self.0.lock().unwrap().push_back(action);
|
self.0.lock().push_back(action);
|
||||||
}
|
}
|
||||||
fn ensure_done(&self) {
|
fn ensure_done(&self) {
|
||||||
assert_eq!(self.0.lock().unwrap().len(), 0);
|
assert_eq!(self.0.lock().len(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1053,7 +1053,6 @@ mod tests {
|
|||||||
match self
|
match self
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.pop_front()
|
.pop_front()
|
||||||
.expect("got create_file with no expectation")
|
.expect("got create_file with no expectation")
|
||||||
{
|
{
|
||||||
@ -1068,7 +1067,6 @@ mod tests {
|
|||||||
match self
|
match self
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.pop_front()
|
.pop_front()
|
||||||
.expect("got sync with no expectation")
|
.expect("got sync with no expectation")
|
||||||
{
|
{
|
||||||
@ -1080,7 +1078,6 @@ mod tests {
|
|||||||
match self
|
match self
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.pop_front()
|
.pop_front()
|
||||||
.expect("got unlink_file with no expectation")
|
.expect("got unlink_file with no expectation")
|
||||||
{
|
{
|
||||||
@ -1096,7 +1093,7 @@ mod tests {
|
|||||||
impl Drop for MockDir {
|
impl Drop for MockDir {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if !::std::thread::panicking() {
|
if !::std::thread::panicking() {
|
||||||
assert_eq!(self.0.lock().unwrap().len(), 0);
|
assert_eq!(self.0.lock().len(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1116,10 +1113,10 @@ mod tests {
|
|||||||
MockFile(Arc::new(Mutex::new(VecDeque::new())))
|
MockFile(Arc::new(Mutex::new(VecDeque::new())))
|
||||||
}
|
}
|
||||||
fn expect(&self, action: MockFileAction) {
|
fn expect(&self, action: MockFileAction) {
|
||||||
self.0.lock().unwrap().push_back(action);
|
self.0.lock().push_back(action);
|
||||||
}
|
}
|
||||||
fn ensure_done(&self) {
|
fn ensure_done(&self) {
|
||||||
assert_eq!(self.0.lock().unwrap().len(), 0);
|
assert_eq!(self.0.lock().len(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1128,7 +1125,6 @@ mod tests {
|
|||||||
match self
|
match self
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.pop_front()
|
.pop_front()
|
||||||
.expect("got sync_all with no expectation")
|
.expect("got sync_all with no expectation")
|
||||||
{
|
{
|
||||||
@ -1140,7 +1136,6 @@ mod tests {
|
|||||||
match self
|
match self
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.pop_front()
|
.pop_front()
|
||||||
.expect("got write with no expectation")
|
.expect("got write with no expectation")
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use base::strutil::{decode_size, encode_size};
|
use base::strutil::{decode_size, encode_size};
|
||||||
use base::Error;
|
use base::Error;
|
||||||
|
use base::Mutex;
|
||||||
use cursive::traits::{Nameable, Resizable};
|
use cursive::traits::{Nameable, Resizable};
|
||||||
use cursive::view::Scrollable;
|
use cursive::view::Scrollable;
|
||||||
use cursive::Cursive;
|
use cursive::Cursive;
|
||||||
@ -11,7 +12,7 @@ use cursive::{views, With};
|
|||||||
use db::writer;
|
use db::writer;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use super::tab_complete::TabCompleteEditView;
|
use super::tab_complete::TabCompleteEditView;
|
||||||
@ -119,7 +120,7 @@ fn confirm_deletion(model: &Mutex<Model>, siv: &mut Cursive, to_delete: i64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn actually_delete(model: &Mutex<Model>, siv: &mut Cursive) {
|
fn actually_delete(model: &Mutex<Model>, siv: &mut Cursive) {
|
||||||
let model = model.lock().unwrap();
|
let model = model.lock();
|
||||||
let new_limits: Vec<_> = model
|
let new_limits: Vec<_> = model
|
||||||
.streams
|
.streams
|
||||||
.iter()
|
.iter()
|
||||||
@ -147,7 +148,7 @@ fn actually_delete(model: &Mutex<Model>, siv: &mut Cursive) {
|
|||||||
|
|
||||||
fn press_change(model: &Arc<Mutex<Model>>, siv: &mut Cursive) {
|
fn press_change(model: &Arc<Mutex<Model>>, siv: &mut Cursive) {
|
||||||
let to_delete = {
|
let to_delete = {
|
||||||
let l = model.lock().unwrap();
|
let l = model.lock();
|
||||||
if l.errors > 0 {
|
if l.errors > 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -185,7 +186,7 @@ fn press_change(model: &Arc<Mutex<Model>>, siv: &mut Cursive) {
|
|||||||
siv.add_layer(dialog);
|
siv.add_layer(dialog);
|
||||||
} else {
|
} else {
|
||||||
siv.pop_layer();
|
siv.pop_layer();
|
||||||
update_limits(&model.lock().unwrap(), siv);
|
update_limits(&model.lock(), siv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,13 +385,13 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
|
|||||||
.child(views::TextView::new("usage").fixed_width(BYTES_WIDTH))
|
.child(views::TextView::new("usage").fixed_width(BYTES_WIDTH))
|
||||||
.child(views::TextView::new("limit").fixed_width(BYTES_WIDTH)),
|
.child(views::TextView::new("limit").fixed_width(BYTES_WIDTH)),
|
||||||
);
|
);
|
||||||
let l = model.lock().unwrap();
|
let l = model.lock();
|
||||||
for (&id, stream) in &l.streams {
|
for (&id, stream) in &l.streams {
|
||||||
let mut record_cb = views::Checkbox::new();
|
let mut record_cb = views::Checkbox::new();
|
||||||
record_cb.set_checked(stream.record);
|
record_cb.set_checked(stream.record);
|
||||||
record_cb.set_on_change({
|
record_cb.set_on_change({
|
||||||
let model = model.clone();
|
let model = model.clone();
|
||||||
move |_siv, record| edit_record(&mut model.lock().unwrap(), id, record)
|
move |_siv, record| edit_record(&mut model.lock(), id, record)
|
||||||
});
|
});
|
||||||
list.add_child(
|
list.add_child(
|
||||||
&stream.label,
|
&stream.label,
|
||||||
@ -403,7 +404,7 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
|
|||||||
.on_edit({
|
.on_edit({
|
||||||
let model = model.clone();
|
let model = model.clone();
|
||||||
move |siv, content, _pos| {
|
move |siv, content, _pos| {
|
||||||
edit_limit(&mut model.lock().unwrap(), siv, id, content)
|
edit_limit(&mut model.lock(), siv, id, content)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_submit({
|
.on_submit({
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
||||||
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
|
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use base::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cursive::{
|
use cursive::{
|
||||||
direction::Direction,
|
direction::Direction,
|
||||||
@ -37,25 +38,25 @@ impl TabCompleteEditView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_content(&self) -> Arc<String> {
|
pub fn get_content(&self) -> Arc<String> {
|
||||||
self.edit_view.lock().unwrap().get_content()
|
self.edit_view.lock().get_content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for TabCompleteEditView {
|
impl View for TabCompleteEditView {
|
||||||
fn draw(&self, printer: &Printer) {
|
fn draw(&self, printer: &Printer) {
|
||||||
self.edit_view.lock().unwrap().draw(printer)
|
self.edit_view.lock().draw(printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
self.edit_view.lock().unwrap().layout(size)
|
self.edit_view.lock().layout(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
|
fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
|
||||||
self.edit_view.lock().unwrap().take_focus(source)
|
self.edit_view.lock().take_focus(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, event: Event) -> EventResult {
|
fn on_event(&mut self, event: Event) -> EventResult {
|
||||||
if !self.edit_view.lock().unwrap().is_enabled() {
|
if !self.edit_view.lock().is_enabled() {
|
||||||
return EventResult::Ignored;
|
return EventResult::Ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,12 +67,12 @@ impl View for TabCompleteEditView {
|
|||||||
EventResult::consumed()
|
EventResult::consumed()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.edit_view.lock().unwrap().on_event(event)
|
self.edit_view.lock().on_event(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn important_area(&self, view_size: Vec2) -> Rect {
|
fn important_area(&self, view_size: Vec2) -> Rect {
|
||||||
self.edit_view.lock().unwrap().important_area(view_size)
|
self.edit_view.lock().important_area(view_size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,10 +81,10 @@ fn tab_complete(
|
|||||||
tab_completer: TabCompleteFn,
|
tab_completer: TabCompleteFn,
|
||||||
autofill_one: bool,
|
autofill_one: bool,
|
||||||
) -> EventResult {
|
) -> EventResult {
|
||||||
let completions = tab_completer(edit_view.lock().unwrap().get_content().as_str());
|
let completions = tab_completer(edit_view.lock().get_content().as_str());
|
||||||
EventResult::with_cb_once(move |siv| match *completions {
|
EventResult::with_cb_once(move |siv| match *completions {
|
||||||
[] => {}
|
[] => {}
|
||||||
[ref completion] if autofill_one => edit_view.lock().unwrap().set_content(completion)(siv),
|
[ref completion] if autofill_one => edit_view.lock().set_content(completion)(siv),
|
||||||
[..] => {
|
[..] => {
|
||||||
siv.add_layer(TabCompletePopup {
|
siv.add_layer(TabCompletePopup {
|
||||||
popup: views::MenuPopup::new(Arc::new({
|
popup: views::MenuPopup::new(Arc::new({
|
||||||
@ -91,7 +92,7 @@ fn tab_complete(
|
|||||||
for completion in completions {
|
for completion in completions {
|
||||||
let edit_view = edit_view.clone();
|
let edit_view = edit_view.clone();
|
||||||
tree.add_leaf(completion.clone(), move |siv| {
|
tree.add_leaf(completion.clone(), move |siv| {
|
||||||
edit_view.lock().unwrap().set_content(&completion)(siv)
|
edit_view.lock().set_content(&completion)(siv)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -114,7 +115,7 @@ impl TabCompletePopup {
|
|||||||
let tab_completer = self.tab_completer.clone();
|
let tab_completer = self.tab_completer.clone();
|
||||||
EventResult::with_cb_once(move |s| {
|
EventResult::with_cb_once(move |s| {
|
||||||
s.pop_layer();
|
s.pop_layer();
|
||||||
edit_view.lock().unwrap().on_event(event).process(s);
|
edit_view.lock().on_event(event).process(s);
|
||||||
tab_complete(edit_view, tab_completer, false).process(s);
|
tab_complete(edit_view, tab_completer, false).process(s);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -288,12 +288,12 @@ where
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::stream::{self, Stream};
|
use crate::stream::{self, Stream};
|
||||||
use base::clock::{self, Clocks};
|
use base::clock::{self, Clocks};
|
||||||
|
use base::Mutex;
|
||||||
use base::{bail, Error};
|
use base::{bail, Error};
|
||||||
use db::{recording, testutil, CompositeId};
|
use db::{recording, testutil, CompositeId};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
struct ProxyingStream {
|
struct ProxyingStream {
|
||||||
@ -388,7 +388,7 @@ mod tests {
|
|||||||
_options: stream::Options,
|
_options: stream::Options,
|
||||||
) -> Result<Box<dyn stream::Stream>, Error> {
|
) -> Result<Box<dyn stream::Stream>, Error> {
|
||||||
assert_eq!(&url, &self.expected_url);
|
assert_eq!(&url, &self.expected_url);
|
||||||
let mut l = self.streams.lock().unwrap();
|
let mut l = self.streams.lock();
|
||||||
match l.pop() {
|
match l.pop() {
|
||||||
Some(stream) => {
|
Some(stream) => {
|
||||||
trace!("MockOpener returning next stream");
|
trace!("MockOpener returning next stream");
|
||||||
@ -396,7 +396,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
trace!("MockOpener shutting down");
|
trace!("MockOpener shutting down");
|
||||||
self.shutdown_tx.lock().unwrap().take();
|
self.shutdown_tx.lock().take();
|
||||||
bail!(Cancelled, msg("done"))
|
bail!(Cancelled, msg("done"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -478,7 +478,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
stream.run();
|
stream.run();
|
||||||
assert!(opener.streams.lock().unwrap().is_empty());
|
assert!(opener.streams.lock().is_empty());
|
||||||
db.syncer_channel.flush();
|
db.syncer_channel.flush();
|
||||||
let db = db.db.lock();
|
let db = db.db.lock();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user