Exception transparency in job.cancel (original cause is rethrown)
Clarified possible states for Job/CancellableContinuation/Deferred/LazyDeferred in docs
Deferred.isCompletedExceptionally and isCancelled are introduced.
Job.getInactiveCancellationException is renamed to getCompletionException
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt
index a9482a4..15e7a72 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt
@@ -89,7 +89,7 @@
 ) : AbstractCoroutine<Unit>(parentContext) {
     override fun afterCompletion(state: Any?) {
         // note the use of the parent's job context below!
-        if (state is CompletedExceptionally) handleCoroutineException(parentContext, state.cancelCause)
+        if (state is CompletedExceptionally) handleCoroutineException(parentContext, state.exception)
     }
 }
 
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
index f5c536e..53a0940 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
@@ -26,14 +26,24 @@
 // --------------- cancellable continuations ---------------
 
 /**
- * Cancellable continuation. Its job is completed when it is resumed or cancelled.
- * When [cancel] function is explicitly invoked, this continuation resumes with [CancellationException].
- * If the cancel reason was not a [CancellationException], then the original exception is added as cause of the
- * [CancellationException] that this continuation resumes with.
+ * Cancellable continuation. Its job is _completed_ when it is resumed or cancelled.
+ * When [cancel] function is explicitly invoked, this continuation resumes with [CancellationException] or
+ * with the specified cancel cause.
+ *
+ * Cancellable continuation has three states:
+ * * _Active_ (initial state) -- [isActive] `true`, [isCancelled] `false`.
+ * * _Resumed_ (final _completed_ state) -- [isActive] `false`, [isCancelled] `false`.
+ * * _Canceled_ (final _completed_ state) -- [isActive] `false`, [isCancelled] `true`.
+ *
+ * Invocation of [cancel] transitions this continuation from _active_ to _cancelled_ state, while
+ * invocation of [resume] or [resumeWithException] transitions it from _active_ to _resumed_ state.
+ *
+ * Invocation of [resume] or [resumeWithException] in _resumed_ state produces [IllegalStateException]
+ * but is ignored in _cancelled_ state.
  */
 public interface CancellableContinuation<in T> : Continuation<T>, Job {
     /**
-     * Returns `true` if this continuation was cancelled. It implies that [isActive] is `false`.
+     * Returns `true` if this continuation was [cancelled][cancel]. It implies that [isActive] is `false`.
      */
     val isCancelled: Boolean
 
@@ -87,7 +97,7 @@
 internal fun getParentJobOrAbort(cont: Continuation<*>): Job? {
     val job = cont.context[Job]
     // fast path when parent job is already complete (we don't even construct SafeCancellableContinuation object)
-    if (job != null && !job.isActive) throw job.getInactiveCancellationException()
+    if (job != null && !job.isActive) throw job.getCompletionException()
     return job
 }
 
@@ -144,7 +154,7 @@
         while (true) { // lock-free loop on state
             val state = getState() // atomic read
             when (state) {
-                is Active -> if (tryUpdateState(state, Failed(exception))) return state
+                is Active -> if (tryUpdateState(state, CompletedExceptionally(exception))) return state
                 else -> return null // cannot resume -- not active anymore
             }
         }
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt
index 196793b..3f510bc 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt
@@ -101,7 +101,7 @@
             dispatcher.dispatch(context, Runnable {
                 withCoroutineContext(context) {
                     if (job?.isActive == false)
-                        continuation.resumeWithException(job.getInactiveCancellationException())
+                        continuation.resumeWithException(job.getCompletionException())
                     else
                         continuation.resume(value)
                 }
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt
index 1b41704..8ac8d54 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineScope.kt
@@ -59,7 +59,7 @@
         while (true) { // lock-free loop on state
             val state = getState() // atomic read
             when (state) {
-                is Active -> if (updateState(state, Failed(exception))) return
+                is Active -> if (updateState(state, CompletedExceptionally(exception))) return
                 is Cancelled -> {
                     // ignore resumes on cancelled continuation, but handle exception if a different one is here
                     if (exception != state.exception) handleCoroutineException(context, exception)
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt
index c9c3582..ab61173 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt
@@ -23,9 +23,34 @@
  * Deferred value is conceptually a non-blocking cancellable future.
  * It is created with [defer] coroutine builder.
  * It is in [active][isActive] state while the value is being computed.
+ *
+ * Deferred value has four states:
+ *
+ * * _Active_ (initial state) -- [isActive] `true`, [isCompletedExceptionally] `false`,
+ *   and [isCancelled] `false`.
+ *   Both [getCompleted] and [getCompletionException] throw [IllegalStateException].
+ * * _Computed_ (final _completed_ state) -- [isActive] `false`,
+ *   [isCompletedExceptionally] `false`, [isCancelled] `false`.
+ * * _Failed_ (final _completed_ state) -- [isActive] `false`,
+ *   [isCompletedExceptionally] `true`, [isCancelled] `false`.
+ * * _Canceled_ (final _completed_ state) -- [isActive] `false`,
+ *   [isCompletedExceptionally] `true`, [isCancelled] `true`.
  */
 public interface Deferred<out T> : Job {
     /**
+     * Returns `true` if computation of this deferred value has _completed exceptionally_ -- it had
+     * either _failed_ with exception during computation or was [cancelled][cancel].
+     * It implies that [isActive] is `false`.
+     */
+    val isCompletedExceptionally: Boolean
+
+    /**
+     * Returns `true` if computation of this deferred value was [cancelled][cancel].
+     * It implies that [isActive] is `false` and [isCompletedExceptionally] is `true`.
+     */
+    val isCancelled: Boolean
+
+    /**
      * Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete.
      * This suspending function is cancellable.
      * If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
@@ -62,6 +87,9 @@
 ) : AbstractCoroutine<T>(context), Deferred<T> {
     protected open fun start(): Boolean = false // LazyDeferredCoroutine overrides
 
+    override val isCompletedExceptionally: Boolean get() = getState() is CompletedExceptionally
+    override val isCancelled: Boolean get() = getState() is Cancelled
+
     @Suppress("UNCHECKED_CAST")
     suspend override fun await(): T {
         // quick check if already complete (avoid extra object creation)
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt
index cfea2f2..39e0d60 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt
@@ -27,17 +27,23 @@
 // --------------- core job interfaces ---------------
 
 /**
- * A background job. It has two states: _active_ (initial state) and _completed_ (final state).
+ * A background job.
+ * A job can be _cancelled_ at any time with [cancel] function that forces it to become _completed_ immediately.
  *
- * A job can be _cancelled_ at any time with [cancel] function that forces it to become completed immediately.
+ * It has two states:
+ * * _Active_ (initial state) -- [isActive] `true`,
+ *   [getCompletionException] throws [IllegalStateException].
+ * * _Completed_ (final state) -- [isActive] `false`.
+ *
  * A job in the coroutine [context][CoroutineScope.context] 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]
- * inside the coroutine.
+ * or the cancellation cause inside the coroutine.
  *
  * A job can have a _parent_. A job with a parent is cancelled when its parent completes.
  *
- * All functions on this interface are thread-safe.
+ * 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 {
     /**
@@ -56,17 +62,23 @@
     public val isActive: Boolean
 
     /**
-     * Returns [CancellationException] that [cancellable][suspendCancellableCoroutine] suspending functions throw when
-     * trying to suspend in the context of this job. This function throws [IllegalAccessException] when invoked
-     * for an [active][isActive] job.
+     * Returns the exception that signals the completion of this job -- it returns the original
+     * [cancel] cause or an instance of [CancellationException] if this job had completed
+     * normally or was cancelled without a cause. This function throws
+     * [IllegalStateException] when invoked for an [active][isActive] job.
+     *
+     * The [cancellable][suspendCancellableCoroutine] suspending functions throw this exception
+     * when trying to suspend in the context of this job.
      */
-    fun getInactiveCancellationException(): CancellationException
+    fun getCompletionException(): Throwable
 
     /**
      * Registers completion handler. The action depends on the state of this job.
      * When job is cancelled with [cancel], then the handler is immediately invoked
-     * with a cancellation reason. Otherwise, handler will be invoked once when this
-     * job is complete (cancellation also is a form of completion).
+     * with a cancellation cause or with a fresh [CancellationException].
+     * Otherwise, handler will be invoked once when this job is complete
+     * (cancellation also is a form of completion).
+     *
      * The resulting [Registration] can be used to [Registration.unregister] if this
      * registration is no longer needed. There is no need to unregister after completion.
      */
@@ -75,8 +87,8 @@
     /**
      * Cancel this activity 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 cancelled or it is [NonCancellable]).
-     * Repeated invocation of this function has no effect and always produces `false`.
+     * (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
@@ -247,19 +259,19 @@
 
     fun completeUpdateState(expect: Any, update: Any?) {
         // #3. Invoke completion handlers
-        val reason = (update as? CompletedExceptionally)?.cancelCause
+        val cause = (update as? CompletedExceptionally)?.exception
         var completionException: Throwable? = null
         when (expect) {
             // SINGLE/SINGLE+ state -- one completion handler (common case)
             is JobNode -> try {
-                expect.invoke(reason)
+                expect.invoke(cause)
             } catch (ex: Throwable) {
                 completionException = ex
             }
             // LIST state -- a list of completion handlers
             is NodeList -> expect.forEach<JobNode> { node ->
                 try {
-                    node.invoke(reason)
+                    node.invoke(cause)
                 } catch (ex: Throwable) {
                     completionException?.apply { addSuppressed(ex) } ?: run { completionException = ex }
                 }
@@ -275,12 +287,12 @@
 
     final override val isActive: Boolean get() = state is Active
 
-    override fun getInactiveCancellationException(): CancellationException {
+    override fun getCompletionException(): Throwable {
         val state = getState()
         return when (state) {
             is Active -> throw IllegalStateException("Job is still active")
-            is CompletedExceptionally -> state.cancellationException
-            else -> CancellationException("Job has completed with result")
+            is CompletedExceptionally -> state.exception
+            else -> CancellationException("Job has completed normally")
         }
     }
 
@@ -311,7 +323,7 @@
                 }
                 // is not active anymore
                 else -> {
-                    handler((state as? Cancelled)?.cancelCause)
+                    handler((state as? CompletedExceptionally)?.exception)
                     return EmptyRegistration
                 }
             }
@@ -379,49 +391,40 @@
      */
     internal interface Active
 
-    private object Empty : Active
+    private object Empty : Active {
+        override fun toString(): String = "Empty"
+    }
 
-    private class NodeList : LockFreeLinkedListHead(), Active
+    private class NodeList : LockFreeLinkedListHead(), Active {
+        override fun toString(): String = buildString {
+            append("[")
+            var first = true
+            this@NodeList.forEach<JobNode> { node ->
+                if (first) first = false else append(", ")
+                append(node)
+            }
+            append("]")
+        }
+    }
 
     /**
-     * Abstract class for a [state][getState] of a job that had completed exceptionally, including cancellation.
+     * Class for a [state][getState] of a job that had completed exceptionally, including cancellation.
      */
-    internal abstract class CompletedExceptionally {
-        abstract val cancelCause: Throwable // original reason or fresh CancellationException
-        abstract val exception: Throwable // the exception to be thrown in continuation
-
-        // convert cancelCause to CancellationException on first need
+    internal open class CompletedExceptionally(cause: Throwable?) {
         @Volatile
-        private var _cancellationException: CancellationException? = null
+        private var _exception: Throwable? = cause // materialize CancellationException on first need
 
-        val cancellationException: CancellationException get() =
-            _cancellationException ?: // atomic read volatile var or else build new
-                (cancelCause as? CancellationException ?:
-                        CancellationException(cancelCause.message)
-                                .apply { initCause(cancelCause) })
-                                .also { _cancellationException = it }
+        val exception: Throwable get() =
+            _exception ?: // atomic read volatile var or else create new
+                CancellationException("Job was cancelled").also { _exception = it }
+
+        override fun toString(): String = "${javaClass.simpleName}[$exception]"
     }
 
     /**
-     * Represents a [state][getState] of a cancelled job.
+     * A specific subclass of [CompletedExceptionally] for cancelled jobs.
      */
-    internal class Cancelled(specifiedCause: Throwable?) : CompletedExceptionally() {
-        @Volatile
-        private var _cancelCause = specifiedCause // materialize CancellationException on first need
-
-        override val cancelCause: Throwable get() =
-            _cancelCause ?: // atomic read volatile var or else create new
-                CancellationException("Job was cancelled").also { _cancelCause = it }
-
-        override val exception: Throwable get() = cancellationException
-    }
-
-    /**
-     * Represents a [state][getState] of a failed job.
-     */
-    internal class Failed(override val exception: Throwable) : CompletedExceptionally() {
-        override val cancelCause: Throwable get() = exception
-    }
+    internal class Cancelled(cause: Throwable?) : CompletedExceptionally(cause)
 }
 
 internal abstract class JobNode(
@@ -438,7 +441,7 @@
     val handler: CompletionHandler
 ) : JobNode(job)  {
     override fun invoke(reason: Throwable?) = handler.invoke(reason)
-    override fun toString() = "InvokeOnCompletion[${handler::class.java.name}@${Integer.toHexString(System.identityHashCode(handler))}]"
+    override fun toString() = "InvokeOnCompletion[${handler.javaClass.name}@${Integer.toHexString(System.identityHashCode(handler))}]"
 }
 
 private class ResumeOnCompletion(
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/LazyDeferred.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/LazyDeferred.kt
index 420765c..68cc0a5 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/LazyDeferred.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/LazyDeferred.kt
@@ -25,10 +25,20 @@
  * the first [await] or [start] invocation.
  * It is created with [lazyDefer] coroutine builder.
  *
- * Unlike a simple [Deferred] value, a lazy deferred value has three states:
- * * _Pending_ -- before the starts of the coroutine ([isActive] is `true`, but [isComputing] is `false`).
- * * _Computing_ -- while computing the value ([isActive] is `true` and [isComputing] is `true`).
- * * _Complete_ -- when done computing the value ([isActive] is `false` and [isComputing] is `false`).
+ * Unlike a simple [Deferred] value, a lazy deferred value has five states:
+ *
+ * * _Pending_ (initial, _active_ state before the starts of the coroutine) --
+ *   [isActive] `true`, but [isComputing] `false`,
+ *   [isCompletedExceptionally] `false`, and [isCancelled] `false`.
+ * * _Computing_ (intermediate state while computing the value) --
+ *   [isActive] `true`, [isComputing] `true`,
+ *   [isCompletedExceptionally] `false`, and [isCancelled] `false`.
+ * * _Computed_ (final _completed_ state) -- [isActive] `false`, [isComputing] `false`,
+ *   [isCompletedExceptionally] `false`, [isCancelled] `false`.
+ * * _Failed_ (final _completed_ state) -- [isActive] `false`, [isComputing] `false`,
+ *   [isCompletedExceptionally] `true`, [isCancelled] `false`.
+ * * _Canceled_ (final _completed_ state) -- [isActive] `false`, [isComputing] `false`,
+ *   [isCompletedExceptionally] `true`, [isCancelled] `true`.
  *
  * If this lazy deferred value is [cancelled][cancel], then it becomes immediately complete and
  * cancels ongoing computation coroutine if it was started.
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt
index 3b9a152..e85b9c7 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/NonCancellable.kt
@@ -21,7 +21,9 @@
 
 /**
  * A non-cancelable job that is always [active][isActive]. It is designed to be used with [run] builder
- * to prevent cancellation of code blocks that need to run without cancellation, like this
+ * to prevent cancellation of code blocks that need to run without cancellation.
+ *
+ * Use it like this:
  * ```
  * run(NonCancellable) {
  *     // this code will not be cancelled
@@ -30,7 +32,7 @@
  */
 object NonCancellable : AbstractCoroutineContextElement(Job), Job {
     override val isActive: Boolean  get() = true
-    override fun getInactiveCancellationException(): CancellationException = throw IllegalStateException("This job is always active")
+    override fun getCompletionException(): CancellationException = throw IllegalStateException("This job is always active")
     override fun onCompletion(handler: CompletionHandler): Job.Registration = EmptyRegistration
     override fun cancel(cause: Throwable?): Boolean = false
 }
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt b/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt
index 8fb4fdf..ffa8ffc 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt
+++ b/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt
@@ -139,7 +139,7 @@
         throw IOException()
     }
 
-    @Test(expected = CancellationException::class)
+    @Test(expected = IOException::class)
     fun testCancelParentOnChildException(): Unit = runBlocking {
         expect(1)
         launch(context) {
@@ -151,7 +151,7 @@
         expectUnreached() // because of exception in child
     }
 
-    @Test(expected = CancellationException::class)
+    @Test(expected = IOException::class)
     fun testCancelParentOnNestedException(): Unit = runBlocking {
         expect(1)
         launch(context) {