Skip to content

Commit

Permalink
[native-mt] Fix ObjC autorelease object leaks with native-mt dispatch…
Browse files Browse the repository at this point in the history
…ers (#2477)


Fixes #2322
  • Loading branch information
andersio authored Jan 19, 2021
1 parent 89a9f7d commit cb19b9a
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 2 deletions.
19 changes: 17 additions & 2 deletions kotlinx-coroutines-core/common/src/EventLoop.common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ internal abstract class EventLoop : CoroutineDispatcher() {
public fun processUnconfinedEvent(): Boolean {
val queue = unconfinedQueue ?: return false
val task = queue.removeFirstOrNull() ?: return false
task.run()
platformAutoreleasePool {
task.run()
}
return true
}
/**
Expand Down Expand Up @@ -271,7 +273,9 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
// then process one event from queue
val task = dequeue()
if (task != null) {
task.run()
platformAutoreleasePool {
task.run()
}
return 0
}
return nextTime
Expand Down Expand Up @@ -526,6 +530,17 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {

internal expect fun createEventLoop(): EventLoop

/**
* Used by Darwin targets to wrap a [Runnable.run] call in an Objective-C Autorelease Pool. It is a no-op on JVM, JS and
* non-Darwin native targets.
*
* Coroutines on Darwin targets can call into the Objective-C world, where a callee may push a to-be-returned object to
* the Autorelease Pool, so as to avoid a premature ARC release before it reaches the caller. This means the pool must
* be eventually drained to avoid leaks. Since Kotlin Coroutines does not use [NSRunLoop], which provides automatic
* pool management, it must manage the pool creation and pool drainage manually.
*/
internal expect inline fun platformAutoreleasePool(crossinline block: () -> Unit)

internal expect fun nanoTime(): Long

internal expect object DefaultExecutor {
Expand Down
2 changes: 2 additions & 0 deletions kotlinx-coroutines-core/js/src/EventLoop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import kotlin.coroutines.*

internal actual fun createEventLoop(): EventLoop = UnconfinedEventLoop()

internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit) = block()

internal actual fun nanoTime(): Long = unsupported()

internal class UnconfinedEventLoop : EventLoop() {
Expand Down
2 changes: 2 additions & 0 deletions kotlinx-coroutines-core/jvm/src/EventLoop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ internal class BlockingEventLoop(

internal actual fun createEventLoop(): EventLoop = BlockingEventLoop(Thread.currentThread())

internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit) = block()

/**
* Processes next event in the current thread's event loop.
*
Expand Down
9 changes: 9 additions & 0 deletions kotlinx-coroutines-core/nativeDarwin/src/EventLoop.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

import kotlinx.cinterop.*

internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit): Unit = autoreleasepool { block() }
40 changes: 40 additions & 0 deletions kotlinx-coroutines-core/nativeDarwin/test/AutoreleaseLeakTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

import kotlin.native.ref.*
import kotlin.native.concurrent.*
import kotlin.native.internal.*
import platform.Foundation.*
import platform.darwin.NSObject
import kotlinx.cinterop.*
import kotlin.test.*

class AutoreleaseLeakTest : TestBase() {
private val testThread = currentThread()

@Test
fun testObjCAutorelease() {
val weakRef = AtomicReference<WeakReference<NSObject>?>(null)

runTest {
withContext(Dispatchers.Default) {
val job = launch {
assertNotEquals(testThread, currentThread())
val objcObj = NSObject()
weakRef.value = WeakReference(objcObj).freeze()

// Emulate an autorelease return value in native code.
objc_retainAutoreleaseReturnValue(objcObj.objcPtr())
}
job.join()
GC.collect()
}

assertNotNull(weakRef.value)
assertNull(weakRef.value?.value)
}
}
}
7 changes: 7 additions & 0 deletions kotlinx-coroutines-core/nativeOther/src/EventLoop.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit): Unit = block()

0 comments on commit cb19b9a

Please sign in to comment.