add a TimerGuard around db locks & ops

I moved the clocks member from LockedDatabase to Database to make this happen,
so the new DatabaseGuard (replacing a direct MutexGuard<LockedDatabase>) can
access it before acquiring the lock.

I also made the type of clock a type parameter of Database (and so several
other things throughout the system). This allowed me to drop the Arc<>, but
more importantly it means that the Clocks trait doesn't need to stay
object-safe. I plan to take advantage of that shortly.
This commit is contained in:
Scott Lamb
2018-03-23 13:31:23 -07:00
parent c0da1ef880
commit addeb9d2f6
10 changed files with 209 additions and 171 deletions

View File

@@ -125,7 +125,7 @@ struct Args {
pub fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
let (_db_dir, conn) = super::open_conn(&args.flag_db_dir, super::OpenMode::ReadWrite)?;
let clocks = Arc::new(clock::RealClocks{});
let clocks = clock::RealClocks {};
let db = Arc::new(db::Database::new(clocks, conn, true)?);
let mut siv = Cursive::new();

View File

@@ -66,7 +66,7 @@ pub fn run() -> Result<(), Error> {
pragma journal_mode = wal;
pragma page_size = 16384;
"#)?;
db::Database::init(&mut conn)?;
db::init(&mut conn)?;
info!("Database initialized.");
Ok(())
}

View File

@@ -96,7 +96,7 @@ struct Syncer {
pub fn run() -> Result<(), Error> {
let args: Args = super::parse_args(USAGE)?;
let clocks = Arc::new(clock::RealClocks{});
let clocks = clock::RealClocks {};
let (_db_dir, conn) = super::open_conn(
&args.flag_db_dir,
if args.flag_read_only { super::OpenMode::ReadOnly } else { super::OpenMode::ReadWrite })?;
@@ -123,7 +123,6 @@ pub fn run() -> Result<(), Error> {
let streams = l.streams_by_id().len();
let env = streamer::Environment {
db: &db,
clocks: clocks.clone(),
opener: &*stream::FFMPEG,
shutdown: &shutdown_streamers,
};

View File

@@ -1518,6 +1518,7 @@ impl http_serve::Entity for File {
#[cfg(test)]
mod tests {
use byteorder::{BigEndian, ByteOrder};
use clock::RealClocks;
use db::recording::{self, TIME_UNITS_PER_SEC};
use db::testutil::{self, TestDb, TEST_STREAM_ID};
use db::writer;
@@ -1745,7 +1746,7 @@ mod tests {
}
}
fn copy_mp4_to_db(db: &TestDb) {
fn copy_mp4_to_db(db: &TestDb<RealClocks>) {
let mut input =
stream::FFMPEG.open(stream::Source::File("src/testdata/clip.mp4")).unwrap();
@@ -1756,8 +1757,7 @@ mod tests {
extra_data.width, extra_data.height, extra_data.sample_entry,
extra_data.rfc6381_codec).unwrap();
let dir = db.dirs_by_stream_id.get(&TEST_STREAM_ID).unwrap();
let mut output = writer::Writer::new(&::base::clock::RealClocks{}, dir, &db.db,
&db.syncer_channel, TEST_STREAM_ID,
let mut output = writer::Writer::new(dir, &db.db, &db.syncer_channel, TEST_STREAM_ID,
video_sample_entry_id);
// end_pts is the pts of the end of the most recent frame (start + duration).
@@ -1785,7 +1785,7 @@ mod tests {
db.syncer_channel.flush();
}
pub fn create_mp4_from_db(tdb: &TestDb,
pub fn create_mp4_from_db(tdb: &TestDb<RealClocks>,
skip_90k: i32, shorten_90k: i32, include_subtitles: bool) -> File {
let mut builder = FileBuilder::new(Type::Normal);
builder.include_timestamp_subtitle_track(include_subtitles);
@@ -1858,7 +1858,7 @@ mod tests {
/// Makes a `.mp4` file which is only good for exercising the `Slice` logic for producing
/// sample tables that match the supplied encoder.
fn make_mp4_from_encoders(type_: Type, db: &TestDb,
fn make_mp4_from_encoders(type_: Type, db: &TestDb<RealClocks>,
mut recordings: Vec<db::RecordingToInsert>,
desired_range_90k: Range<i32>) -> File {
let mut builder = FileBuilder::new(type_);
@@ -1879,7 +1879,7 @@ mod tests {
#[test]
fn test_all_sync_frames() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
let mut r = db::RecordingToInsert::default();
let mut encoder = recording::SampleIndexEncoder::new();
for i in 1..6 {
@@ -1933,7 +1933,7 @@ mod tests {
#[test]
fn test_half_sync_frames() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
let mut r = db::RecordingToInsert::default();
let mut encoder = recording::SampleIndexEncoder::new();
for i in 1..6 {
@@ -1996,7 +1996,7 @@ mod tests {
#[test]
fn test_multi_segment() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
let mut encoders = Vec::new();
let mut r = db::RecordingToInsert::default();
let mut encoder = recording::SampleIndexEncoder::new();
@@ -2033,7 +2033,7 @@ mod tests {
#[test]
fn test_zero_duration_recording() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
let mut encoders = Vec::new();
let mut r = db::RecordingToInsert::default();
let mut encoder = recording::SampleIndexEncoder::new();
@@ -2059,7 +2059,7 @@ mod tests {
#[test]
fn test_media_segment() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
let mut r = db::RecordingToInsert::default();
let mut encoder = recording::SampleIndexEncoder::new();
for i in 1..6 {
@@ -2102,7 +2102,7 @@ mod tests {
#[test]
fn test_round_trip() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
copy_mp4_to_db(&db);
let mp4 = create_mp4_from_db(&db, 0, 0, false);
let new_filename = write_mp4(&mp4, db.tmpdir.path());
@@ -2123,7 +2123,7 @@ mod tests {
#[test]
fn test_round_trip_with_subtitles() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
copy_mp4_to_db(&db);
let mp4 = create_mp4_from_db(&db, 0, 0, true);
let new_filename = write_mp4(&mp4, db.tmpdir.path());
@@ -2144,7 +2144,7 @@ mod tests {
#[test]
fn test_round_trip_with_edit_list() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
copy_mp4_to_db(&db);
let mp4 = create_mp4_from_db(&db, 1, 0, false);
let new_filename = write_mp4(&mp4, db.tmpdir.path());
@@ -2165,7 +2165,7 @@ mod tests {
#[test]
fn test_round_trip_with_shorten() {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
copy_mp4_to_db(&db);
let mp4 = create_mp4_from_db(&db, 0, 1, false);
let new_filename = write_mp4(&mp4, db.tmpdir.path());
@@ -2214,7 +2214,7 @@ mod bench {
impl BenchServer {
fn new() -> BenchServer {
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
testutil::add_dummy_recordings_to_db(&db.db, 60);
let mp4 = create_mp4_from_db(&db, 0, 0, false);
let p = mp4.0.initial_sample_byte_pos;
@@ -2256,7 +2256,7 @@ mod bench {
#[bench]
fn build_index(b: &mut Bencher) {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
testutil::add_dummy_recordings_to_db(&db.db, 1);
let db = db.db.lock();
@@ -2307,7 +2307,7 @@ mod bench {
#[bench]
fn mp4_construction(b: &mut Bencher) {
testutil::init();
let db = TestDb::new();
let db = TestDb::new(RealClocks {});
testutil::add_dummy_recordings_to_db(&db.db, 60);
b.iter(|| {
create_mp4_from_db(&db, 0, 0, false);

View File

@@ -41,23 +41,21 @@ use time;
pub static ROTATE_INTERVAL_SEC: i64 = 60;
/// Common state that can be used by multiple `Streamer` instances.
pub struct Environment<'a, 'b, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
pub clocks: Arc<C>,
pub struct Environment<'a, 'b, C, S> where C: Clocks + Clone, S: 'a + stream::Stream {
pub opener: &'a stream::Opener<S>,
pub db: &'b Arc<Database>,
pub db: &'b Arc<Database<C>>,
pub shutdown: &'b Arc<AtomicBool>,
}
pub struct Streamer<'a, C, S> where C: Clocks, S: 'a + stream::Stream {
pub struct Streamer<'a, C, S> where C: Clocks + Clone, S: 'a + stream::Stream {
shutdown: Arc<AtomicBool>,
// State below is only used by the thread in Run.
rotate_offset_sec: i64,
rotate_interval_sec: i64,
db: Arc<Database>,
db: Arc<Database<C>>,
dir: Arc<dir::SampleFileDir>,
syncer_channel: writer::SyncerChannel<::std::fs::File>,
clocks: Arc<C>,
opener: &'a stream::Opener<S>,
stream_id: i32,
short_name: String,
@@ -65,7 +63,7 @@ pub struct Streamer<'a, C, S> where C: Clocks, S: 'a + stream::Stream {
redacted_url: String,
}
impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks + Clone, S: 'a + stream::Stream {
pub fn new<'b>(env: &Environment<'a, 'b, C, S>, dir: Arc<dir::SampleFileDir>,
syncer_channel: writer::SyncerChannel<::std::fs::File>,
stream_id: i32, c: &Camera, s: &Stream, rotate_offset_sec: i64,
@@ -77,7 +75,6 @@ impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
db: env.db.clone(),
dir,
syncer_channel: syncer_channel,
clocks: env.clocks.clone(),
opener: env.opener,
stream_id: stream_id,
short_name: format!("{}-{}", c.short_name, s.type_.as_str()),
@@ -93,7 +90,7 @@ impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
if let Err(e) = self.run_once() {
let sleep_time = time::Duration::seconds(1);
warn!("{}: sleeping for {:?} after error: {:?}", self.short_name, sleep_time, e);
self.clocks.sleep(sleep_time);
self.db.clocks().sleep(sleep_time);
}
}
info!("{}: shutting down", self.short_name);
@@ -101,16 +98,17 @@ impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
fn run_once(&mut self) -> Result<(), Error> {
info!("{}: Opening input: {}", self.short_name, self.redacted_url);
let clocks = self.db.clocks();
let mut stream = {
let _t = TimerGuard::new(&*self.clocks, || format!("opening {}", self.redacted_url));
let _t = TimerGuard::new(&clocks, || format!("opening {}", self.redacted_url));
self.opener.open(stream::Source::Rtsp(&self.url))?
};
let realtime_offset = self.clocks.realtime() - self.clocks.monotonic();
let realtime_offset = self.db.clocks().realtime() - clocks.monotonic();
// TODO: verify width/height.
let extra_data = stream.get_extra_data()?;
let video_sample_entry_id = {
let _t = TimerGuard::new(&*self.clocks, || "inserting video sample entry");
let _t = TimerGuard::new(&clocks, || "inserting video sample entry");
self.db.lock().insert_video_sample_entry(extra_data.width, extra_data.height,
extra_data.sample_entry,
extra_data.rfc6381_codec)?
@@ -121,11 +119,11 @@ impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
// Seconds since epoch at which to next rotate.
let mut rotate: Option<i64> = None;
let mut transformed = Vec::new();
let mut w = writer::Writer::new(&*self.clocks, &self.dir, &self.db, &self.syncer_channel,
self.stream_id, video_sample_entry_id);
let mut w = writer::Writer::new(&self.dir, &self.db, &self.syncer_channel, self.stream_id,
video_sample_entry_id);
while !self.shutdown.load(Ordering::SeqCst) {
let pkt = {
let _t = TimerGuard::new(&*self.clocks, || "getting next packet");
let _t = TimerGuard::new(&clocks, || "getting next packet");
stream.get_next()?
};
let pts = pkt.pts().ok_or_else(|| format_err!("packet with no pts"))?;
@@ -135,12 +133,12 @@ impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
debug!("{}: have first key frame", self.short_name);
seen_key_frame = true;
}
let frame_realtime = self.clocks.monotonic() + realtime_offset;
let frame_realtime = clocks.monotonic() + realtime_offset;
let local_time = recording::Time::new(frame_realtime);
rotate = if let Some(r) = rotate {
if frame_realtime.sec > r && pkt.is_key() {
trace!("{}: write on normal rotation", self.short_name);
let _t = TimerGuard::new(&*self.clocks, || "closing writer");
let _t = TimerGuard::new(&clocks, || "closing writer");
w.close(Some(pts));
None
} else {
@@ -159,7 +157,7 @@ impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
// usual. This ensures there's plenty of frame times to use when calculating
// the start time.
let r = r + if w.previously_opened()? { 0 } else { self.rotate_interval_sec };
let _t = TimerGuard::new(&*self.clocks, || "creating writer");
let _t = TimerGuard::new(&clocks, || "creating writer");
r
},
};
@@ -173,13 +171,13 @@ impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
} else {
orig_data
};
let _t = TimerGuard::new(&*self.clocks,
let _t = TimerGuard::new(&clocks,
|| format!("writing {} bytes", transformed_data.len()));
w.write(transformed_data, local_time, pts, pkt.is_key())?;
rotate = Some(r);
}
if rotate.is_some() {
let _t = TimerGuard::new(&*self.clocks, || "closing writer");
let _t = TimerGuard::new(&clocks, || "closing writer");
w.close(None);
}
Ok(())
@@ -322,7 +320,7 @@ mod tests {
fn basic() {
testutil::init();
// 2015-04-25 00:00:00 UTC
let clocks = Arc::new(clock::SimulatedClocks::new(time::Timespec::new(1429920000, 0)));
let clocks = clock::SimulatedClocks::new(time::Timespec::new(1429920000, 0));
clocks.sleep(time::Duration::seconds(86400)); // to 2015-04-26 00:00:00 UTC
let stream = stream::FFMPEG.open(stream::Source::File("src/testdata/clip.mp4")).unwrap();
@@ -335,9 +333,8 @@ mod tests {
streams: Mutex::new(vec![stream]),
shutdown: Arc::new(AtomicBool::new(false)),
};
let db = testutil::TestDb::new();
let env = super::Environment{
clocks: Arc::clone(&clocks),
let db = testutil::TestDb::new(clocks.clone());
let env = super::Environment {
opener: &opener,
db: &db.db,
shutdown: &opener.shutdown,