This library provides abstractions for defining "long-running" computations.
The concepts in computation-process are often similar to "normal" asynchronous
code, but offer certain features that were never a priority in asynchronous
programming. The target audience are projects that implement CPU-intensive,
long-running computations but require granular control over the computation state
(e.g., because the computation is controlled from a user interface).
This is currently still very "experimental." I am releasing this on
crates.ioto allow some initial large-scale usage experiments, but please bear in mind that the API can change in the future.
Specific problems for which computation-process offers an opinionated design pattern:
- Cancellation: Each computation can be forcefully stopped using cooperative cancellation
(compatible with the
cancel-thiscrate). A canceled computation should remain in a consistent state from which it can be restarted even though some intermediate results may need to be recomputed. - Suspend/resume: A computation can define safe suspend points (based on polling). During these points, it is safe to serialize/interleave or otherwise "transfer" the computation without losing any progress.
- Interleaving and priority scheduling: Presence of suspend points allows us to safely interleave multiple computations on a single thread. Interleaving can use the inner state of each computation for priority-based scheduling.
- Serialization: The state of each computation is isolated into a dedicated object and can be therefore saved/restored during any of the suspend points.
CancellableandCompletable: Function returnsCancellable<T>if it can be interrupted by a cancellation token fromcancel-this. A function returnsCompletable<T>if it is cancellable, and it also returnsIncomplete::Suspendedwhenever it is safe to suspend.Computable<T>andAlgorithm<CTX, STATE, T>: An object implementsComputable<T>if it can be driven into completion by repeatedly callingtry_compute, which returnsCompletable<T>. AnAlgorithmis then an extension ofComputablethat can be configured (i.e., created) usingCTXandSTATEobjects, and it provides access to these objects during computation.- Similarly,
Generatable<T>andGenAlgorithm<CTX, STATE, T>are variants ofComputableandAlgorithmthat do not produce a single value, but rather a "stream" ofTvalues (like a cancellable/suspendable iterator). - A
ComputationandGeneratorare the default implementations of these interfaces. They delegate the actual computation toComputationStepandGeneratorStepwhile taking care of the remaining "boilerplate."
It is highly recommended to enable LTO when using computation-process because it allows better
inlining of our computation abstractions (at the expense of build time).
To enable LTO, add this to your Cargo.toml:
[profile.release]
lto = true
use computation_process::{Completable, Computable, Computation, ComputationStep, Incomplete, Stateful};
struct CountingStep;
impl ComputationStep<u32, u32, u32> for CountingStep {
fn step(target: &u32, count: &mut u32) -> Completable<u32> {
*count += 1;
if *count >= *target {
Ok(*count)
} else {
Err(Incomplete::Suspended)
}
}
}
fn example() {
let mut computation = Computation::<u32, u32, u32, CountingStep>::from_parts(5, 0);
assert_eq!(computation.compute().unwrap(), 5);
}use computation_process::{Completable, Generatable, Generator, GeneratorStep, Stateful};
struct RangeStep;
impl GeneratorStep<u32, u32, u32> for RangeStep {
fn step(max: &u32, current: &mut u32) -> Completable<Option<u32>> {
*current += 1;
if *current <= *max {
Ok(Some(*current))
} else {
Ok(None)
}
}
}
fn example() {
let mut generator = Generator::<u32, u32, u32, RangeStep>::from_parts(3, 0);
assert_eq!(generator.try_next(), Some(Ok(1)));
assert_eq!(generator.try_next(), Some(Ok(2)));
assert_eq!(generator.try_next(), Some(Ok(3)));
assert_eq!(generator.try_next(), None);
}