#![cfg_attr(test, allow(clippy::unwrap_used, reason = "test scope"))]
use std::{collections::VecDeque, marker::PhantomData};
pub struct ChannelFusedIter<T, I, C> {
iter: I,
channel: C,
buffer_size: usize,
_phantom: PhantomData<T>,
}
pub trait FusableChannel<T>: Send + Sync {
fn send_batch(&self, items: Vec<T>) -> Result<(), Vec<T>>;
fn recv_batch(&self, max_items: usize) -> Vec<T>;
fn is_closed(&self) -> bool;
}
impl<T, I, C> ChannelFusedIter<T, I, C>
where
I: Iterator<Item = T>,
C: FusableChannel<T>,
T: Send,
{
pub fn new(iter: I, channel: C, buffer_size: usize) -> Self {
Self {
iter,
channel,
buffer_size: buffer_size.max(1),
_phantom: PhantomData,
}
}
pub fn process(self) -> Result<(), std::io::Error> {
let mut buffer = Vec::with_capacity(self.buffer_size);
for item in self.iter {
buffer.push(item);
if buffer.len() >= self.buffer_size {
match self.channel.send_batch(buffer) {
Ok(()) => buffer = Vec::with_capacity(self.buffer_size),
Err(_rejected) => {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"Channel closed",
));
}
}
}
}
if !buffer.is_empty() {
self.channel.send_batch(buffer).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::BrokenPipe, "Channel closed")
})?;
}
Ok(())
}
}
pub struct ChannelSplitter<T, I, C> {
iter: I,
channels: Vec<C>,
strategy: SplitStrategy,
_phantom: PhantomData<T>,
}
#[derive(Debug, Clone, Copy)]
pub enum SplitStrategy {
RoundRobin,
LoadBalanced,
Broadcast,
}
impl<T, I, C> ChannelSplitter<T, I, C>
where
I: Iterator<Item = T>,
C: FusableChannel<T>,
T: Send + Clone,
{
pub fn new(iter: I, strategy: SplitStrategy) -> Self {
Self {
iter,
channels: Vec::new(),
strategy,
_phantom: PhantomData,
}
}
pub fn add_channel(mut self, channel: C) -> Self {
self.channels.push(channel);
self
}
pub fn process(self) -> Result<(), std::io::Error> {
let num_channels = self.channels.len();
if num_channels == 0 {
return Ok(());
}
let mut channel_idx = 0;
let mut buffers: Vec<Vec<T>> = (0..num_channels).map(|_| Vec::with_capacity(64)).collect();
for item in self.iter {
match self.strategy {
SplitStrategy::RoundRobin => {
buffers[channel_idx].push(item);
channel_idx = (channel_idx + 1) % num_channels;
}
SplitStrategy::Broadcast => {
for buffer in &mut buffers {
buffer.push(item.clone());
}
}
SplitStrategy::LoadBalanced => {
let min_idx = buffers
.iter()
.enumerate()
.min_by_key(|(_, b)| b.len())
.map(|(i, _)| i)
.unwrap_or(0);
buffers[min_idx].push(item);
}
}
for (i, buffer) in buffers.iter_mut().enumerate() {
if buffer.len() >= 64 {
let items = std::mem::replace(buffer, Vec::with_capacity(64));
self.channels[i].send_batch(items).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::BrokenPipe, "Channel closed")
})?;
}
}
}
for (i, buffer) in buffers.into_iter().enumerate() {
if !buffer.is_empty() {
self.channels[i].send_batch(buffer).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::BrokenPipe, "Channel closed")
})?;
}
}
Ok(())
}
}
pub struct ChannelMerger<T, C> {
channels: Vec<C>,
strategy: MergeStrategy,
buffer: VecDeque<T>,
next_channel: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum MergeStrategy {
FairMerge,
Priority,
FirstAvailable,
}
impl<T, C> ChannelMerger<T, C>
where
C: FusableChannel<T>,
{
pub fn new(strategy: MergeStrategy) -> Self {
Self {
channels: Vec::new(),
strategy,
buffer: VecDeque::new(),
next_channel: 0,
}
}
pub fn add_channel(mut self, channel: C) -> Self {
self.channels.push(channel);
self
}
}
impl<T, C> Iterator for ChannelMerger<T, C>
where
C: FusableChannel<T>,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.buffer.pop_front() {
return Some(item);
}
match self.strategy {
MergeStrategy::FairMerge => {
let channel_count = self.channels.len();
if channel_count == 0 {
return None;
}
for _ in 0..channel_count {
let channel_index = self.next_channel % channel_count;
self.next_channel = (channel_index + 1) % channel_count;
let items = self.channels[channel_index].recv_batch(1);
if !items.is_empty() {
self.buffer.extend(items);
return self.buffer.pop_front();
}
}
}
MergeStrategy::FirstAvailable => {
for channel in &self.channels {
let items = channel.recv_batch(64);
if !items.is_empty() {
self.buffer.extend(items);
return self.buffer.pop_front();
}
}
}
MergeStrategy::Priority => {
for channel in &self.channels {
let items = channel.recv_batch(64);
if !items.is_empty() {
self.buffer.extend(items);
return self.buffer.pop_front();
}
}
}
}
None
}
}
pub trait ChannelFusionExt: Iterator + Sized {
fn fuse_channel<C>(
self,
channel: C,
buffer_size: usize,
) -> ChannelFusedIter<Self::Item, Self, C>
where
C: FusableChannel<Self::Item>,
Self::Item: Send,
{
ChannelFusedIter::new(self, channel, buffer_size)
}
fn split_channels<C>(self, strategy: SplitStrategy) -> ChannelSplitter<Self::Item, Self, C>
where
C: FusableChannel<Self::Item>,
Self::Item: Send + Clone,
{
ChannelSplitter::new(self, strategy)
}
}
impl<I: Iterator + Sized> ChannelFusionExt for I {}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
struct TestChannel<T> {
items: Arc<Mutex<Vec<T>>>,
}
impl<T> TestChannel<T> {
fn from_items(items: Vec<T>) -> Self {
Self {
items: Arc::new(Mutex::new(items)),
}
}
}
impl<T: Send> FusableChannel<T> for TestChannel<T> {
fn send_batch(&self, items: Vec<T>) -> Result<(), Vec<T>> {
self.items.lock().unwrap().extend(items);
Ok(())
}
fn recv_batch(&self, max_items: usize) -> Vec<T> {
let mut items = self.items.lock().unwrap();
let n = max_items.min(items.len());
items.drain(..n).collect()
}
fn is_closed(&self) -> bool {
false
}
}
#[test]
fn test_channel_fusion() {
let data = vec![1, 2, 3, 4, 5];
let channel = TestChannel {
items: Arc::new(Mutex::new(Vec::new())),
};
let items_ref = channel.items.clone();
data.into_iter().fuse_channel(channel, 2).process().unwrap();
let result = items_ref.lock().unwrap();
assert_eq!(*result, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_channel_splitter() {
let data = vec![1, 2, 3, 4, 5, 6];
let channel1 = TestChannel {
items: Arc::new(Mutex::new(Vec::new())),
};
let channel2 = TestChannel {
items: Arc::new(Mutex::new(Vec::new())),
};
let items1 = channel1.items.clone();
let items2 = channel2.items.clone();
data.into_iter()
.split_channels(SplitStrategy::RoundRobin)
.add_channel(channel1)
.add_channel(channel2)
.process()
.unwrap();
assert_eq!(*items1.lock().unwrap(), vec![1, 3, 5]);
assert_eq!(*items2.lock().unwrap(), vec![2, 4, 6]);
}
#[test]
fn test_channel_merger_fair_merge_uses_fifo_order() {
let channel1 = TestChannel::from_items(vec![1, 3]);
let channel2 = TestChannel::from_items(vec![2, 4]);
let merged = ChannelMerger::new(MergeStrategy::FairMerge)
.add_channel(channel1)
.add_channel(channel2)
.collect::<Vec<_>>();
assert_eq!(merged, vec![1, 2, 3, 4]);
}
#[test]
fn test_channel_splitter_broadcast_clones_to_every_channel() {
let data = vec![1, 2, 3];
let channel1 = TestChannel {
items: Arc::new(Mutex::new(Vec::new())),
};
let channel2 = TestChannel {
items: Arc::new(Mutex::new(Vec::new())),
};
let items1 = channel1.items.clone();
let items2 = channel2.items.clone();
data.into_iter()
.split_channels(SplitStrategy::Broadcast)
.add_channel(channel1)
.add_channel(channel2)
.process()
.unwrap();
assert_eq!(*items1.lock().unwrap(), vec![1, 2, 3]);
assert_eq!(*items2.lock().unwrap(), vec![1, 2, 3]);
}
}