use std::collections::VecDeque;
use std::sync::Arc;
use timely_bytes::arc::Bytes;
use super::bytes_exchange::QueueEntry;
pub type SpillPolicyFn = Arc<dyn Fn() -> (Box<dyn SpillPolicy>, Box<dyn SpillPolicy>) + Send + Sync>;
pub trait SpillPolicy: Send {
fn apply(&mut self, queue: &mut VecDeque<QueueEntry>);
}
pub trait BytesSpill: Send {
fn spill(&mut self, chunks: &mut Vec<Bytes>, handles: &mut Vec<Box<dyn BytesFetch>>);
}
pub trait BytesFetch: Send {
fn fetch(self: Box<Self>) -> Result<Vec<Bytes>, Box<dyn BytesFetch>>;
}
pub mod threshold {
use super::*;
pub struct Threshold {
strategy: Box<dyn BytesSpill>,
pub threshold_bytes: usize,
pub head_reserve_bytes: usize,
}
impl Threshold {
pub fn new(strategy: Box<dyn BytesSpill>) -> Self {
Threshold {
strategy,
threshold_bytes: 256 << 20, head_reserve_bytes: 64 << 20, }
}
}
impl SpillPolicy for Threshold {
fn apply(&mut self, queue: &mut VecDeque<QueueEntry>) {
let resident: usize = queue.iter().map(|e| match e {
QueueEntry::Bytes(b) => b.len(),
QueueEntry::Paged(_) => 0,
}).sum();
if resident <= self.head_reserve_bytes + self.threshold_bytes {
return;
}
let head_reserve = self.head_reserve_bytes;
let mut cumulative: usize = 0;
let last_index = queue.len().saturating_sub(1);
let mut target_indices: Vec<usize> = Vec::new();
let mut target_bytes: Vec<Bytes> = Vec::new();
for (i, entry) in queue.iter().enumerate() {
if i == last_index { break; }
match entry {
QueueEntry::Bytes(b) => {
if cumulative >= head_reserve {
target_indices.push(i);
target_bytes.push(b.clone());
}
cumulative += b.len();
}
QueueEntry::Paged(_) => {}
}
}
if target_bytes.is_empty() {
return;
}
let mut handles: Vec<Box<dyn BytesFetch>> = Vec::new();
self.strategy.spill(&mut target_bytes, &mut handles);
for (i, handle) in target_indices.into_iter().zip(handles) {
queue[i] = QueueEntry::Paged(handle);
}
}
}
}
pub mod prefetch {
use super::*;
pub struct PrefetchPolicy {
pub budget: usize,
}
impl PrefetchPolicy {
pub fn new(budget: usize) -> Self {
PrefetchPolicy { budget }
}
}
impl SpillPolicy for PrefetchPolicy {
fn apply(&mut self, queue: &mut VecDeque<QueueEntry>) {
let mut resident_head = 0;
let mut i = 0;
while i < queue.len() && resident_head < self.budget {
match &queue[i] {
QueueEntry::Bytes(b) => {
resident_head += b.len();
i += 1;
}
QueueEntry::Paged(_) => {
let entry = queue.remove(i).expect("index valid");
if let QueueEntry::Paged(h) = entry {
match h.fetch() {
Ok(fetched) => {
let n = fetched.len();
for (j, b) in fetched.into_iter().enumerate() {
resident_head += b.len();
queue.insert(i + j, QueueEntry::Bytes(b));
}
i += n;
}
Err(h) => {
queue.insert(i, QueueEntry::Paged(h));
break;
}
}
}
}
}
}
}
}
}
pub use threshold::Threshold;
pub use prefetch::PrefetchPolicy;
#[cfg(test)]
mod tests {
use super::*;
fn bytes_of(data: &[u8]) -> Bytes {
timely_bytes::arc::BytesMut::from(data.to_vec()).freeze()
}
struct MockStrategy;
struct MockHandle { data: Bytes }
impl BytesSpill for MockStrategy {
fn spill(&mut self, chunks: &mut Vec<Bytes>, handles: &mut Vec<Box<dyn BytesFetch>>) {
handles.extend(chunks.drain(..)
.map(|b| Box::new(MockHandle { data: b }) as Box<dyn BytesFetch>));
}
}
impl BytesFetch for MockHandle {
fn fetch(self: Box<Self>) -> Result<Vec<Bytes>, Box<dyn BytesFetch>> { Ok(vec![self.data]) }
}
#[test]
fn eager_policy_moves_middle_entries() {
struct EagerPolicy { strategy: Box<dyn BytesSpill> }
impl SpillPolicy for EagerPolicy {
fn apply(&mut self, queue: &mut VecDeque<QueueEntry>) {
let last = queue.len().saturating_sub(1);
let mut indices = Vec::new();
let mut bytes = Vec::new();
for (i, entry) in queue.iter().enumerate() {
if i == last { break; }
if let QueueEntry::Bytes(b) = entry {
indices.push(i);
bytes.push(b.clone());
}
}
if bytes.is_empty() { return; }
let mut handles = Vec::new();
self.strategy.spill(&mut bytes, &mut handles);
for (i, h) in indices.into_iter().zip(handles) {
queue[i] = QueueEntry::Paged(h);
}
}
}
let mut p = EagerPolicy { strategy: Box::new(MockStrategy) };
let mut queue: VecDeque<QueueEntry> = VecDeque::new();
for i in 0..4 {
queue.push_back(QueueEntry::Bytes(bytes_of(&[i as u8; 8])));
}
p.apply(&mut queue);
assert!(matches!(queue[0], QueueEntry::Paged(_)));
assert!(matches!(queue[1], QueueEntry::Paged(_)));
assert!(matches!(queue[2], QueueEntry::Paged(_)));
assert!(matches!(queue[3], QueueEntry::Bytes(_)));
}
#[test]
fn merge_queue_spill_roundtrip_mock() {
use super::super::bytes_exchange::{MergeQueue, BytesPush, BytesPull};
let head_reserve = 128;
let mut tp = Threshold::new(Box::new(MockStrategy));
tp.threshold_bytes = 512;
tp.head_reserve_bytes = head_reserve;
let writer_policy: Box<dyn SpillPolicy> = Box::new(tp);
let reader_policy: Box<dyn SpillPolicy> = Box::new(PrefetchPolicy::new(head_reserve));
let buzzer = crate::buzzer::Buzzer::default();
let (mut writer, mut reader) =
MergeQueue::new_pair(buzzer, Some(writer_policy), Some(reader_policy));
let mut expected: Vec<Vec<u8>> = Vec::new();
for i in 0..100 {
let data = vec![(i % 251) as u8; 64];
expected.push(data.clone());
writer.extend(Some(bytes_of(&data)));
}
let mut received: Vec<Bytes> = Vec::new();
loop {
let before = received.len();
reader.drain_into(&mut received);
if received.len() == before { break; }
}
let expected_flat: Vec<u8> = expected.into_iter().flatten().collect();
let received_flat: Vec<u8> = received.iter().flat_map(|b| b.iter().copied()).collect();
assert_eq!(expected_flat, received_flat);
}
}