use std::hash::Hash as StdHash;
use std::sync::Arc;
use indexmap::IndexMap;
use p2panda_core::{AnyHeader, Extensions, Hash, LogId, Operation};
use tokio::sync::Mutex;
#[derive(Debug)]
pub enum OooResult<'a, E> {
InOrder(&'a Operation<E>),
Ordered(Vec<Operation<E>>),
OutOfOrder,
Outdated,
}
#[derive(Clone, Debug)]
pub struct OooBuffer<L, E>
where
L: LogId,
E: Extensions,
{
buffer: Arc<Mutex<ChainRing<Hash, L, Operation<E>>>>,
}
impl<L, E> Default for OooBuffer<L, E>
where
L: LogId,
E: Extensions,
{
fn default() -> Self {
Self::new()
}
}
impl<L, E> OooBuffer<L, E>
where
L: LogId,
E: Extensions,
{
pub fn new() -> Self {
Self::with_capacity(128)
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
buffer: Arc::new(Mutex::new(ChainRing::with_capacity(capacity))),
}
}
pub async fn len(&self) -> usize {
self.buffer.lock().await.len()
}
pub async fn is_empty(&self) -> bool {
self.buffer.lock().await.is_empty()
}
pub async fn clear(&self) {
self.buffer.lock().await.clear();
}
pub async fn process<'a>(
&self,
operation: &'a Operation<E>,
latest_header: Option<&AnyHeader>,
log_id: &L,
prune_flag: bool,
) -> OooResult<'a, E> {
if prune_flag {
return OooResult::InOrder(operation);
}
match latest_header {
Some(latest_header) => {
if operation.header.seq_num < latest_header.seq_num {
OooResult::Outdated
} else if latest_header.seq_num == operation.header.seq_num.saturating_sub_signed(1)
{
OooResult::InOrder(operation)
} else {
self.push_and_pop_from(operation, latest_header.backlink, log_id)
.await
}
}
None => {
self.push_and_pop_from(operation, None, log_id).await
}
}
}
async fn push_and_pop_from<'a>(
&self,
operation: &'a Operation<E>,
expected_backlink: Option<Hash>,
log_id: &L,
) -> OooResult<'a, E> {
let mut buffer = self.buffer.lock().await;
buffer.push(
operation.hash,
operation.header.backlink,
log_id.clone(),
operation.clone(),
);
let result = buffer.pop_from(expected_backlink, log_id.clone());
if result.is_empty() {
OooResult::OutOfOrder
} else {
OooResult::Ordered(result)
}
}
}
#[derive(Debug)]
struct ChainRing<ID, L, T>
where
ID: Copy + Eq + StdHash,
L: Clone + Eq + StdHash,
{
buffer: IndexMap<ChainRingKey<ID, L>, ChainRingValue<ID, T>>,
capacity: usize,
}
#[derive(Debug, Eq, PartialEq, StdHash)]
struct ChainRingKey<ID, L>
where
ID: Copy + Eq + StdHash,
L: Clone + Eq + StdHash,
{
backlink: Option<ID>,
log_id: L,
}
#[derive(Debug)]
struct ChainRingValue<ID, T> {
id: ID,
item: T,
}
impl<ID, L, T> ChainRing<ID, L, T>
where
ID: Copy + Eq + StdHash,
L: Clone + Eq + StdHash,
{
pub fn with_capacity(capacity: usize) -> Self {
Self {
buffer: IndexMap::with_capacity(capacity),
capacity,
}
}
pub fn push(&mut self, id: ID, backlink: Option<ID>, log_id: L, item: T) {
if self.buffer.len() >= self.capacity {
self.buffer.pop();
}
self.buffer.insert(
ChainRingKey { backlink, log_id },
ChainRingValue { id, item },
);
}
pub fn pop_from(&mut self, backlink: Option<ID>, log_id: L) -> Vec<T> {
let mut result = Vec::new();
let mut next = ChainRingKey {
backlink,
log_id: log_id.clone(),
};
while let Some(ChainRingValue { id, item }) = self.buffer.shift_remove(&next) {
result.push(item);
next = ChainRingKey {
backlink: Some(id),
log_id: log_id.clone(),
};
}
result
}
pub fn len(&self) -> usize {
self.buffer.len()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn clear(&mut self) {
self.buffer.clear()
}
}
#[cfg(test)]
mod tests {
use super::ChainRing;
#[test]
fn push_and_pop_from() {
let mut ring = ChainRing::with_capacity(64);
ring.push(5, Some(4), "test-log", 5);
ring.push(6, Some(5), "test-log", 6);
ring.push(7, Some(6), "test-log", 7);
assert_eq!(ring.len(), 3);
assert!(ring.pop_from(Some(3), "test-log").is_empty());
ring.push(4, Some(3), "test-log", 4);
assert_eq!(ring.len(), 4);
assert_eq!(ring.pop_from(Some(3), "test-log"), vec![4, 5, 6, 7]);
assert_eq!(ring.len(), 0);
}
#[test]
fn log_from_beginning() {
let mut ring = ChainRing::with_capacity(64);
ring.push(0, None, "test-log", 0);
ring.push(1, Some(0), "test-log", 1);
ring.push(2, Some(1), "test-log", 2);
assert_eq!(ring.len(), 3);
assert_eq!(ring.pop_from(None, "test-log"), vec![0, 1, 2]);
}
#[test]
fn ring_buffer() {
let mut ring = ChainRing::with_capacity(2);
ring.push(1, Some(0), "test-log", 1);
ring.push(2, Some(1), "test-log", 2);
ring.push(3, Some(2), "test-log", 3);
assert_eq!(ring.len(), 2);
}
}