use crate::config::ServiceConfig;
use crate::error::TimerError;
use crate::task::{
CallbackWrapper, CompletionReceiver, TaskCompletion, TaskHandle, TaskId, TimerTask,
TimerTaskWithCompletionNotifier,
};
use crate::wheel::Wheel;
use crate::{BatchHandle, TimerHandle};
use futures::future::BoxFuture;
use futures::stream::{FuturesUnordered, StreamExt};
use lite_sync::{
oneshot::lite::{Receiver, Sender, channel},
spsc,
};
use parking_lot::Mutex;
use std::collections::HashSet;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc;
use tokio::sync::watch;
use tokio::task::JoinHandle;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TaskNotification {
OneShot(TaskId),
Periodic(TaskId),
}
impl TaskNotification {
pub fn task_id(&self) -> TaskId {
match self {
TaskNotification::OneShot(id) => *id,
TaskNotification::Periodic(id) => *id,
}
}
pub fn is_oneshot(&self) -> bool {
matches!(self, TaskNotification::OneShot(_))
}
pub fn is_periodic(&self) -> bool {
matches!(self, TaskNotification::Periodic(_))
}
}
enum ServiceCommand {
AddBatchHandle {
task_ids: Vec<TaskId>,
completion_rxs: Vec<CompletionReceiver>,
},
AddTimerHandle {
task_id: TaskId,
completion_rx: CompletionReceiver,
},
}
pub struct TimerService {
command_tx: mpsc::Sender<ServiceCommand>,
timeout_rx: Option<spsc::Receiver<TaskNotification, 32>>,
actor_handle: Option<JoinHandle<()>>,
wheel: Arc<Mutex<Wheel>>,
shutdown_tx: Option<Sender<()>>,
registered_task_ids: Arc<Mutex<HashSet<TaskId>>>,
}
impl TimerService {
pub fn allocate_handle(&self) -> TaskHandle {
self.wheel.lock().allocate_handle()
}
pub fn allocate_handles(&self, count: usize) -> Vec<TaskHandle> {
self.wheel.lock().allocate_handles(count)
}
pub(crate) fn new(wheel: Arc<Mutex<Wheel>>, config: ServiceConfig) -> Self {
let (command_tx, command_rx) = mpsc::channel(config.command_channel_capacity.get());
let (timeout_tx, timeout_rx) = spsc::channel(config.timeout_channel_capacity);
let (shutdown_tx, shutdown_rx) = channel::<()>();
let registered_task_ids = Arc::new(Mutex::new(HashSet::new()));
let wheel_closed = wheel.lock().owner().subscribe_closed();
let actor = ServiceActor::new(
command_rx,
timeout_tx,
shutdown_rx,
wheel_closed,
Arc::clone(®istered_task_ids),
);
let actor_handle = tokio::spawn(async move {
actor.run().await;
});
Self {
command_tx,
timeout_rx: Some(timeout_rx),
actor_handle: Some(actor_handle),
wheel,
shutdown_tx: Some(shutdown_tx),
registered_task_ids,
}
}
pub fn take_receiver(&mut self) -> Option<spsc::Receiver<TaskNotification, 32>> {
self.timeout_rx.take()
}
#[inline]
pub fn cancel_task(&self, task_id: TaskId) -> Result<bool, TimerError> {
let mut wheel = self.wheel.lock();
wheel.cancel(task_id)
}
#[inline]
pub fn cancel_batch(&self, task_ids: &[TaskId]) -> Result<usize, TimerError> {
if task_ids.is_empty() {
return Ok(0);
}
let mut wheel = self.wheel.lock();
wheel.cancel_batch(task_ids)
}
#[inline]
pub fn postpone(
&self,
task_id: TaskId,
new_delay: Duration,
callback: Option<CallbackWrapper>,
) -> Result<bool, TimerError> {
let mut wheel = self.wheel.lock();
wheel.postpone_at(task_id, new_delay, callback)
}
#[inline]
pub fn postpone_batch(&self, updates: Vec<(TaskId, Duration)>) -> Result<usize, TimerError> {
if updates.is_empty() {
return Ok(0);
}
let mut wheel = self.wheel.lock();
wheel.postpone_batch_at(updates)
}
#[inline]
pub fn postpone_batch_with_callbacks(
&self,
updates: Vec<(TaskId, Duration, Option<CallbackWrapper>)>,
) -> Result<usize, TimerError> {
if updates.is_empty() {
return Ok(0);
}
let mut wheel = self.wheel.lock();
wheel.postpone_batch_with_callbacks_at(updates)
}
#[inline]
pub fn register(&self, handle: TaskHandle, task: TimerTask) -> Result<TimerHandle, TimerError> {
let task_id = handle.task_id();
let (task, completion_rx) = TimerTaskWithCompletionNotifier::from_timer_task(task);
{
let mut wheel_guard = self.wheel.lock();
wheel_guard.insert_at(handle, task)?;
}
self.registered_task_ids.lock().insert(task_id);
match self.command_tx.try_send(ServiceCommand::AddTimerHandle {
task_id,
completion_rx,
}) {
Ok(()) => Ok(TimerHandle::new(task_id, self.wheel.clone())),
Err(error) => {
drop(error);
self.rollback_registration(&[task_id]);
Err(TimerError::RegisterFailed)
}
}
}
#[inline]
pub fn register_batch(
&self,
handles: Vec<TaskHandle>,
tasks: Vec<TimerTask>,
) -> Result<BatchHandle, TimerError> {
if handles.len() != tasks.len() {
return Err(TimerError::BatchLengthMismatch {
handles_len: handles.len(),
tasks_len: tasks.len(),
});
}
let task_count = tasks.len();
let mut completion_rxs = Vec::with_capacity(task_count);
let mut task_ids = Vec::with_capacity(task_count);
let mut prepared_handles = Vec::with_capacity(task_count);
let mut prepared_tasks = Vec::with_capacity(task_count);
for (handle, task) in handles.into_iter().zip(tasks) {
let task_id = handle.task_id();
let (task, completion_rx) = TimerTaskWithCompletionNotifier::from_timer_task(task);
task_ids.push(task_id);
completion_rxs.push(completion_rx);
prepared_handles.push(handle);
prepared_tasks.push(task);
}
{
let mut wheel_guard = self.wheel.lock();
wheel_guard.insert_batch_at(prepared_handles, prepared_tasks)?;
}
self.registered_task_ids
.lock()
.extend(task_ids.iter().copied());
match self.command_tx.try_send(ServiceCommand::AddBatchHandle {
task_ids: task_ids.clone(),
completion_rxs,
}) {
Ok(()) => Ok(BatchHandle::new(task_ids, self.wheel.clone())),
Err(error) => {
drop(error);
self.rollback_registration(&task_ids);
Err(TimerError::RegisterFailed)
}
}
}
fn rollback_registration(&self, task_ids: &[TaskId]) {
if task_ids.is_empty() {
return;
}
self.registered_task_ids
.lock()
.retain(|task_id| !task_ids.contains(task_id));
let mut wheel = self.wheel.lock();
let _ = wheel.cancel_batch(task_ids);
}
fn cancel_registered_tasks(&self) {
let task_ids: Vec<_> = self.registered_task_ids.lock().drain().collect();
if task_ids.is_empty() {
return;
}
let mut wheel = self.wheel.lock();
let _ = wheel.cancel_batch(&task_ids);
}
pub async fn shutdown(mut self) {
self.cancel_registered_tasks();
if let Some(shutdown_tx) = self.shutdown_tx.take() {
let _ = shutdown_tx.send(());
}
if let Some(handle) = self.actor_handle.take() {
let _ = handle.await;
}
}
}
impl Drop for TimerService {
fn drop(&mut self) {
if let Some(handle) = self.actor_handle.take() {
handle.abort();
}
}
}
struct ServiceActor {
command_rx: mpsc::Receiver<ServiceCommand>,
timeout_tx: spsc::Sender<TaskNotification, 32>,
shutdown_rx: Receiver<()>,
wheel_closed: watch::Receiver<bool>,
registered_task_ids: Arc<Mutex<HashSet<TaskId>>>,
}
impl ServiceActor {
fn new(
command_rx: mpsc::Receiver<ServiceCommand>,
timeout_tx: spsc::Sender<TaskNotification, 32>,
shutdown_rx: Receiver<()>,
wheel_closed: watch::Receiver<bool>,
registered_task_ids: Arc<Mutex<HashSet<TaskId>>>,
) -> Self {
Self {
command_rx,
timeout_tx,
shutdown_rx,
wheel_closed,
registered_task_ids,
}
}
async fn send_notification(
timeout_tx: &spsc::Sender<TaskNotification, 32>,
notification: TaskNotification,
shutdown_rx: &mut Receiver<()>,
wheel_closed: &mut watch::Receiver<bool>,
) -> bool {
tokio::select! {
biased;
_ = &mut *shutdown_rx => false,
_ = wheel_closed.changed() => false,
result = timeout_tx.send(notification) => result.is_ok(),
}
}
async fn run(mut self) {
let mut oneshot_futures: FuturesUnordered<BoxFuture<'static, (TaskId, TaskCompletion)>> =
FuturesUnordered::new();
type PeriodicFutureResult = (
TaskId,
Option<TaskCompletion>,
crate::task::PeriodicCompletionReceiver,
);
let mut periodic_futures: FuturesUnordered<BoxFuture<'static, PeriodicFutureResult>> =
FuturesUnordered::new();
let timeout_tx = self.timeout_tx;
let mut shutdown_rx = self.shutdown_rx;
let mut wheel_closed = self.wheel_closed;
let registered_task_ids = self.registered_task_ids;
if *wheel_closed.borrow() {
return;
}
loop {
tokio::select! {
_ = &mut shutdown_rx => {
break;
}
_ = wheel_closed.changed() => {
break;
}
Some((task_id, completion)) = oneshot_futures.next() => {
registered_task_ids.lock().remove(&task_id);
if completion == TaskCompletion::Called
&& !Self::send_notification(
&timeout_tx,
TaskNotification::OneShot(task_id),
&mut shutdown_rx,
&mut wheel_closed,
)
.await
{
break;
}
}
Some((task_id, reason, mut receiver)) = periodic_futures.next() => {
if let Some(TaskCompletion::Called) = reason {
if !Self::send_notification(
&timeout_tx,
TaskNotification::Periodic(task_id),
&mut shutdown_rx,
&mut wheel_closed,
)
.await
{
break;
}
let future: BoxFuture<'static, PeriodicFutureResult> = Box::pin(async move {
let reason = receiver.recv().await;
(task_id, reason, receiver)
});
periodic_futures.push(future);
} else {
registered_task_ids.lock().remove(&task_id);
}
}
Some(cmd) = self.command_rx.recv() => {
match cmd {
ServiceCommand::AddBatchHandle { task_ids, completion_rxs } => {
for (task_id, rx) in task_ids.into_iter().zip(completion_rxs.into_iter()) {
match rx {
crate::task::CompletionReceiver::OneShot(receiver) => {
let future: BoxFuture<'static, (TaskId, TaskCompletion)> = Box::pin(async move {
let completion = receiver
.recv()
.await
.unwrap_or(TaskCompletion::Cancelled);
(task_id, completion)
});
oneshot_futures.push(future);
},
crate::task::CompletionReceiver::Periodic(mut receiver) => {
let future: BoxFuture<'static, PeriodicFutureResult> = Box::pin(async move {
let reason = receiver.recv().await;
(task_id, reason, receiver)
});
periodic_futures.push(future);
}
}
}
}
ServiceCommand::AddTimerHandle { task_id, completion_rx } => {
match completion_rx {
crate::task::CompletionReceiver::OneShot(receiver) => {
let future: BoxFuture<'static, (TaskId, TaskCompletion)> = Box::pin(async move {
let completion = receiver
.recv()
.await
.unwrap_or(TaskCompletion::Cancelled);
(task_id, completion)
});
oneshot_futures.push(future);
},
crate::task::CompletionReceiver::Periodic(mut receiver) => {
let future: BoxFuture<'static, PeriodicFutureResult> = Box::pin(async move {
let reason = receiver.recv().await;
(task_id, reason, receiver)
});
periodic_futures.push(future);
}
}
}
}
}
else => {
break;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{TimerTask, TimerWheel};
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
#[tokio::test]
async fn test_service_creation() {
let timer = TimerWheel::with_defaults();
let _service = timer.create_service(ServiceConfig::default());
}
#[tokio::test]
async fn test_add_timer_handle_and_receive_timeout() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let handle = service.allocate_handle();
let task_id = handle.task_id();
let task = TimerTask::new_oneshot(
Duration::from_millis(50),
Some(CallbackWrapper::new(|| async {})),
);
service.register(handle, task).unwrap();
let rx = service.take_receiver().unwrap();
let received_notification = tokio::time::timeout(Duration::from_millis(200), rx.recv())
.await
.expect("Should receive timeout notification")
.expect("Should receive Some value");
assert_eq!(received_notification, TaskNotification::OneShot(task_id));
}
#[tokio::test]
async fn test_shutdown() {
let timer = TimerWheel::with_defaults();
let service = timer.create_service(ServiceConfig::default());
let handle1 = service.allocate_handle();
let handle2 = service.allocate_handle();
let task1 = TimerTask::new_oneshot(Duration::from_secs(10), None);
let task2 = TimerTask::new_oneshot(Duration::from_secs(10), None);
service.register(handle1, task1).unwrap();
service.register(handle2, task2).unwrap();
service.shutdown().await;
}
#[tokio::test]
async fn test_shutdown_cancels_owned_tasks_and_closes_receiver() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let callback_count = Arc::new(AtomicU32::new(0));
let callback_count_clone = Arc::clone(&callback_count);
let handle = service
.register(
service.allocate_handle(),
TimerTask::new_oneshot(
Duration::from_secs(10),
Some(CallbackWrapper::new(move || {
let callback_count = Arc::clone(&callback_count_clone);
async move {
callback_count.fetch_add(1, Ordering::SeqCst);
}
})),
),
)
.unwrap();
let receiver = service.take_receiver().unwrap();
service.shutdown().await;
assert!(
tokio::time::timeout(Duration::from_millis(100), receiver.recv())
.await
.expect("service receiver should close")
.is_none()
);
assert!(!handle.cancel().unwrap());
assert_eq!(callback_count.load(Ordering::SeqCst), 0);
}
#[tokio::test]
async fn test_timer_shutdown_closes_bound_service() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let receiver = service.take_receiver().unwrap();
timer.shutdown().await;
assert!(
tokio::time::timeout(Duration::from_millis(100), receiver.recv())
.await
.expect("service receiver should close when its wheel closes")
.is_none()
);
assert!(matches!(
service.register(
service.allocate_handle(),
TimerTask::new_oneshot(Duration::from_secs(1), None),
),
Err(TimerError::Shutdown)
));
service.shutdown().await;
}
#[tokio::test(flavor = "current_thread")]
async fn test_shutdown_when_timeout_channel_is_full() {
let config = ServiceConfig::builder()
.timeout_channel_capacity(NonZeroUsize::new(1).unwrap())
.build();
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(config);
let first_handle = service.allocate_handle();
service
.register(
first_handle,
TimerTask::new_oneshot(Duration::from_millis(10), None),
)
.unwrap();
let second_handle = service.allocate_handle();
service
.register(
second_handle,
TimerTask::new_oneshot(Duration::from_millis(20), None),
)
.unwrap();
let _receiver = service.take_receiver().unwrap();
tokio::time::sleep(Duration::from_millis(80)).await;
tokio::time::timeout(Duration::from_millis(100), service.shutdown())
.await
.expect("shutdown should not wait for a full timeout channel");
}
#[tokio::test]
async fn test_schedule_once_direct() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let counter = Arc::new(AtomicU32::new(0));
let counter_clone = Arc::clone(&counter);
let handle = service.allocate_handle();
let task_id = handle.task_id();
let task = TimerTask::new_oneshot(
Duration::from_millis(50),
Some(CallbackWrapper::new(move || {
let counter = Arc::clone(&counter_clone);
async move {
counter.fetch_add(1, Ordering::SeqCst);
}
})),
);
service.register(handle, task).unwrap();
let rx = service.take_receiver().unwrap();
let received_notification = tokio::time::timeout(Duration::from_millis(200), rx.recv())
.await
.expect("Should receive timeout notification")
.expect("Should receive Some value");
assert_eq!(received_notification, TaskNotification::OneShot(task_id));
tokio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_schedule_once_notify_direct() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let handle = service.allocate_handle();
let task_id = handle.task_id();
let task = TimerTask::new_oneshot(Duration::from_millis(50), None);
service.register(handle, task).unwrap();
let rx = service.take_receiver().unwrap();
let received_notification = tokio::time::timeout(Duration::from_millis(200), rx.recv())
.await
.expect("Should receive timeout notification")
.expect("Should receive Some value");
assert_eq!(received_notification, TaskNotification::OneShot(task_id));
}
#[tokio::test]
async fn test_task_timeout_cleans_up_task_sender() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let handle = service.allocate_handle();
let task_id = handle.task_id();
let task = TimerTask::new_oneshot(Duration::from_millis(50), None);
service.register(handle, task).unwrap();
let rx = service.take_receiver().unwrap();
let received_notification = tokio::time::timeout(Duration::from_millis(200), rx.recv())
.await
.expect("Should receive timeout notification")
.expect("Should receive Some value");
assert_eq!(received_notification, TaskNotification::OneShot(task_id));
tokio::time::sleep(Duration::from_millis(10)).await;
let cancelled = service.cancel_task(task_id).unwrap();
assert!(!cancelled, "Timed out task should not exist anymore");
}
#[tokio::test]
async fn test_take_receiver_twice() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let rx1 = service.take_receiver();
assert!(rx1.is_some(), "First take_receiver should return Some");
let rx2 = service.take_receiver();
assert!(rx2.is_none(), "Second take_receiver should return None");
}
#[tokio::test]
async fn test_concurrent_registration_single_service() {
let timer = TimerWheel::with_defaults();
let service = Arc::new(timer.create_service(ServiceConfig::default()));
let mut tasks = Vec::new();
for _ in 0..10 {
let service_clone = Arc::clone(&service);
tasks.push(tokio::spawn(async move {
for _ in 0..20 {
let handle = service_clone.allocate_handle();
let task = TimerTask::new_oneshot(Duration::from_millis(500), None);
service_clone
.register(handle, task)
.expect("register should succeed");
}
}));
}
for task in tasks {
task.await.unwrap();
}
}
#[tokio::test(flavor = "current_thread")]
async fn test_register_failure_rolls_back_single_task_when_channel_is_full() {
let config = ServiceConfig::builder()
.command_channel_capacity(NonZeroUsize::new(1).unwrap())
.build();
let timer = TimerWheel::with_defaults();
let service = timer.create_service(config);
let first_handle = service.allocate_handle();
let first_task_id = first_handle.task_id();
service
.register(
first_handle,
TimerTask::new_oneshot(Duration::from_secs(10), None),
)
.unwrap();
let failed_handle = service.allocate_handle();
let failed_task_id = failed_handle.task_id();
let callback_count = Arc::new(AtomicU32::new(0));
let callback_count_clone = Arc::clone(&callback_count);
let result = service.register(
failed_handle,
TimerTask::new_periodic(
Duration::from_millis(10),
Duration::from_millis(10),
Some(CallbackWrapper::new(move || {
let callback_count = Arc::clone(&callback_count_clone);
async move {
callback_count.fetch_add(1, Ordering::SeqCst);
}
})),
None,
)
.unwrap(),
);
assert!(matches!(result, Err(TimerError::RegisterFailed)));
assert!(!service.cancel_task(failed_task_id).unwrap());
assert!(service.cancel_task(first_task_id).unwrap());
assert!(service.wheel.lock().is_empty());
tokio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(callback_count.load(Ordering::SeqCst), 0);
}
#[tokio::test(flavor = "current_thread")]
async fn test_register_batch_failure_rolls_back_all_tasks_when_channel_is_full() {
let config = ServiceConfig::builder()
.command_channel_capacity(NonZeroUsize::new(1).unwrap())
.build();
let timer = TimerWheel::with_defaults();
let service = timer.create_service(config);
let first_handle = service.allocate_handle();
let first_task_id = first_handle.task_id();
service
.register(
first_handle,
TimerTask::new_oneshot(Duration::from_secs(10), None),
)
.unwrap();
let failed_handles = service.allocate_handles(2);
let failed_task_ids: Vec<_> = failed_handles
.iter()
.map(|handle| handle.task_id())
.collect();
let failed_tasks = vec![
TimerTask::new_oneshot(Duration::from_secs(10), None),
TimerTask::new_oneshot(Duration::from_secs(10), None),
];
let result = service.register_batch(failed_handles, failed_tasks);
assert!(matches!(result, Err(TimerError::RegisterFailed)));
assert_eq!(service.cancel_batch(&failed_task_ids).unwrap(), 0);
assert!(service.cancel_task(first_task_id).unwrap());
assert!(service.wheel.lock().is_empty());
}
#[tokio::test]
async fn test_register_failure_rolls_back_when_actor_is_closed() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let actor_handle = service.actor_handle.take().unwrap();
actor_handle.abort();
let _ = actor_handle.await;
let handle = service.allocate_handle();
let task_id = handle.task_id();
let result = service.register(
handle,
TimerTask::new_oneshot(Duration::from_secs(10), None),
);
assert!(matches!(result, Err(TimerError::RegisterFailed)));
assert!(!service.cancel_task(task_id).unwrap());
assert!(service.wheel.lock().is_empty());
}
#[tokio::test]
async fn test_register_batch_failure_rolls_back_when_actor_is_closed() {
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
let actor_handle = service.actor_handle.take().unwrap();
actor_handle.abort();
let _ = actor_handle.await;
let handles = service.allocate_handles(2);
let task_ids: Vec<_> = handles.iter().map(|handle| handle.task_id()).collect();
let tasks = vec![
TimerTask::new_oneshot(Duration::from_secs(10), None),
TimerTask::new_oneshot(Duration::from_secs(10), None),
];
let result = service.register_batch(handles, tasks);
assert!(matches!(result, Err(TimerError::RegisterFailed)));
assert_eq!(service.cancel_batch(&task_ids).unwrap(), 0);
assert!(service.wheel.lock().is_empty());
}
}