Skip to content

Commit

Permalink
feat(simple): add no-default-bound feature
Browse files Browse the repository at this point in the history
For those who cannot easily implement `Default` (ex. a large struct),
a version of the simple dropper that relies on an inner `Option<T>`
can be more convenient to write.

This commit adds a feature flag `no-default-bound` that removes the
`Default` bound requirement.

Co-authored-by: Linken Quy Dinh <[email protected]>
  • Loading branch information
t3hmrman and beckend committed Jan 31, 2024
1 parent 7ff6cee commit 0699145
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 96 deletions.
1 change: 1 addition & 0 deletions crates/async-dropper-simple/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ A simple struct-wrapper (i.e. AsyncDropper<T>) based implementation of AsyncDrop
default = []
tokio = ["dep:tokio"]
async-std = ["dep:async-std"]
no-default-bound = []

[dependencies]
async-std = { workspace = true, optional = true }
Expand Down
8 changes: 8 additions & 0 deletions crates/async-dropper-simple/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@
- `async_dropper::derive` provides a trait called `AsyncDrop` and corresponding [derive macro][rust-derive-macro], which try to use `Default` and `PartialEq` to determine when to async drop.

The code in this crate powers `async_dropper::simple`. See the `async_dropper` crate for more details.

## Feature flags

| Flag | Description |
|--------------------|-------------------------------------------------------------------------------------|
| `tokio` | Use the [`tokio`][tokio] async runtime |
| `async-std` | use the [`async-std`][async-std] async runtime |
| `no-default-bound` | Avoid requiring the `Default` bound by wrapping the interior data in an `Option<T>` |
149 changes: 149 additions & 0 deletions crates/async-dropper-simple/src/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//! Implementation of simple AsyncDropper that does not require `Default`
//!
//! This implementation might be preferrable for people who cannot reasonably
//! implement `Default` for their struct, but have easily accessible `Eq`,`PartialEq`,
//! `Hash`, and/or `Clone` instances.
#![cfg(not(feature = "no-default-bound"))]

use std::time::Duration;

#[async_trait::async_trait]
pub trait AsyncDrop {
async fn async_drop(&mut self);
}

/// Wrapper struct that enables `async_drop()` behavior.
///
/// This version requires a `Default` implementation.
#[derive(Default)]
#[allow(dead_code)]
pub struct AsyncDropper<T: AsyncDrop + Default + Send + 'static> {
dropped: bool,
timeout: Option<Duration>,
inner: T,
}

impl<T: AsyncDrop + Default + Send + 'static> AsyncDropper<T> {
pub fn new(inner: T) -> Self {
Self {
dropped: false,
timeout: None,
inner,
}
}

pub fn with_timeout(timeout: Duration, inner: T) -> Self {
Self {
dropped: false,
timeout: Some(timeout),
inner,
}
}
}

#[cfg(all(not(feature = "tokio"), not(feature = "async-std")))]
impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
compile_error!(
"either 'async-std' or 'tokio' features must be enabled for the async-dropper crate"
)
}
}

#[cfg(all(feature = "async-std", feature = "tokio"))]
impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
compile_error!(
"'async-std' and 'tokio' features cannot both be specified for the async-dropper crate"
)
}
}

#[cfg(all(feature = "tokio", not(feature = "async-std")))]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
if !self.dropped {
let mut this = std::mem::take(self);
this.dropped = true;

// Create task
let timeout = self.timeout;
let task = tokio::spawn(async move {
this.inner.async_drop().await;
});

tokio::task::block_in_place(|| match timeout {
Some(d) => {
let _ = futures::executor::block_on(tokio::time::timeout(d, task));
}
None => {
let _ = futures::executor::block_on(task);
}
});
}
}
}

#[cfg(all(feature = "async-std", not(feature = "tokio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "async-std")))]
impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
if !self.dropped {
let mut this = std::mem::take(self);
this.dropped = true;

// Create task
let timeout = self.timeout.clone();
let task = async_std::task::spawn(async move {
this.inner.async_drop().await;
});

match timeout {
Some(d) => {
let _ = futures::executor::block_on(async_std::future::timeout(d, task));
}
None => {
let _ = futures::executor::block_on(task);
}
};
}
}
}

#[cfg(test)]
mod tests {
use crate::AsyncDropper;
use async_trait::async_trait;

/// Testing struct
#[derive(Default)]
struct Test(String);

#[async_trait]
impl AsyncDrop for Test {
async fn async_drop(&mut self) {
tokio::time::sleep(Duration::from_secs(1)).await;
}
}

/// Ensure that `Default`-bounded dropper works with tokio
#[cfg(tokio)]
#[tokio::test]
async fn tokio_works() -> Result<(), Box<dyn std::error::Error>> {
let start = std::time::Instant::now();
let wrapped_t = AsyncDropper::new(Test::new("no timeout".into()));
drop(wrapped_t);
assert!(start.elapsed(), "two seconds have passed since drop");
}

/// Ensure that `Default`-bounded dropper works with async-std
#[cfg(async-std)]
#[tokio::test]
async fn tokio_works() -> Result<(), Box<dyn std::error::Error>> {
let start = std::time::Instant::now();
let wrapped_t = AsyncDropper::new(Test::new("no timeout".into()));
drop(wrapped_t);
assert!(start.elapsed(), "two seconds have passed since drop");
}
}
102 changes: 6 additions & 96 deletions crates/async-dropper-simple/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,105 +2,15 @@
//! The code in this file was shamelessly stolen from
//! https://stackoverflow.com/questions/71541765/rust-async-drop
use std::time::Duration;

#[async_trait::async_trait]
pub trait AsyncDrop {
async fn async_drop(&mut self);
}

#[derive(Default)]
#[allow(dead_code)]
pub struct AsyncDropper<T: AsyncDrop + Default + Send + 'static> {
dropped: bool,
timeout: Option<Duration>,
inner: T,
}

impl<T: AsyncDrop + Default + Send + 'static> AsyncDropper<T> {
pub fn new(inner: T) -> Self {
Self {
dropped: false,
timeout: None,
inner,
}
}

pub fn with_timeout(timeout: Duration, inner: T) -> Self {
Self {
dropped: false,
timeout: Some(timeout),
inner,
}
}
}

#[cfg(all(not(feature = "tokio"), not(feature = "async-std")))]
impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
compile_error!(
"either 'async-std' or 'tokio' features must be enabled for the async-dropper crate"
)
}
}

#[cfg(all(feature = "async-std", feature = "tokio"))]
impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
compile_error!(
"'async-std' and 'tokio' features cannot both be specified for the async-dropper crate"
)
}
}

#[cfg(all(feature = "tokio", not(feature = "async-std")))]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
if !self.dropped {
let mut this = std::mem::take(self);
this.dropped = true;

// Create task
let timeout = self.timeout;
let task = tokio::spawn(async move {
this.inner.async_drop().await;
});

tokio::task::block_in_place(|| match timeout {
Some(d) => {
let _ = futures::executor::block_on(tokio::time::timeout(d, task));
}
None => {
let _ = futures::executor::block_on(task);
}
});
}
}
}

#[cfg(all(feature = "async-std", not(feature = "tokio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "async-std")))]
impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
if !self.dropped {
let mut this = std::mem::take(self);
this.dropped = true;
mod no_default_bound;
pub use no_default_bound::AsyncDropper;

// Create task
let timeout = self.timeout.clone();
let task = async_std::task::spawn(async move {
this.inner.async_drop().await;
});

match timeout {
Some(d) => {
let _ = futures::executor::block_on(async_std::future::timeout(d, task));
}
None => {
let _ = futures::executor::block_on(task);
}
};
}
}
}
#[cfg(not(feature = "no-default-bound"))]
mod default;
#[cfg(not(feature = "no-default-bound"))]
pub use default::AsyncDropper;
Loading

0 comments on commit 0699145

Please sign in to comment.