Repository: qdrant/qdrant
File: lib/trififo/src/seqlock.rs (Rust)

Code before:
///
/// ```
/// use trififo::seqlock::SeqLock;
///
/// fn main() {
///
///     let shared_resource = 666;
///
///     let (reader, writer) = SeqLock::new_reader_writer(shared_resource);
///
///     let reader_2 = reader.clone(); // This can be cheaply copied, pointing to the same resource.
///
///     std::thread::spawn(move || {
///         let value = reader_2.read(|value| *value);
///         println!("Value: {}", value);
///     });
///
///     // writer can be sent to a thread, but can't be shared between them
///     std::thread::spawn(move || {
///         unsafe {
///             writer.write(|value| {
///                 *value += 10;
///             })
///         };
///     });
/// }
/// ```
pub struct SeqLock<T> {
    seq: AtomicUsize,
    inner: UnsafeCell<T>,
}

impl<T> SeqLock<T> {
    fn new(v: T) -> Self {
        SeqLock {
            seq: AtomicUsize::new(0),
            inner: UnsafeCell::new(v),
        }
    }

    /// Wrap the value in a seqlock.
    ///
    /// Returns a reader which is Sync and Send; and a writer, which is only Send.
    ///
    /// The reader can be cloned and is safe to use between threads
    ///
    /// The writer should only have a single thread using it.
    pub fn new_reader_writer(v: T) -> (SeqLockReader<T>, SeqLockWriter<T>) {
        let lock = Arc::new(SeqLock::new(v));
        let reader = SeqLockReader { lock: lock.clone() };
        let writer = SeqLockWriter { lock: lock.clone() };

Review comment (suggestion):
We can add a new unsafe marker trait that is empty. We can use it to enforce all `T`s have to opt-in to using it. Maybe that would make us developers more aware of what types can be used with a structure like this. And it prevents us from dumping random types in here in the future if we don't read our own documentation well enough.

Like this:

```rust
unsafe impl MyTypeIsSafe for usize {}
```



Code after:
///
/// To opt-in to making this promise, the wrapped type must implement `SeqLockSafe`, which
/// means it is safe to use within this SeqLock.
///
/// ```
/// use trififo::seqlock::{SeqLockSafe, SeqLock};
///
/// let shared_resource = 666;
///
/// let (reader, mut writer) = SeqLock::new_reader_writer(shared_resource);
///
/// let reader_2 = reader.clone(); // This can be cheaply copied, pointing to the same resource.
///
/// std::thread::spawn(move || {
///     let value = reader_2.read(|value| *value);
///     println!("Value: {}", value);
/// });
///
/// // writer can be sent to a thread, but can't be shared between them
/// std::thread::spawn(move || {
///    writer.write(|value| {
///        *value += 10;
///    });
/// });
/// ```
pub struct SeqLock<T> {
    seq: AtomicUsize,
    inner: UnsafeCell<T>,
}

/// Marker trait to promise that a type won't panic if it is read while it is being
/// concurrently mutated.
///
/// # SAFETY
///
/// The way a seqlock can prevent lock contention, is by allowing reader to access
/// the protected resource even during modification. Implementing this trait makes it
/// explicit that the wrapped type will never crash during a read, even if the inner data
/// is inconsistent. This contract includes, but is not limited to:
/// - Not reallocating, to prevent use-after-free.
/// - Checking bounds.
/// - Graceful handle of panics.
/// - ...
///
/// However, it is fine to let readers return garbage/torn reads, as it is the seqlock responsibility
/// to ensure this garbage is never considered valid; in such case the read is repeated.
///
/// The type must only expose functions/fields that satisfy these requirements. Anything that takes
/// `&self` is considered a read, and `&mut self` is considered a write.
pub unsafe trait SeqLockSafe {}