blob: 0854e497287184d60eb81cb2042620b922a6d568 [file] [log] [blame]
/*
* Copyright 2016-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:JvmMultifileClass
@file:JvmName("JobKt")
package kotlinx.coroutines.experimental
import kotlinx.coroutines.experimental.internalAnnotations.*
import kotlinx.coroutines.experimental.selects.*
import kotlin.coroutines.experimental.*
// --------------- core job interfaces ---------------
/**
* A background job. Conceptually, a job is a cancellable thing with a simple life-cycle that
* culminates in its completion. Jobs can be arranged into parent-child hierarchies where cancellation
* or completion of parent immediately cancels all its [children].
*
* The most basic instances of [Job] are created with [launch] coroutine builder or with a
* `Job()` factory function. Other coroutine builders and primitives like
* [Deferred] also implement [Job] interface.
*
* A job has the following states:
*
* | **State** | [isActive] | [isCompleted] | [isCancelled] |
* | --------------------------------------- | ---------- | ------------- | ------------- |
* | _New_ (optional initial state) | `false` | `false` | `false` |
* | _Active_ (default initial state) | `true` | `false` | `false` |
* | _Completing_ (optional transient state) | `true` | `false` | `false` |
* | _Cancelling_ (optional transient state) | `false` | `false` | `true` |
* | _Cancelled_ (final state) | `false` | `true` | `true` |
* | _Completed_ (final state) | `false` | `true` | `false` |
*
* Usually, a job is created in _active_ state (it is created and started). However, coroutine builders
* that provide an optional `start` parameter create a coroutine in _new_ state when this parameter is set to
* [CoroutineStart.LAZY]. Such a job can be made _active_ by invoking [start] or [join].
*
* A job can be _cancelled_ at any time with [cancel] function that forces it to transition to
* _cancelling_ state immediately. Job that is not backed by a coroutine (see `Job()` function) and does not have
* [children] becomes _cancelled_ on [cancel] immediately.
* Otherwise, job becomes _cancelled_ when it finishes executing its code and
* when all its children [complete][isCompleted].
*
* ```
* wait children
* +-----+ start +--------+ complete +-------------+ finish +-----------+
* | New | ---------------> | Active | -----------> | Completing | -------> | Completed |
* +-----+ +--------+ +-------------+ +-----------+
* | | |
* | cancel | cancel | cancel
* V V |
* +-----------+ finish +------------+ |
* | Cancelled | <--------- | Cancelling | <----------------+
* |(completed)| +------------+
* +-----------+
* ```
*
* A job in the
* [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html)
* represents the coroutine itself.
* A job is active while the coroutine is working and job's cancellation aborts the coroutine when
* the coroutine is suspended on a _cancellable_ suspension point by throwing [CancellationException].
*
* A job can have a _parent_ job. A job with a parent is cancelled when its parent is cancelled or completes exceptionally.
* Parent job waits for all its children to complete in _completing_ or _cancelling_ state.
* _Completing_ state is purely internal to the job. For an outside observer a _completing_ job is still active,
* while internally it is waiting for its children.
*
* All functions on this interface and on all interfaces derived from it are **thread-safe** and can
* be safely invoked from concurrent coroutines without external synchronization.
*/
public interface Job : CoroutineContext.Element {
/**
* Key for [Job] instance in the coroutine context.
*/
public companion object Key : CoroutineContext.Key<Job> {
/**
* Creates a new job object in _active_ state.
* It is optionally a child of a [parent] job.
* @suppress **Deprecated**
*/
@Deprecated("Replaced with top-level function", level = DeprecationLevel.HIDDEN)
public operator fun invoke(parent: Job? = null): Job = Job(parent)
init {
/*
* Here we make sure that CoroutineExceptionHandler is always initialized in advance, so
* that if a coroutine fails due to StackOverflowError we don't fail to report this error
* trying to initialize CoroutineExceptionHandler
*/
CoroutineExceptionHandler
}
}
// ------------ state query ------------
/**
* Returns `true` when this job is active -- it was already started and has not completed or cancelled yet.
* The job that is waiting for its [children] to complete is still considered to be active if it
* was not cancelled.
*/
public val isActive: Boolean
/**
* Returns `true` when this job has completed for any reason. A job that was cancelled and has
* finished its execution is also considered complete. Job becomes complete only after
* all its [children] complete.
*/
public val isCompleted: Boolean
/**
* Returns `true` if this job was [cancelled][cancel]. In the general case, it does not imply that the
* job has already [completed][isCompleted] (it may still be cancelling whatever it was doing).
*/
public val isCancelled: Boolean
/**
* Returns [CancellationException] that signals the completion of this job. This function is
* used by [cancellable][suspendCancellableCoroutine] suspending functions. They throw exception
* returned by this function when they suspend in the context of this job and this job becomes _complete_.
*
* This function returns the original [cancel] cause of this job if that `cause` was an instance of
* [CancellationException]. Otherwise (if this job was cancelled with a cause of a different type, or
* was cancelled without a cause, or had completed normally), an instance of [JobCancellationException] is
* returned. The [JobCancellationException.cause] of the resulting [JobCancellationException] references
* the original cancellation cause that was passed to [cancel] function.
*
* This function throws [IllegalStateException] when invoked on a job that has not
* [completed][isCompleted] nor [cancelled][isCancelled] yet.
*/
public fun getCancellationException(): CancellationException
/**
* @suppress **Deprecated**: Renamed to [getCancellationException]
*/
@Deprecated("Renamed to getCancellationException", replaceWith = ReplaceWith("getCancellationException()"))
public fun getCompletionException(): Throwable =
getCancellationException()
// ------------ state update ------------
/**
* Starts coroutine related to this job (if any) if it was not started yet.
* The result `true` if this invocation actually started coroutine or `false`
* if it was already started or completed.
*/
public fun start(): Boolean
/**
* Cancels this job with an optional cancellation [cause]. The result is `true` if this job was
* cancelled as a result of this invocation and `false` otherwise
* (if it was already _completed_ or if it is [NonCancellable]).
* Repeated invocations of this function have no effect and always produce `false`.
*
* When cancellation has a clear reason in the code, an instance of [CancellationException] should be created
* at the corresponding original cancellation site and passed into this method to aid in debugging by providing
* both the context of cancellation and text description of the reason.
*/
public fun cancel(cause: Throwable? = null): Boolean
// ------------ parent-child ------------
/**
* Returns a sequence of this job's children.
*
* A job becomes a child of this job when it is constructed with this job in its
* [CoroutineContext] or using an explicit `parent` parameter.
*
* A parent-child relation has the following effect:
*
* * Cancellation of parent with [cancel] or its exceptional completion (failure)
* immediately cancels all its children.
* * Parent cannot complete until all its children are complete. Parent waits for all its children to
* complete in _completing_ or _cancelling_ state.
* * Uncaught exception in a child, by default, cancels parent. In particular, this applies to
* children created with [launch] coroutine builder. Note, that [async] and other future-like
* coroutine builders do not have uncaught exceptions by definition, since all their exceptions are
* caught and are encapsulated in their result.
*/
public val children: Sequence<Job>
/**
* Attaches child job so that this job becomes its parent and
* returns a handle that should be used to detach it.
*
* A parent-child relation has the following effect:
* * Cancellation of parent with [cancel] or its exceptional completion (failure)
* immediately cancels all its children.
* * Parent cannot complete until all its children are complete. Parent waits for all its children to
* complete in _completing_ or _cancelling_ state.
*
* **A child must store the resulting [DisposableHandle] and [dispose][DisposableHandle.dispose] the attachment
* to its parent on its own completion.**
*
* Coroutine builders and job factory functions that accept `parent` [CoroutineContext] parameter
* lookup a [Job] instance in the parent context and use this function to attach themselves as a child.
* They also store a reference to the resulting [DisposableHandle] and dispose a handle when they complete.
*
* @suppress This is an internal API. This method is too error prone for public API.
*/
@Deprecated(message = "Start child coroutine with 'parent' parameter", level = DeprecationLevel.WARNING)
public fun attachChild(child: Job): DisposableHandle
/**
* Cancels all children jobs of this coroutine with the given [cause]. Unlike [cancel],
* the state of this job itself is not affected.
* @suppress **Deprecated**: Binary compatibility, it is an extension now
*/
@Deprecated(message = "Binary compatibility, it is an extension now", level = DeprecationLevel.HIDDEN)
public fun cancelChildren(cause: Throwable? = null)
// ------------ state waiting ------------
/**
* Suspends coroutine until this job is complete. This invocation resumes normally (without exception)
* when the job is complete for any reason and the [Job] of the invoking coroutine is still [active][isActive].
* This function also [starts][Job.start] the corresponding coroutine if the [Job] was still in _new_ state.
*
* Note, that the job becomes complete only when all its children are complete.
*
* This suspending function is cancellable and **always** checks for the cancellation of invoking coroutine's Job.
* If the [Job] of the invoking coroutine is cancelled or completed when this
* suspending function is invoked or while it is suspended, this function
* throws [CancellationException].
*
* In particular, it means that a parent coroutine invoking `join` on a child coroutine that was started using
* `launch(coroutineContext) { ... }` builder throws [CancellationException] if the child
* had crashed, unless a non-standard [CoroutineExceptionHandler] if installed in the context.
*
* This function can be used in [select] invocation with [onJoin] clause.
* Use [isCompleted] to check for completion of this job without waiting.
*
* There is [cancelAndJoin] function that combines an invocation of [cancel] and `join`.
*/
public suspend fun join()
/**
* Clause for [select] expression of [join] suspending function that selects when the job is complete.
* This clause never fails, even if the job completes exceptionally.
*/
public val onJoin: SelectClause0
// ------------ low-level state-notification ------------
/**
* @suppress **Deprecated**: For binary compatibility
*/
@Deprecated(message = "For binary compatibility", level = DeprecationLevel.HIDDEN)
public fun invokeOnCompletion(handler: CompletionHandler, onCancelling: Boolean): DisposableHandle
/**
* @suppress **Deprecated**: Use with named `onCancelling` and `handler` parameters.
*/
@Deprecated(message = "Use with named `onCancelling` and `handler` parameters", level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith("this.invokeOnCompletion(onCancelling = onCancelling_, handler = handler)"))
public fun invokeOnCompletion(onCancelling_: Boolean = false, handler: CompletionHandler): DisposableHandle
/**
* Registers handler that is **synchronously** invoked once on completion of this job.
* When job is already complete, then the handler is immediately invoked
* with a job's exception or cancellation cause or `null`. Otherwise, handler will be invoked once when this
* job is complete.
*
* The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] the
* registration of this handler and release its memory if its invocation is no longer needed.
* There is no need to dispose the handler after completion of this job. The references to
* all the handlers are released when this job completes.
*
* Installed [handler] should not throw any exceptions. If it does, they will get caught,
* wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code.
*
* **Note**: Implementations of `CompletionHandler` must be fast and _lock-free_.
*/
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
/**
* Registers handler that is **synchronously** invoked once on cancellation or completion of this job.
* When job is already cancelling or complete, then the handler is immediately invoked
* with a job's cancellation cause or `null` unless [invokeImmediately] is set to false.
* Otherwise, handler will be invoked once when this job is cancelled or complete.
*
* Invocation of this handler on a transition to a transient _cancelling_ state
* is controlled by [onCancelling] boolean parameter.
* The handler is invoked on invocation of [cancel] when
* job becomes _cancelling_ if [onCancelling] parameter is set to `true`. However,
* when this [Job] is not backed by a coroutine, like [CompletableDeferred] or [CancellableContinuation]
* (both of which do not posses a _cancelling_ state), then the value of [onCancelling] parameter is ignored.
*
* The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] the
* registration of this handler and release its memory if its invocation is no longer needed.
* There is no need to dispose the handler after completion of this job. The references to
* all the handlers are released when this job completes.
*
* Installed [handler] should not throw any exceptions. If it does, they will get caught,
* wrapped into [CompletionHandlerException], and rethrown, potentially causing crash of unrelated code.
*
* **Note**: This function is a part of internal machinery that supports parent-child hierarchies
* and allows for implementation of suspending functions that wait on the Job's state.
* This function should not be used in general application code.
* Implementations of `CompletionHandler` must be fast and _lock-free_.
*
* @param onCancelling when `true`, then the [handler] is invoked as soon as this job transitions to _cancelling_ state;
* when `false` then the [handler] is invoked only when it transitions to _completed_ state.
* @param invokeImmediately when `true` and this job is already in the desired state (depending on [onCancelling]),
* then the [handler] is immediately and synchronously invoked and [NonDisposableHandle] is returned;
* when `false` then [NonDisposableHandle] is returned, but the [handler] is not invoked.
* @param handler the handler.
*/
public fun invokeOnCompletion(
onCancelling: Boolean = false,
invokeImmediately: Boolean = true,
handler: CompletionHandler): DisposableHandle
// ------------ unstable internal API ------------
/**
* @suppress **Error**: Operator '+' on two Job objects is meaningless.
* Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts.
* The job to the right of `+` just replaces the job the left of `+`.
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated(message = "Operator '+' on two Job objects is meaningless. " +
"Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts. " +
"The job to the right of `+` just replaces the job the left of `+`.",
level = DeprecationLevel.ERROR)
public operator fun plus(other: Job) = other
}
/**
* Creates a new job object in an _active_ state.
* It is optionally a child of a [parent] job.
*/
@Suppress("FunctionName")
public fun Job(parent: Job? = null): Job = JobImpl(parent)
/**
* A handle to an allocated object that can be disposed to make it eligible for garbage collection.
*/
public interface DisposableHandle {
/**
* Disposes the corresponding object, making it eligible for garbage collection.
* Repeated invocation of this function has no effect.
*/
public fun dispose()
}
// -------------------- Job extensions --------------------
/**
* Unregisters a specified [registration] when this job is complete.
*
* This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
* ```
* invokeOnCompletion { registration.unregister() }
* ```
* @suppress: **Deprecated**: Renamed to `disposeOnCompletion`.
*/
@Deprecated(message = "Renamed to `disposeOnCompletion`",
replaceWith = ReplaceWith("disposeOnCompletion(registration)"))
public fun Job.unregisterOnCompletion(registration: DisposableHandle): DisposableHandle =
invokeOnCompletion(handler = DisposeOnCompletion(this, registration).asHandler)
/**
* Disposes a specified [handle] when this job is complete.
*
* This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
* ```
* invokeOnCompletion { handle.dispose() }
* ```
*/
public fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle =
invokeOnCompletion(handler = DisposeOnCompletion(this, handle).asHandler)
/**
* Cancels the job and suspends invoking coroutine until the cancelled job is complete.
*
* This suspending function is cancellable and **always** checks for the cancellation of invoking coroutine's Job.
* If the [Job] of the invoking coroutine is cancelled or completed when this
* suspending function is invoked or while it is suspended, this function
* throws [CancellationException].
*
* In particular, it means that a parent coroutine invoking `cancelAndJoin` on a child coroutine that was started using
* `launch(coroutineContext) { ... }` builder throws [CancellationException] if the child
* had crashed, unless a non-standard [CoroutineExceptionHandler] if installed in the context.
*
* This is a shortcut for the invocation of [cancel][Job.cancel] followed by [join][Job.join].
*/
public suspend fun Job.cancelAndJoin() {
cancel()
return join()
}
/**
* Cancels all [children][Job.children] jobs of this coroutine with the given [cause] using [Job.cancel]
* for all of them. Unlike [Job.cancel] on this job as a whole, the state of this job itself is not affected.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // See KT-21598
public fun Job.cancelChildren(cause: Throwable? = null) {
children.forEach { it.cancel(cause) }
}
/**
* Suspends coroutine until all [children][Job.children] of this job are complete using
* [Job.join] for all of them. Unlike [Job.join] on this job as a whole, it does not wait until
* this job is complete.
*/
public suspend fun Job.joinChildren() {
children.forEach { it.join() }
}
// -------------------- CoroutineContext extensions --------------------
/**
* Returns `true` when the [Job] of the coroutine in this context is still active
* (has not completed and was not cancelled yet).
*
* Check this property in long-running computation loops to support cancellation
* when [CoroutineScope.isActive] is not available:
*
* ```
* while (coroutineContext.isActive) {
* // do some computation
* }
* ```
*
* The `coroutineContext.isActive` expression is a shortcut for `coroutineContext[Job]?.isActive == true`.
* See [Job.isActive].
*/
public val CoroutineContext.isActive: Boolean
get() = this[Job]?.isActive == true
/**
* Cancels [Job] of this context with an optional cancellation [cause]. The result is `true` if the job was
* cancelled as a result of this invocation and `false` if there is no job in the context or if it was already
* cancelled or completed. See [Job.cancel] for details.
*/
public fun CoroutineContext.cancel(cause: Throwable? = null): Boolean =
this[Job]?.cancel(cause) ?: false
/**
* Cancels all children of the [Job] in this context with an optional cancellation [cause].
* It does not do anything if there is no job in the context or it has no children.
* See [Job.cancelChildren] for details.
*/
public fun CoroutineContext.cancelChildren(cause: Throwable? = null) {
this[Job]?.cancelChildren(cause)
}
/**
* @suppress **Deprecated**: `join` is now a member function of `Job`.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "DeprecatedCallableAddReplaceWith")
@Deprecated(message = "`join` is now a member function of `Job`")
public suspend fun Job.join() = this.join()
/**
* No-op implementation of [DisposableHandle].
*/
public object NonDisposableHandle : DisposableHandle {
/** Does not do anything. */
override fun dispose() {}
/** Returns "NonDisposableHandle" string. */
override fun toString(): String = "NonDisposableHandle"
}