Use a flock(2)-based Mutex in Filesystem::lock().

- If this proves to be too expensive, we may need to look for a
    shared-memory based synchronization mechanism.  However, no
    existing one seemed suitable.

  - Fixes #81.
This commit is contained in:
Justus Winter 2019-03-13 13:03:33 +01:00
parent 37edb8e774
commit 0ac9fe32e7
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
4 changed files with 79 additions and 4 deletions

View File

@ -20,6 +20,7 @@ hex = "0.3"
base64 = "0.10"
pathdiff = "0.1"
idna = "0.1"
fs2 = "0.4"
[lib]
name = "hagrid_database"

View File

@ -1,4 +1,3 @@
use parking_lot::Mutex;
use std::convert::{TryInto, TryFrom};
use std::fs::{create_dir_all, read_link, remove_file, rename, File};
use std::io::{Read, Write};
@ -14,11 +13,11 @@ use pathdiff::diff_paths;
use {Database, Delete, Verify, Query};
use types::{Email, Fingerprint, KeyID};
use sync::MutexGuard;
use sync::{MutexGuard, FlockMutex};
use Result;
pub struct Filesystem {
update_lock: Mutex<()>,
update_lock: FlockMutex,
base: PathBuf,
base_by_keyid: PathBuf,
@ -83,7 +82,7 @@ impl Filesystem {
info!("Opened base dir '{}'", base.display());
Ok(Filesystem {
update_lock: Mutex::new(()),
update_lock: FlockMutex::new(&base)?,
base: base,
base_by_keyid: base_by_keyid,
base_by_fingerprint: base_by_fingerprint,

View File

@ -12,6 +12,7 @@ use std::str::FromStr;
extern crate failure;
use failure::Error;
use failure::Fallible as Result;
extern crate fs2;
extern crate idna;
#[macro_use] extern crate log;
extern crate parking_lot;

View File

@ -1,7 +1,13 @@
use std::fs::File;
use std::io;
use std::path::Path;
use fs2::FileExt;
use parking_lot;
pub enum MutexGuard<'a, T> {
ParkingLot(parking_lot::MutexGuard<'a, T>),
Flock(FlockMutexGuard<'a>),
}
impl<'a, T> From<parking_lot::MutexGuard<'a, T>> for MutexGuard<'a, T> {
@ -9,3 +15,71 @@ impl<'a, T> From<parking_lot::MutexGuard<'a, T>> for MutexGuard<'a, T> {
MutexGuard::ParkingLot(g)
}
}
impl<'a> From<FlockMutexGuard<'a>> for MutexGuard<'a, ()> {
fn from(g: FlockMutexGuard<'a>) -> Self {
MutexGuard::Flock(g)
}
}
/// A minimalistic flock-based mutex.
///
/// This just barely implements enough what we need from a mutex.
pub struct FlockMutex {
f: File,
}
impl FlockMutex {
pub fn new<P: AsRef<Path>>(p: P) -> io::Result<Self> {
Ok(Self {
f: File::open(p)?
})
}
pub fn lock(&self) -> FlockMutexGuard {
while let Err(e) = self.f.lock_exclusive() {
// According to flock(2), possible errors returned are:
//
// EBADF fd is not an open file descriptor.
//
// EINTR While waiting to acquire a lock, the call
// was interrupted by delivery of a signal
// caught by a handler; see signal(7).
//
// EINVAL operation is invalid.
//
// ENOLCK The kernel ran out of memory for allocating
// lock records.
//
// EWOULDBLOCK
// The file is locked and the LOCK_NB flag was
// selected.
//
// We entrust Rust's type system with keeping the file
// handle valid, therefore flock should not fail with
// EBADF. We use only valid operations, we don't use
// LOCK_NB, and we don't handle resource exhaustion.
//
// Therefore, only EINTR needs to be handled, which we do
// by retrying.
assert_eq!(e.kind(), io::ErrorKind::Interrupted);
}
FlockMutexGuard {
m: &self,
}
}
}
pub struct FlockMutexGuard<'a> {
m: &'a FlockMutex,
}
impl<'a> Drop for FlockMutexGuard<'a> {
fn drop(&mut self) {
while let Err(e) = self.m.f.unlock() {
// See above.
assert_eq!(e.kind(), io::ErrorKind::Interrupted);
}
}
}