1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! Traits related to migration state storage.
//!
//! They are separate from [`migrate-core`] crate to guarantee more stability,
//! even when there are breaking changes to [`migrate-core`] crate.
//! This is because it serves as an interface for the considerable part of
//! [`migrate`] ecosystem, namely for different state storage and locks
//! implementations. We would like to avoid updating all of them, especially
//! if they don't reside in our repository.
//!
//! [`migrate`]: https://docs.rs/migrate
//! [`migrate-core`]: https://docs.rs/migrate-core
#![warn(missing_docs, unreachable_pub, rust_2018_idioms)]
// Makes rustc abort compilation if there are any unsafe blocks in the crate.
// Presence of this annotation is picked up by tools such as cargo-geiger
// and lets them ensure that there is indeed no unsafe code as opposed to
// something they couldn't detect (e.g. unsafe added via macro expansion, etc).
#![forbid(unsafe_code)]

use async_trait::async_trait;
use std::error::Error;

/// Type alias for the [`std::result::Result`] type used in the traits
pub type Result<T, E = Box<dyn Error + Send + Sync>> = std::result::Result<T, E>;

/// Client for the migration state storage.
///
/// State storage is basically a [`Vec`]`<`[`u8`]`>`.
/// Implementations of this trait should not make any assumptions about
/// the state shape (i.e. what the given [`Vec`]`<`[`u8`]`>` represents). The given
/// bytes are not even guaranteed to be valid UTF8.
#[async_trait]
pub trait StateClient {
    // FIXME: when fetch or update fail, we don't call unlock()
    // this might be fine, the implementation should handle this,
    // send heartbeats to verify the lock is not poisonned, or is this invariant
    // too complicated for implementations to implement and we might help with
    // this somehow on our high-level end?

    /// Return all bytes stored in the storage.
    ///
    /// If the storage wasn't initialized yet with `update()` call previously
    /// then it should return `Ok(vec![])` (empty vector), otherwise value
    /// stored with the most recent `update()` call should be returned
    async fn fetch(&mut self) -> Result<Vec<u8>>;

    /// Puts given bytes into the storage.
    ///
    /// It shouldn't make any assumptions about what these bytes represent,
    /// there are no guarantees about the byte pattern `migrate` uses to
    /// store the serialized migration state representation.
    ///
    /// For the first ever call to [`update()`](Self::update) it should
    /// initialize storage with the given bytes, and if [`fetch()`](Self::fetch)
    /// was called before intialization hapenned, then [`fetch()`](Self::fetch)
    /// should return `Ok(None)`.
    async fn update(&mut self, state: Vec<u8>) -> Result<()>;
}

/// Lock over a migration state storage.
///
/// It guards underlying migration state preventing concurrent access
/// from multiple threads and processes. Ideally, this should be a distributed
/// lock implementation.
///
/// The main method of this trait is [`StateLock::lock()`], see its docs for more
/// details.
#[async_trait]
pub trait StateLock {
    /// # General concept
    ///
    /// Acquires exclusive lock to migration state.
    ///
    /// Acquiring exclusive lock means that no other subjects
    /// (threads, current and other remote compute instance's processes)
    /// can access the state. Future returned by this method should
    /// be resolved only after the lock is unlocked (via [`StateGuard::unlock()`])
    /// if it is currently locked, or resolve right away if no other subject is
    /// holding the lock.
    ///
    /// This means that if some other subject is already holding a lock,
    /// we should wait for it to unlock it (by awaiting returned future to resolve).
    ///
    /// The lock has to be held until a call to [`StateGuard::unlock()`] on
    /// returned [`StateGuard`] implementation.
    ///
    /// Described behavior is expected when the `force` parameter is [`false`]
    ///
    /// # Boolean `force` parameter
    ///
    /// When the `force` boolean parameter is set to [`true`], method must
    /// acquire exclusive lock even if it currently acquired by some other subject
    /// and provide the access to underlying state storage regardless.
    ///
    /// This operation is dangerous, because it bypasses locking mechanism
    /// which may lead to concurrent state storage mutations.
    /// It exists to help circumvent situations where some subject has
    /// died without unlocking the lock, thus leaving it locked potentially
    /// forver.
    async fn lock(self: Box<Self>, force: bool) -> Result<Box<dyn StateGuard>>;
}

/// Object returned from [`StateLock::lock()`] that holds
/// state storage lock while alive, preventing concurrent access to it
/// from multiple threads and processes.
#[async_trait]
pub trait StateGuard {
    /// Returns the [`StateClient`] to be used to access the migration state
    /// while this [`StateGuard`] hold the lock.
    fn client(&mut self) -> &mut dyn StateClient;

    /// Unlocks currently held migration state lock allowing for
    /// other subjects to acquire it with [`StateLock::lock()`] once again
    async fn unlock(self: Box<Self>) -> Result<()>;
}

#[test]
fn object_safety() {
    fn _test(_: &dyn StateGuard, _: &dyn StateLock, _: &dyn StateClient) {}
}