Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify future module, add context object for execute #12

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ fn sync_work(input: String)-> u8 {
pub async fn a_future_that_has_blocking_sync_work() -> u8 {
// relies on application-specified strategy for translating execute into a future that won't
// block the current worker thread
vacation::execute(move || { sync_work("foo".to_string()) }, vacation::ChanceOfBlocking::High, "example.operation").await.unwrap()
vacation::execute(
move || { sync_work("foo".to_string()) },
vacation::ExecuteContext {
chance_of_blocking: vacation::ChanceOfBlocking::High,
namespace: "example.operation"
}
jlizen marked this conversation as resolved.
Show resolved Hide resolved
).await.unwrap()
}
```

Expand All @@ -50,15 +56,16 @@ want to enable the `future` feature flag:
vacation = { version = "0.1", features = ["future"] }
```

This enables the [`future::FutureBuilder`] api along with the two types of `Vacation` futures it can generate:
- [`future::OffloadFirst`] - delegate work to vacation, and then process the results into an inner future and poll the inner future to completion
- [`future::OffloadWithFuture`] - poll the inner future, while also using a callback to retrieve any vacation work and polling it alongisde the inner future
This enables the [`future::FutureBuilder`] api which a generates [`future::OffloadWith`] wrapper future. On poll,
this wrapper drives the inner future, while checking if there is work available to offload to vacation. If there is,
it drives that work instead, deferring further polling of the inner future until the offloaded work is complete.

## Usage - Application owners
Application authors will need to add this library as a a direct dependency in order to customize the execution strategy
beyond the default no-op.

Application authors can also call [`execute()`] if there are application-layer compute-heavy segments in futures.
Application authors can also call [`execute()`] if there are application-layer compute-heavy segments in your futures that you
want to delegate to vacation.

### Simple example

Expand All @@ -77,11 +84,17 @@ async fn main() {
vacation::install_tokio_strategy().unwrap();

// if wanting to delegate work to vacation:
let vacation_future = vacation::execute(|| {
// represents compute heavy work
std::thread::sleep(std::time::Duration::from_millis(500));
5
}, vacation::ChanceOfBlocking::High, "example.operation");
let vacation_future = vacation::execute(
|| {
// represents compute heavy work
std::thread::sleep(std::time::Duration::from_millis(500));
5
},
vacation::ExecuteContext {
chance_of_blocking: vacation::ChanceOfBlocking::High,
namespace: "example.operation"
}
jlizen marked this conversation as resolved.
Show resolved Hide resolved
);

assert_eq!(vacation_future.await.unwrap(), 5);
# }
Expand Down
21 changes: 6 additions & 15 deletions src/executor/custom.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
use std::future::Future;

use crate::{concurrency_limit::ConcurrencyLimit, error::Error, ChanceOfBlocking};
use crate::{concurrency_limit::ConcurrencyLimit, error::Error, ExecuteContext};

/// The input for the custom closure
pub struct CustomClosureInput {
/// the actual work to execute, your custom closure must run this
pub work: Box<dyn FnOnce() + Send + 'static>,
/// caller-specified likehood of blocking, for customizing strategies
pub chance_of_blocking: ChanceOfBlocking,
/// caller-specified operatino namespace, for customizing strategies
pub namespace: &'static str,
/// caller-specified metadata that allows fine tuning strategies
pub context: ExecuteContext,
}

impl std::fmt::Debug for CustomClosureInput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CustomClosureInput")
.field("chance_of_blocking", &self.chance_of_blocking)
.field("namespace", &self.namespace)
.field("context", &self.context)
.finish()
}
}
Expand Down Expand Up @@ -49,12 +46,7 @@ impl Custom {
// the compiler correctly is pointing out that the custom closure isn't guaranteed to call f.
// but, we leave that to the implementer to guarantee since we are limited by working with static signatures
#[allow(unused_variables)]
pub(crate) async fn execute<F, R>(
&self,
f: F,
chance_of_blocking: ChanceOfBlocking,
namespace: &'static str,
) -> Result<R, Error>
pub(crate) async fn execute<F, R>(&self, f: F, context: ExecuteContext) -> Result<R, Error>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
Expand All @@ -74,8 +66,7 @@ impl Custom {

let input = CustomClosureInput {
work: wrapped_input_closure,
chance_of_blocking,
namespace,
context,
};

Box::into_pin((self.closure)(input))
Expand Down
Loading