Skip to content

Commit

Permalink
Complete test suite to check interception (#207)
Browse files Browse the repository at this point in the history
* Test RaceTriple returning on correct context, and replace `suspendCoroutine` with `suspendCoroutineUninterceptedOrReturn`.

* Test RacePair returning on correct context, and replace `suspendCoroutine` with `suspendCoroutineUninterceptedOrReturn`.

* Test RaceN returning on correct context, and replace `suspendCoroutine` with `suspendCoroutineUninterceptedOrReturn`.

* Test ParTupledN returning on correct context, and replace `suspendCoroutine` with `suspendCoroutineUninterceptedOrReturn`.

* Test Fiber returning on correct context.

* Add checkAll with Arb

* Complete test suite timer.kt

* Add missing tests failures

* ktlintFormat
  • Loading branch information
nomisRev authored Jul 1, 2020
1 parent d452456 commit 622114f
Show file tree
Hide file tree
Showing 14 changed files with 603 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED

/**
* Parallel maps [fa], [fb] in parallel on [ComputationPool].
Expand Down Expand Up @@ -100,8 +102,9 @@ suspend fun <A, B> parTupledN(
fa: suspend () -> A,
fb: suspend () -> B
): Pair<A, B> =
suspendCoroutine { cont ->
suspendCoroutineUninterceptedOrReturn { cont ->
val conn = cont.context.connection()
val cont = cont.intercepted()
val cb = cont::resumeWith

// Used to store Throwable, Either<A, B> or empty (null). (No sealed class used for a slightly better performing ParMap2)
Expand Down Expand Up @@ -156,6 +159,8 @@ suspend fun <A, B> parTupledN(
sendException(connA, e)
})
})

COROUTINE_SUSPENDED
}

/**
Expand All @@ -173,8 +178,9 @@ suspend fun <A, B, C> parTupledN(
fb: suspend () -> B,
fc: suspend () -> C
): Triple<A, B, C> =
suspendCoroutine { cont ->
suspendCoroutineUninterceptedOrReturn { cont ->
val conn = cont.context.connection()
val cont = cont.intercepted()
val cb = cont::resumeWith

val state: AtomicRefW<Triple<A?, B?, C?>?> = AtomicRefW(null)
Expand Down Expand Up @@ -252,4 +258,6 @@ suspend fun <A, B, C> parTupledN(
sendException(connA, connC, e)
})
})

COROUTINE_SUSPENDED
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED

/**
* Races the participants [fa], [fb] in parallel on the [ComputationPool].
Expand Down Expand Up @@ -60,8 +62,10 @@ suspend fun <A, B> raceN(ctx: CoroutineContext, fa: suspend () -> A, fb: suspend
})
} else Unit

return suspendCoroutine { cont ->
return suspendCoroutineUninterceptedOrReturn { cont ->
val conn = cont.context.connection()
val cont = cont.intercepted()

val active = AtomicBooleanW(true)
val connA = SuspendConnection()
val connB = SuspendConnection()
Expand All @@ -82,5 +86,7 @@ suspend fun <A, B> raceN(ctx: CoroutineContext, fa: suspend () -> A, fb: suspend
onError(active, cont::resumeWith, conn, connA, it)
})
})

COROUTINE_SUSPENDED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED

sealed class Race3<out A, out B, out C> {
data class First<A>(val winner: A) : Race3<A, Nothing, Nothing>()
Expand Down Expand Up @@ -103,8 +105,9 @@ suspend fun <A, B, C> raceN(
})
} else Unit

return suspendCoroutine { cont ->
return suspendCoroutineUninterceptedOrReturn { cont ->
val conn = cont.context.connection()
val cont = cont.intercepted()

val active = AtomicBooleanW(true)
val connA = SuspendConnection()
Expand Down Expand Up @@ -136,5 +139,7 @@ suspend fun <A, B, C> raceN(
onError(active, cont::resumeWith, conn, connA, connB, it)
})
})

COROUTINE_SUSPENDED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED

typealias RacePair<A, B> = Either<Pair<A, Fiber<B>>, Pair<Fiber<A>, B>>

fun <A, B, C> Either<Pair<A, Fiber<B>>, Pair<Fiber<A>, B>>.fold(ifLeft: (A, Fiber<B>) -> C, ifRight: (Fiber<A>, B) -> C): C =
fold({ (a, b) -> ifLeft(a, b) }, { (a, b) -> ifRight(a, b) })

suspend fun <A, B> racePair(fa: suspend () -> A, fb: suspend () -> B): RacePair<A, B> =
racePair(ComputationPool, fa, fb)

Expand Down Expand Up @@ -44,57 +49,61 @@ suspend fun <A, B> racePair(
ctx: CoroutineContext,
fa: suspend () -> A,
fb: suspend () -> B
): RacePair<A, B> = suspendCoroutine { cont ->
val conn = cont.context.connection()
val active = AtomicBooleanW(true)
): RacePair<A, B> =
suspendCoroutineUninterceptedOrReturn { cont ->
val conn = cont.context.connection()
val cont = cont.intercepted()
val active = AtomicBooleanW(true)

// Cancellable connection for the left value
val connA = SuspendConnection()
val promiseA = UnsafePromise<A>()
// Cancellable connection for the left value
val connA = SuspendConnection()
val promiseA = UnsafePromise<A>()

// Cancellable connection for the right value
val connB = SuspendConnection()
val promiseB = UnsafePromise<B>()
// Cancellable connection for the right value
val connB = SuspendConnection()
val promiseB = UnsafePromise<B>()

conn.pushPair(connA, connB)
conn.pushPair(connA, connB)

fa.startCoroutineCancellable(CancellableContinuation(ctx, connA) { result ->
result.fold({ a ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.success(Either.Left(Pair(a, Fiber(promiseB, connB)))))
} else {
promiseA.complete(Result.success(a))
}
}, { error ->
if (active.getAndSet(false)) { // if an error finishes first, stop the race.
connB.cancelToken().cancel.startCoroutine(Continuation(EmptyCoroutineContext) { r2 ->
fa.startCoroutineCancellable(CancellableContinuation(ctx, connA) { result ->
result.fold({ a ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.failure(r2.fold({ error }, { Platform.composeErrors(error, it) })))
})
} else {
promiseA.complete(Result.failure(error))
}
cont.resumeWith(Result.success(Either.Left(Pair(a, Fiber(promiseB, connB)))))
} else {
promiseA.complete(Result.success(a))
}
}, { error ->
if (active.getAndSet(false)) { // if an error finishes first, stop the race.
connB.cancelToken().cancel.startCoroutine(Continuation(EmptyCoroutineContext) { r2 ->
conn.pop()
cont.resumeWith(Result.failure(r2.fold({ error }, { Platform.composeErrors(error, it) })))
})
} else {
promiseA.complete(Result.failure(error))
}
})
})
})

fb.startCoroutineCancellable(CancellableContinuation(ctx, connB) { result ->
result.fold({ b ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.success(Either.Right(Pair(Fiber(promiseA, connA), b))))
} else {
promiseB.complete(Result.success(b))
}
}, { error ->
if (active.getAndSet(false)) { // if an error finishes first, stop the race.
connA.cancelToken().cancel.startCoroutine(Continuation(EmptyCoroutineContext) { r2 ->
fb.startCoroutineCancellable(CancellableContinuation(ctx, connB) { result ->
result.fold({ b ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.failure(r2.fold({ error }, { Platform.composeErrors(error, it) })))
})
} else {
promiseB.complete(Result.failure(error))
}
cont.resumeWith(Result.success(Either.Right(Pair(Fiber(promiseA, connA), b))))
} else {
promiseB.complete(Result.success(b))
}
}, { error ->
if (active.getAndSet(false)) { // if an error finishes first, stop the race.
connA.cancelToken().cancel.startCoroutine(Continuation(EmptyCoroutineContext) { r2 ->
conn.pop()
cont.resumeWith(Result.failure(r2.fold({ error }, { Platform.composeErrors(error, it) })))
})
} else {
promiseB.complete(Result.failure(error))
}
})
})
})
}

COROUTINE_SUSPENDED
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED

sealed class RaceTriple<A, B, C> {
data class First<A, B, C>(val winner: A, val fiberB: Fiber<B>, val fiberC: Fiber<C>) : RaceTriple<A, B, C>()
Expand Down Expand Up @@ -60,79 +62,83 @@ suspend fun <A, B, C> raceTriple(
fa: suspend () -> A,
fb: suspend () -> B,
fc: suspend () -> C
): RaceTriple<A, B, C> = suspendCoroutine { cont ->
val conn = cont.context.connection()
val active = AtomicBooleanW(true)

// Cancellable connection for the left value
val connA = SuspendConnection()
val promiseA = UnsafePromise<A>()

// Cancellable connection for the right value
val connB = SuspendConnection()
val promiseB = UnsafePromise<B>()

// Cancellable connection for the right value
val connC = SuspendConnection()
val promiseC = UnsafePromise<C>()

conn.push(listOf(connA.cancelToken(), connB.cancelToken(), connC.cancelToken()))

fun <A> onError(
error: Throwable,
connB: SuspendConnection,
connC: SuspendConnection,
promise: UnsafePromise<A>
): Unit {
if (active.getAndSet(false)) { // if an error finishes first, stop the race.
connB.cancelToken().cancel.startCoroutine(Continuation(EmptyCoroutineContext) { r2 ->
connC.cancelToken().cancel.startCoroutine(Continuation(EmptyCoroutineContext) { r3 ->
conn.pop()
): RaceTriple<A, B, C> =
suspendCoroutineUninterceptedOrReturn { cont ->
val conn = cont.context.connection()
val cont = cont.intercepted()
val active = AtomicBooleanW(true)

val errorResult = r2.fold({
r3.fold({ error }, { e3 -> Platform.composeErrors(error, e3) })
}, { e2 ->
r3.fold({ Platform.composeErrors(error, e2) }, { e3 -> Platform.composeErrors(error, e2, e3) })
})
// Cancellable connection for the left value
val connA = SuspendConnection()
val promiseA = UnsafePromise<A>()

cont.resumeWith(Result.failure(errorResult))
})
})
} else {
promise.complete(Result.failure(error))
}
}
// Cancellable connection for the right value
val connB = SuspendConnection()
val promiseB = UnsafePromise<B>()

fa.startCoroutineCancellable(CancellableContinuation(ctx, connA) { result ->
result.fold({ a ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.success(RaceTriple.First(a, Fiber(promiseB, connB), Fiber(promiseC, connC))))
} else {
promiseA.complete(Result.success(a))
}
}, { error -> onError(error, connB, connC, promiseA) })
})

fb.startCoroutineCancellable(CancellableContinuation(ctx, connB) { result ->
result.fold({ b ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.success(RaceTriple.Second(Fiber(promiseA, connA), b, Fiber(promiseC, connC))))
} else {
promiseB.complete(Result.success(b))
}
}, { error -> onError(error, connA, connC, promiseB) })
})

fc.startCoroutineCancellable(CancellableContinuation(ctx, connC) { result ->
result.fold({ c ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.success(RaceTriple.Third(Fiber(promiseA, connA), Fiber(promiseB, connB), c)))
// Cancellable connection for the right value
val connC = SuspendConnection()
val promiseC = UnsafePromise<C>()

conn.push(listOf(connA.cancelToken(), connB.cancelToken(), connC.cancelToken()))

fun <A> onError(
error: Throwable,
connB: SuspendConnection,
connC: SuspendConnection,
promise: UnsafePromise<A>
): Unit {
if (active.getAndSet(false)) { // if an error finishes first, stop the race.
connB.cancelToken().cancel.startCoroutine(Continuation(EmptyCoroutineContext) { r2 ->
connC.cancelToken().cancel.startCoroutine(Continuation(EmptyCoroutineContext) { r3 ->
conn.pop()

val errorResult = r2.fold({
r3.fold({ error }, { e3 -> Platform.composeErrors(error, e3) })
}, { e2 ->
r3.fold({ Platform.composeErrors(error, e2) }, { e3 -> Platform.composeErrors(error, e2, e3) })
})

cont.resumeWith(Result.failure(errorResult))
})
})
} else {
promiseC.complete(Result.success(c))
promise.complete(Result.failure(error))
}
}, { error -> onError(error, connA, connB, promiseC) })
})
}
}

fa.startCoroutineCancellable(CancellableContinuation(ctx, connA) { result ->
result.fold({ a ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.success(RaceTriple.First(a, Fiber(promiseB, connB), Fiber(promiseC, connC))))
} else {
promiseA.complete(Result.success(a))
}
}, { error -> onError(error, connB, connC, promiseA) })
})

fb.startCoroutineCancellable(CancellableContinuation(ctx, connB) { result ->
result.fold({ b ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.success(RaceTriple.Second(Fiber(promiseA, connA), b, Fiber(promiseC, connC))))
} else {
promiseB.complete(Result.success(b))
}
}, { error -> onError(error, connA, connC, promiseB) })
})

fc.startCoroutineCancellable(CancellableContinuation(ctx, connC) { result ->
result.fold({ c ->
if (active.getAndSet(false)) {
conn.pop()
cont.resumeWith(Result.success(RaceTriple.Third(Fiber(promiseA, connA), Fiber(promiseB, connB), c)))
} else {
promiseC.complete(Result.success(c))
}
}, { error -> onError(error, connA, connB, promiseC) })
})

COROUTINE_SUSPENDED
}
Loading

0 comments on commit 622114f

Please sign in to comment.