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

[concurrency] Task cancellation and deadlines #34459

Merged
merged 2 commits into from
Nov 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions stdlib/public/Concurrency/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
PartialAsyncTask.swift
Task.cpp
Task.swift
TaskCancellation.swift
_TimeTypes.swift
TaskAlloc.cpp
TaskStatus.cpp
Mutex.cpp
Expand Down
37 changes: 37 additions & 0 deletions stdlib/public/Concurrency/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public enum Task {
extension Task {

/// Returns the current task's priority.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
/* @instantaneous */
public static func currentPriority() async -> Priority {
fatalError("\(#function) not implemented yet.")
}
Expand Down Expand Up @@ -208,6 +212,31 @@ extension Task {
}
}

// ==== Voluntary Suspension -----------------------------------------------------
extension Task {

/// Suspend until a given point in time.
///
/// ### Cancellation
/// Does not check for cancellation and suspends the current context until the
/// given deadline.
///
/// - Parameter until: point in time until which to suspend.
public static func sleep(until: Deadline) async {
fatalError("\(#function) not implemented yet.")
}

/// Explicitly suspend the current task, potentially giving up execution actor
/// of current actor/task, allowing other tasks to execute.
///
/// This is not a perfect cure for starvation;
/// if the task is the highest-priority task in the system, it might go
/// immediately back to executing.
public static func yield() async {
fatalError("\(#function) not implemented yet.")
}
}

// ==== UnsafeContinuation -----------------------------------------------------

extension Task {
Expand Down Expand Up @@ -251,6 +280,10 @@ extension Task {
/// The operation functions must resume the continuation *exactly once*.
///
/// The continuation will not begin executing until the operation function returns.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
/* @instantaneous */
public static func withUnsafeContinuation<T>(
operation: (UnsafeContinuation<T>) -> Void
) async -> T {
Expand All @@ -260,6 +293,10 @@ extension Task {
/// The operation functions must resume the continuation *exactly once*.
///
/// The continuation will not begin executing until the operation function returns.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
/* @instantaneous */
public static func withUnsafeThrowingContinuation<T>(
operation: (UnsafeThrowingContinuation<T, Error>) -> Void
) async throws -> T {
Expand Down
199 changes: 199 additions & 0 deletions stdlib/public/Concurrency/TaskCancellation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
////===----------------------------------------------------------------------===//
////
//// This source file is part of the Swift.org open source project
////
//// Copyright (c) 2020 Apple Inc. and the Swift project authors
//// Licensed under Apache License v2.0 with Runtime Library Exception
////
//// See https://swift.org/LICENSE.txt for license information
//// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
////
////===----------------------------------------------------------------------===//

import Swift
@_implementationOnly import _SwiftConcurrencyShims

// ==== Task Cancellation ------------------------------------------------------

extension Task {

/// Returns `true` if the task is cancelled, and should stop executing.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
///
/// - SeeAlso: `checkCancellation()`
/* @instantaneous */
public static func isCancelled() async -> Bool {
ktoso marked this conversation as resolved.
Show resolved Hide resolved
// let task = __getTask() // TODO: pending internal API to get tasks
// task.isCancelled || task.deadline.isOverdue
fatalError("\(#function) not implemented yet.")
}
ktoso marked this conversation as resolved.
Show resolved Hide resolved

/// Check if the task is cancelled and throw an `CancellationError` if it was.
///
/// It is intentional that no information is passed to the task about why it
/// was cancelled. A task may be cancelled for many reasons, and additional
/// reasons may accrue / after the initial cancellation (for example, if the
/// task fails to immediately exit, it may pass a deadline).
///
/// The goal of cancellation is to allow tasks to be cancelled in a
/// lightweight way, not to be a secondary method of inter-task communication.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
///
/// - SeeAlso: `isCancelled()`
/* @instantaneous */
public static func checkCancellation() async throws {
if await Task.isCancelled() {
throw CancellationError()
}
}

/// Execute an operation with cancellation handler which will immediately be
/// invoked if the current task is cancelled.
///
/// This differs from the operation cooperatively checking for cancellation
/// and reacting to it in that the cancellation handler is _always_ and
/// _immediately_ invoked when the task is cancelled. For example, even if the
/// operation is running code which never checks for cancellation, a cancellation
/// handler still would run and give us a chance to run some cleanup code.
///
/// Does not check for cancellation, and always executes the passed `operation`.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
/* @instantaneous */
public static func withCancellationHandler<T>(
handler: /* @concurrent */ () -> (),
operation: () async throws -> T
) async throws -> T {
fatalError("\(#function) not implemented yet.")
}

/// The default cancellation thrown when a task is cancelled.
///
/// This error is also thrown automatically by `Task.checkCancellation()`,
/// if the current task has been cancelled.
public struct CancellationError: Error {
// no extra information, cancellation is intended to be light-weight
}

}

// ==== Task Deadlines ---------------------------------------------------------

extension Task {

/// Returns the earliest deadline set on the current task.
///
/// If no deadline was set for the task the `Deadline.distantFuture` is returned,
/// as it is effective in conveying that there still is time remaining and the
/// deadline is not overdue yet.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
/* @instantaneous */
public static func currentDeadline() async -> Deadline {
fatalError("\(#function) not implemented yet.")
}

/// Execute a code block with a deadline in `interval`.
///
/// If the current task already has a deadline set that is _prior_
/// to the newly set deadline with this API, that deadline will remain in effect.
///
/// This allows higher level tasks to set an upper bound on the deadline they
/// are willing cumulatively willing to wait for the entire task to execute,
/// regardless of the inner deadlines of the specific child tasks.
///
/// Cancellation is co-operative and must be checked for by the operation, e.g.
/// by invoking `Task.checkCancellation`, or `Task.isCancelled`.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
///
/// - Parameters:
/// - interval: interval after which (from `now()`) the operation task should
/// be considered cancelled.
/// - operation: the operation to execute
/* @instantaneous */
public static func withDeadline<T>(
in interval: _TimeInterval,
operation: () async throws -> T
) async rethrows -> T {
fatalError("\(#function) not implemented yet.")
}

/// Execute a code block with the passed in deadline (unless a shorter deadline is already set).
///
/// If the current task already has a deadline set that is _prior_
/// to the newly set deadline with this API, that deadline will remain in effect.
///
/// This allows higher level tasks to set an upper bound on the deadline they
/// are willing cumulatively willing to wait for the entire task to execute,
/// regardless of the inner deadlines of the specific child tasks.
///
/// Cancellation is co-operative and must be checked for by the operation, e.g.
/// by invoking `Task.checkCancellation` or `Task.isCancelled`.
///
/// ### Suspension
/// This function returns instantly and will never suspend.
///
/// - Parameters:
/// - deadline: the point in time after which the operation task should be
/// considered cancelled.
/// - operation: the operation to execute
/* @instantaneous */
public static func withDeadline<T>(
_ deadline: Deadline,
operation: () async throws -> T
) async rethrows -> T {
fatalError("\(#function) not implemented yet.")
}

/// A deadline is a point in time past-which a task should be considered cancelled.
///
/// Deadlines function the same was as pure cancellation, in the sense that they
/// are cooperative and require the cancelled (deadline exceeding) task to check
/// for this as it is performing its execution.
///
/// Generally tasks (or partial tasks) should perform such check before they
/// start executing, however this is not a strict rule, and some tasks may
/// choose to be un-cancellable.
public struct Deadline {
public typealias WallTime = UInt64 // equivalent to DispatchWallTime
internal let time: WallTime

public init(at time: WallTime) {
self.time = time
}

public static var distantFuture: Self {
.init(at: .max)
}

public static func `in`(_ interval: _TimeInterval) -> Self {
// now() + interval
fatalError("#\(#function) not implemented yet.")
}

/// Returns `true` if the deadline is overdue and deadline should be
/// considered overdue (or "exceeded").
///
/// If this deadline was related to a `Task`, that task should be considered
/// cancelled if the deadline is overdue.
public var isOverdue: Bool {
!self.hasTimeLeft
}

/// Returns `true` if the deadline is still pending with respect to "now".
public var hasTimeLeft: Bool {
fatalError("\(#function) not implemented yet.")// self.hasTimeLeft(until: now())
}

// TODO: public func hasTimeLeft(until: DispatchWallTime or whichever time type we'll use) -> Bool

}
}
66 changes: 66 additions & 0 deletions stdlib/public/Concurrency/_TimeTypes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
////===----------------------------------------------------------------------===//
////
//// This source file is part of the Swift.org open source project
////
//// Copyright (c) 2020 Apple Inc. and the Swift project authors
//// Licensed under Apache License v2.0 with Runtime Library Exception
////
//// See https://swift.org/LICENSE.txt for license information
//// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
////
////===----------------------------------------------------------------------===//

import Swift
@_implementationOnly import _SwiftConcurrencyShims

// FIXME: This file and all "time types" defined here are temporary until we decide what types to use.
// It was suggested to avoid Dispatch types in public API of Swift Concurrency,
// so for now we use "bare minimum" types just to be able to continue prototyping.
extension Task {
/// Represents a time interval, i.e. a number of seconds.
///
/// It can be used to express deadlines, in the form of time interval from "now."
///
/// - Note: This is equivalent to `DispatchTimeInterval` if we were to use it.
public struct _TimeInterval: Equatable, Comparable {
let nanoseconds: UInt64

private init(nanoseconds: UInt64) {
self.nanoseconds = nanoseconds
}

public static func seconds(_ s: UInt64) -> Self {
.init(nanoseconds: clampedInt64Product(s, 1_000_000_000))
}

public static func milliseconds(_ ms: UInt64) -> Self {
.init(nanoseconds: clampedInt64Product(ms, 1_000_000))
}

public static func microseconds(_ us: UInt64) -> Self {
.init(nanoseconds: clampedInt64Product(us, 1000))
}

public static func nanoseconds(_ ns: UInt64) -> Self {
.init(nanoseconds: ns)
}

public static var never: Self {
.init(nanoseconds: .max)
}

public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.nanoseconds < rhs.nanoseconds
}
}

}

// Returns m1 * m2, clamped to the range [UInt64.min, UInt64.max].
private func clampedInt64Product(_ m1: UInt64, _ m2: UInt64) -> UInt64 {
let (result, overflow) = m1.multipliedReportingOverflow(by: m2)
if overflow {
return UInt64.max
}
return result
}