// Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use futures::future::BoxFuture; use futures::{FutureExt, ready}; use std::mem; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll, Waker}; use tokio::sync::{Mutex, OwnedMutexGuard}; #[derive(Default)] pub(crate) struct InnerWrapper { pub(crate) buffer: Arc>>, lock_state: LockState, } impl InnerWrapper { pub(crate) fn clone_buffer(&self) -> Arc>> { Arc::clone(&self.buffer) } pub(crate) fn cloned_buffer(&self) -> Self { assert!(matches!(self.lock_state, LockState::Idle)); InnerWrapper { buffer: self.clone_buffer(), lock_state: LockState::Idle, } } // NOTE: it's responsibility of the caller to ensure the guard is released and state transitions to idle! pub(crate) fn poll_guard_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { match &mut self.lock_state { LockState::Idle => { // 1. first try to obtain the guard without locking let Ok(guard) = self.buffer.clone().try_lock_owned() else { // 2. if that fails, create the future for obtaining it self.lock_state = LockState::TryingToLock(self.buffer.clone().lock_owned().boxed()); return Poll::Pending; }; // correctly transition to locked state and poll ourselves again self.lock_state = LockState::Locked(guard); cx.waker().wake_by_ref(); Poll::Ready(()) } LockState::TryingToLock(lock_fut) => { // see if the guard future has resolved, if so, transition to locked state and schedule for another poll let guard = ready!(lock_fut.as_mut().poll(cx)); self.lock_state = LockState::Locked(guard); cx.waker().wake_by_ref(); Poll::Pending } LockState::Locked(_) => Poll::Ready(()), } } pub(crate) fn guard(&mut self) -> Option<&mut OwnedMutexGuard>> { match &mut self.lock_state { LockState::Locked(guard) => Some(guard), _ => None, } } pub(crate) fn transition_to_idle(&mut self) { self.lock_state = LockState::Idle } } #[derive(Default)] pub(crate) enum LockState { // We haven’t started locking yet #[default] Idle, // Waiting for the mutex lock future to resolve TryingToLock(BoxFuture<'static, OwnedMutexGuard>>), // We hold the mutex guard Locked(OwnedMutexGuard>), } #[derive(Default)] pub struct ContentWrapper { pub(crate) content: T, pub(crate) waker: Option, } impl ContentWrapper { pub fn into_content(self) -> T { self.content } pub fn content(&self) -> &T { &self.content } pub(crate) fn take_content(&mut self) -> T where T: Default, { mem::take(&mut self.content) } } impl ContentWrapper> { pub fn take_at_most(&mut self, count: usize) -> Vec { if self.content.is_empty() { return Vec::new(); } if self.content.len() <= count { return self.take_content(); } let remaining = self.content.split_off(count); mem::replace(&mut self.content, remaining) } } impl LockState {} #[cfg(test)] mod tests { use super::*; #[test] fn take_at_most() { let mut empty: ContentWrapper> = ContentWrapper::default(); let mut non_empty: ContentWrapper> = ContentWrapper { content: vec![1, 2, 3, 4, 5], ..Default::default() }; assert_eq!(empty.take_at_most(0), Vec::::new()); assert_eq!(empty.take_at_most(1), Vec::::new()); assert_eq!(empty.take_at_most(42), Vec::::new()); assert_eq!(non_empty.take_at_most(0), Vec::::new()); assert_eq!(non_empty.take_at_most(1), vec![1]); assert_eq!(non_empty.take_at_most(3), vec![2, 3, 4]); assert_eq!(non_empty.take_at_most(42), vec![5]); let mut non_empty: ContentWrapper> = ContentWrapper { content: vec![1, 2, 3, 4, 5], ..Default::default() }; assert_eq!(non_empty.take_at_most(100), vec![1, 2, 3, 4, 5]); } }