Skip to content

Commit

Permalink
bundle execute() metadata into a context object, prune future module …
Browse files Browse the repository at this point in the history
…and shift to owned inner future in closure while offloading work
  • Loading branch information
jlizen committed Jan 10, 2025
1 parent 4b5b3f7 commit d67d11d
Show file tree
Hide file tree
Showing 15 changed files with 1,109 additions and 1,660 deletions.
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"
}
).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"
}
);
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

0 comments on commit d67d11d

Please sign in to comment.