use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use futures::task::{FutureObj, Spawn, SpawnError};
use mountpoint_s3_crt_sys::*;
use thiserror::Error;
use crate::common::allocator::Allocator;
use crate::common::error::Error;
use crate::common::ref_count::{abort_shutdown_callback, new_shutdown_callback_options};
use crate::common::task_scheduler::{Task, TaskScheduler, TaskStatus};
use crate::io::futures::FutureSpawner;
use crate::io::io_library_init;
use crate::CrtError as _;
use crate::ResultExt;
#[derive(Debug)]
pub struct EventLoop {
pub(crate) inner: NonNull<aws_event_loop>,
_event_loop_group: EventLoopGroup,
}
unsafe impl Send for EventLoop {}
unsafe impl Sync for EventLoop {}
impl EventLoop {
fn schedule_task_future(&self, task: Task, when: u64) {
unsafe {
aws_event_loop_schedule_task_future(self.inner.as_ptr(), task.into_aws_task_ptr(), when);
}
}
fn current_clock_time(&self) -> Result<u64, Error> {
unsafe {
let mut time_nanos: u64 = 0;
aws_event_loop_current_clock_time(self.inner.as_ptr(), &mut time_nanos).ok_or_last_error()?;
Ok(time_nanos)
}
}
}
impl crate::private::Sealed for EventLoop {}
impl TaskScheduler for EventLoop {
fn schedule_task_now(&self, task: Task) -> Result<(), Error> {
unsafe {
aws_event_loop_schedule_task_now(self.inner.as_ptr(), task.into_aws_task_ptr());
}
Ok(())
}
}
impl Clone for EventLoop {
fn clone(&self) -> Self {
Self {
inner: self.inner,
_event_loop_group: self._event_loop_group.clone(),
}
}
}
#[derive(Debug)]
pub struct EventLoopGroup {
pub(crate) inner: NonNull<aws_event_loop_group>,
}
unsafe impl Send for EventLoopGroup {}
unsafe impl Sync for EventLoopGroup {}
impl EventLoopGroup {
pub fn new_default(
allocator: &Allocator,
max_threads: Option<u16>,
on_shutdown: impl FnOnce() + Send + 'static,
) -> Result<Self, Error> {
io_library_init(allocator);
let max_threads = max_threads.unwrap_or(0);
let shutdown_options = new_shutdown_callback_options(on_shutdown);
let inner = unsafe {
aws_event_loop_group_new_default(allocator.inner.as_ptr(), max_threads, &shutdown_options)
.ok_or_last_error()
.on_err(|| abort_shutdown_callback(shutdown_options))?
};
Ok(Self { inner })
}
pub fn get_next_loop(&self) -> Result<EventLoop, Error> {
unsafe {
let inner = aws_event_loop_group_get_next_loop(self.inner.as_ptr()).ok_or_last_error()?;
Ok(EventLoop {
inner,
_event_loop_group: self.clone(),
})
}
}
pub fn get_loop_count(&self) -> usize {
unsafe { aws_event_loop_group_get_loop_count(self.inner.as_ptr()) }
}
}
impl crate::private::Sealed for EventLoopGroup {}
impl TaskScheduler for EventLoopGroup {
fn schedule_task_now(&self, task: Task) -> Result<(), Error> {
let event_loop = self.get_next_loop()?;
event_loop.schedule_task_now(task)
}
}
impl Spawn for EventLoopGroup {
fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> {
let _handle = self.spawn_future(future);
Ok(())
}
}
impl Clone for EventLoopGroup {
fn clone(&self) -> Self {
let inner = unsafe { NonNull::new_unchecked(aws_event_loop_group_acquire(self.inner.as_ptr())) };
Self { inner }
}
}
impl Drop for EventLoopGroup {
fn drop(&mut self) {
unsafe {
aws_event_loop_group_release(self.inner.as_ptr());
}
}
}
#[derive(Debug)]
pub struct EventLoopTimer {
event_loop: EventLoop,
duration: Duration,
state: Arc<AtomicU8>,
}
impl EventLoopTimer {
const TIMER_UNSCHEDULED: u8 = 0;
const TIMER_RUNNING: u8 = 1;
const TIMER_DONE: u8 = 2;
const TIMER_CANCELED: u8 = 3;
pub fn new(event_loop: &EventLoop, duration: Duration) -> Self {
Self {
event_loop: event_loop.clone(),
duration,
state: Arc::new(AtomicU8::new(Self::TIMER_UNSCHEDULED)),
}
}
}
impl Future for EventLoopTimer {
type Output = Result<(), EventLoopTimerError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state.load(Ordering::SeqCst) {
Self::TIMER_DONE => return Poll::Ready(Ok(())),
Self::TIMER_CANCELED => return Poll::Ready(Err(EventLoopTimerError::Canceled)),
_ => {}
}
if self
.state
.compare_exchange(
Self::TIMER_UNSCHEDULED,
Self::TIMER_RUNNING,
Ordering::SeqCst,
Ordering::SeqCst,
)
.is_err()
{
return Poll::Pending;
}
let now = match self.event_loop.current_clock_time() {
Ok(now) => now,
Err(e) => return Poll::Ready(Err(e.into())),
};
let nanos: u64 = self
.duration
.as_nanos()
.try_into()
.expect("cannot set a timer beyond 2^64 nanoseconds");
let waker = cx.waker().clone();
let state = self.state.clone();
self.event_loop.schedule_task_future(
Task::init(
&Allocator::default(),
move |status| {
let new_state = match status {
TaskStatus::RunReady => Self::TIMER_DONE,
TaskStatus::Canceled => Self::TIMER_CANCELED,
};
state.store(new_state, Ordering::SeqCst);
waker.wake()
},
"event_loop_timer",
),
now + nanos,
);
Poll::Pending
}
}
#[derive(Error, Debug)]
pub enum EventLoopTimerError {
#[error("The timer was cancelled")]
Canceled,
#[error("Internal CRT error: {0}")]
InternalError(#[from] crate::common::error::Error),
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::allocator::Allocator;
use crate::io::futures::FutureSpawner;
use futures::executor::block_on;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{mpsc, Arc};
use std::time::Duration;
const RECV_TIMEOUT: Duration = Duration::from_secs(5);
#[test]
fn test_schedule_tasks_default_el_group() {
const NUM_TASKS: i32 = 2_000;
let allocator = Allocator::default();
let el_group = EventLoopGroup::new_default(&allocator, None, || {}).unwrap();
let counter = Arc::new(AtomicI32::new(0));
let (tx, rx) = mpsc::channel::<i32>();
for id in 0..NUM_TASKS {
let el = el_group.get_next_loop().unwrap();
let counter = counter.clone();
let tx = tx.clone();
let task = Task::init(
&allocator,
move |_| {
counter.fetch_add(1, Ordering::SeqCst);
tx.send(id).unwrap();
},
"test",
);
el.schedule_task_now(task).expect("failed to schedule task");
}
for _ in 0..NUM_TASKS {
rx.recv_timeout(RECV_TIMEOUT).unwrap();
}
let final_result = counter.load(Ordering::SeqCst);
assert_eq!(final_result, NUM_TASKS);
}
#[test]
fn test_event_loop_group_shutdown() {
let allocator = Allocator::default();
let (tx, rx) = mpsc::channel();
{
let _el_group = EventLoopGroup::new_default(&allocator, None, move || tx.send(()).unwrap()).unwrap();
}
rx.recv_timeout(RECV_TIMEOUT).unwrap();
}
#[test]
fn test_event_loop_group_get_loop_count() {
let allocator = Allocator::default();
let el_group = EventLoopGroup::new_default(&allocator, None, || {}).unwrap();
assert!(el_group.get_loop_count() > 0);
}
#[test]
#[ignore]
fn test_new_event_loop_group_max_threads_fails() {
let allocator = Allocator::default();
EventLoopGroup::new_default(&allocator, Some(u16::MAX), || {})
.expect_err("creating an event loop group with u16::MAX threads should fail");
}
#[test]
fn test_timer_future() {
let allocator = Allocator::default();
let el_group = EventLoopGroup::new_default(&allocator, None, || {}).unwrap();
let event_loop = el_group.get_next_loop().unwrap();
let timer1 = EventLoopTimer::new(&event_loop, Duration::from_secs(1));
let timer2 = EventLoopTimer::new(&event_loop, Duration::from_secs(1));
let before_nanos = event_loop
.current_clock_time()
.expect("Failed to get current clock time");
let handle = el_group.spawn_future(async {
timer1.await.expect("timer1 failed");
timer2.await.expect("timer2 failed");
});
block_on(handle.into_future()).unwrap();
let after_nanos = event_loop
.current_clock_time()
.expect("Failed to get current clock time");
assert!(after_nanos > before_nanos + u64::try_from(Duration::from_secs(2).as_nanos()).unwrap());
}
}