Skip to content

Commit 45c1a73

Browse files
committed
MPP: Simple JS tests are passing
1 parent 65eff0b commit 45c1a73

File tree

9 files changed

+89
-29
lines changed

9 files changed

+89
-29
lines changed

common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -287,19 +287,6 @@ class CommonCoroutinesTest : TestBase() {
287287
expectUnreached()
288288
}
289289

290-
@Test
291-
fun testCancelManyCompletedAttachedChildren() = runTest {
292-
val parent = launch(coroutineContext) { /* do nothing */ }
293-
val n = 10_000 * stressTestMultiplier
294-
repeat(n) {
295-
// create a child that already completed
296-
val child = launch(coroutineContext, CoroutineStart.UNDISPATCHED) { /* do nothing */ }
297-
// attach it manually
298-
parent.attachChild(child)
299-
}
300-
parent.cancelAndJoin() // cancel parent, make sure no stack overflow
301-
}
302-
303290
@Test
304291
fun testCancelAndJoinChildren() = runTest {
305292
expect(1)

core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutinesTest.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ class CoroutinesTest : TestBase() {
6363
d.await()
6464
}
6565

66+
@Test
67+
fun testCancelManyCompletedAttachedChildren() = runTest {
68+
val parent = launch(coroutineContext) { /* do nothing */ }
69+
val n = 10_000 * stressTestMultiplier
70+
repeat(n) {
71+
// create a child that already completed
72+
val child = launch(coroutineContext, CoroutineStart.UNDISPATCHED) { /* do nothing */ }
73+
// attach it manually
74+
parent.attachChild(child)
75+
}
76+
parent.cancelAndJoin() // cancel parent, make sure no stack overflow
77+
}
78+
6679
private fun throwTestException(): Unit = throw TestException()
6780

6881
private class TestException : Exception {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name" : "kotlinx-coroutines-core-js",
3+
"devDependencies": {
4+
"mocha" : "4.0.1"
5+
}
6+
}

js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,10 @@ private class BlockingCoroutine<T>(
105105
while (true) {
106106
val delay = eventLoop?.processNextEvent() ?: Double.MAX_VALUE
107107
if (isCompleted) break
108-
if (delay > 0) throw IllegalStateException("JS thread cannot be blocked, " +
109-
"runBlocking { ... } cannot be waiting for its completion with timeout")
108+
if (delay > 0) {
109+
throw IllegalStateException("JS thread cannot be blocked, " +
110+
"runBlocking { ... } cannot be waiting for its completion with timeout")
111+
}
110112
}
111113
// process queued events (that could have been added after last processNextEvent and before cancel
112114
if (privateEventLoop) (eventLoop as BlockingEventLoop).apply {

js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ internal class CancellableContinuationImpl<in T>(
256256
@Suppress("UNCHECKED_CAST")
257257
override fun <T> getSuccessfulResult(state: Any?): T =
258258
if (state is CompletedIdempotentResult) state.result as T else state as T
259+
260+
override fun toString(): String =
261+
"CancellableContinuation{${stateString()}}[$delegate]"
259262
}
260263

261264
private class CompletedIdempotentResult(

js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineDispatcher.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ public actual interface Runnable {
7272
}
7373

7474
internal class DispatchTask<in T>(
75-
private val continuation: Continuation<T>,
76-
private val value: Any?, // T | Throwable
77-
private val exception: Boolean,
78-
private val cancellable: Boolean
75+
private val continuation: Continuation<T>,
76+
private val value: Any?, // T | Throwable
77+
private val exception: Boolean,
78+
private val cancellable: Boolean
7979
) : Runnable {
8080
@Suppress("UNCHECKED_CAST")
8181
override fun run() {
@@ -91,6 +91,9 @@ internal class DispatchTask<in T>(
9191
throw RuntimeException("Unexpected exception running $this: $e")
9292
}
9393
}
94+
95+
override fun toString(): String =
96+
"DispatchTask[$continuation, cancellable=$cancellable]"
9497
}
9598

9699
internal class DispatchedContinuation<in T>(

js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/EventLoop.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ private const val RESCHEDULED = 2
4444

4545
internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop {
4646
private val queue = LinkedListHead()
47-
private val delayed = Heap<DelayedTask>()
47+
private var delayed: Heap<DelayedTask>? = null
4848

4949
protected abstract val isCompleted: Boolean
5050

5151
private val nextTime: Double
5252
get() {
5353
if (!queue.isEmpty) return 0.0
54-
val nextDelayedTask = delayed.peek() ?: return Double.MAX_VALUE
54+
val nextDelayedTask = delayed?.peek() ?: return Double.MAX_VALUE
5555
return (nextDelayedTask.time - now()).coerceAtLeast(0.0)
5656
}
5757

@@ -66,7 +66,8 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop {
6666

6767
override fun processNextEvent(): Double {
6868
// queue all delayed tasks that are due to be executed
69-
if (!delayed.isEmpty) {
69+
val delayed = this.delayed
70+
if (delayed?.isEmpty == false) {
7071
val now = now()
7172
while (true) {
7273
delayed.removeFirstIf {
@@ -106,13 +107,14 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop {
106107

107108
private fun scheduleImpl(delayedTask: DelayedTask): Boolean {
108109
if (isCompleted) return false
110+
val delayed = delayed ?: Heap<DelayedTask>().also { this.delayed = it }
109111
delayed.addLast(delayedTask)
110112
return true
111113
}
112114

113115
protected fun rescheduleAllDelayed() {
114116
while (true) {
115-
val delayedTask = delayed.removeFirstOrNull() ?: break
117+
val delayedTask = delayed?.removeFirstOrNull() ?: break
116118
delayedTask.rescheduleOnShutdown()
117119
}
118120
}
@@ -147,16 +149,16 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop {
147149

148150
fun rescheduleOnShutdown() {
149151
if (state != DELAYED) return
150-
if (delayed.remove(this)) {
152+
if (delayed?.remove(this) == true) {
151153
state = RESCHEDULED
152154
handle = DefaultExecutor.schedule(time,this)
153155
} else
154156
state = REMOVED
155157
}
156158

157-
override final fun dispose() {
159+
final override fun dispose() {
158160
when (state) {
159-
DELAYED -> delayed.remove(this)
161+
DELAYED -> delayed?.remove(this)
160162
RESCHEDULED -> DefaultExecutor.removeScheduled(handle)
161163
else -> return
162164
}

js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ public actual object NonDisposableHandle : DisposableHandle {
352352

353353
// --------------- helper classes to simplify job implementation
354354

355+
355356
/**
356357
* A concrete implementation of [Job]. It is optionally a child to a parent job.
357358
* This job is cancelled when the parent is complete, but not vise-versa.
@@ -741,7 +742,7 @@ public open class JobSupport(active: Boolean) : Job {
741742
// must promote to list to correct operate on child lists
742743
if (state is JobNode<*>) {
743744
promoteSingleToNodeList(state)
744-
return@loop // retry
745+
continue@loop // retry
745746
}
746747
// cancel all children in list on exceptional completion
747748
if (proposedUpdate is CompletedExceptionally)
@@ -839,9 +840,9 @@ public open class JobSupport(active: Boolean) : Job {
839840
protected open fun afterCompletion(state: Any?, mode: Int) {}
840841

841842
// for nicer debugging
842-
override final fun toString(): String = "Job{${stateString()}}"
843+
override fun toString(): String = "Job{${stateString()}}"
843844

844-
private fun stateString(): String {
845+
protected fun stateString(): String {
845846
val state = this.state
846847
return when (state) {
847848
is Finishing -> buildString {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package kotlinx.coroutines.experimental.internal
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertFalse
6+
import kotlin.test.assertTrue
7+
8+
class LinkedListTest {
9+
data class IntNode(val i: Int) : LinkedListNode()
10+
11+
@Test
12+
fun testSimpleAddLastRemove() {
13+
val list = LinkedListHead()
14+
assertContents(list)
15+
val n1 = IntNode(1).apply { list.addLast(this) }
16+
assertContents(list, 1)
17+
val n2 = IntNode(2).apply { list.addLast(this) }
18+
assertContents(list, 1, 2)
19+
val n3 = IntNode(3).apply { list.addLast(this) }
20+
assertContents(list, 1, 2, 3)
21+
val n4 = IntNode(4).apply { list.addLast(this) }
22+
assertContents(list, 1, 2, 3, 4)
23+
assertTrue(n1.remove())
24+
assertContents(list, 2, 3, 4)
25+
assertTrue(n3.remove())
26+
assertContents(list, 2, 4)
27+
assertTrue(n4.remove())
28+
assertContents(list, 2)
29+
assertTrue(n2.remove())
30+
assertFalse(n2.remove())
31+
assertContents(list)
32+
}
33+
34+
private fun assertContents(list: LinkedListHead, vararg expected: Int) {
35+
val n = expected.size
36+
val actual = IntArray(n)
37+
var index = 0
38+
list.forEach<IntNode> { actual[index++] = it.i }
39+
assertEquals(n, index)
40+
for (i in 0 until n) assertEquals(expected[i], actual[i], "item i")
41+
assertEquals(expected.isEmpty(), list.isEmpty)
42+
}
43+
}

0 commit comments

Comments
 (0)