///|
/// Owner-scoped cancellation and generation. A scope is created by the task
/// owner (program runtime, service bridge, image owner, or GPU worker); a
/// completion is accepted only while its token still matches.
pub struct TaskScope {
priv mut generation : UInt64
priv mut cancelled : Bool
}
///|
pub fn TaskScope::new() -> TaskScope {
{ generation: 1UL, cancelled: false, }
}
///|
pub fn TaskScope::generation(self : TaskScope) -> UInt64 {
self.generation
}
///|
pub fn TaskScope::cancel(self : TaskScope) -> Unit {
if !self.cancelled {
self.cancelled = true
self.generation = self.generation + 1UL
}
}
///|
pub fn TaskScope::is_cancelled(self : TaskScope) -> Bool {
self.cancelled
}
///|
pub fn TaskScope::token(self : TaskScope) -> CancelToken {
{ scope_generation: self.generation, }
}
///|
/// A completion token captured when a task starts. `is_current` checks both
/// the cancelled flag and the generation, so a scope that was cancelled and
/// reused cannot accept an old completion.
pub struct CancelToken {
priv scope_generation : UInt64
}
///|
pub fn CancelToken::scope_generation(self : CancelToken) -> UInt64 {
self.scope_generation
}
///|
pub fn CancelToken::is_current(self : CancelToken, scope : TaskScope) -> Bool {
!scope.is_cancelled() && self.scope_generation == scope.generation()
}
///|
test "cancel token becomes stale after the scope is cancelled" {
let scope = TaskScope::new()
let token = scope.token()
assert_true(token.is_current(scope))
scope.cancel()
assert_true(scope.is_cancelled())
assert_false(token.is_current(scope))
assert_true(scope.token().scope_generation() > token.scope_generation())
}
///|
test "cancelling twice keeps one generation bump" {
let scope = TaskScope::new()
let generation = scope.generation()
scope.cancel()
scope.cancel()
inspect(scope.generation(), content="\{generation + 1UL}")
}