Wrap worker jobs to autoreleasepool

#KT-42822 Fixed.
This commit is contained in:
SvyatoslavScherbina
2020-10-28 19:44:33 +03:00
committed by Stanislav Erokhin
parent 96d458e35b
commit 47c606f1eb
6 changed files with 260 additions and 0 deletions
@@ -0,0 +1,9 @@
#import <Foundation/NSObject.h>
@interface CreateAutoreleaseDeallocated : NSObject
@property BOOL value;
@end;
@interface CreateAutorelease : NSObject
+(void)createAutorelease:(CreateAutoreleaseDeallocated*)deallocated;
@end;
@@ -0,0 +1,198 @@
import kotlin.native.concurrent.*
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test
fun testExecuteInMainPark() =
test(Execute, InMain, Park)
@Test
fun testExecuteInMainProcessQueue() =
test(Execute, InMain, ProcessQueue)
@Test
fun testExecuteInWorkerPark() =
test(Execute, InWorker, Park)
@Test
fun testExecuteInWorkerProcessQueue() =
test(Execute, InWorker, ProcessQueue)
@Test
fun testExecuteInMainToWorkerNoYield() =
test(Execute, InMainToWorker, NoYield)
@Test
fun testExecuteAfter0InMainPark() =
test(ExecuteAfter0, InMain, Park)
@Test
fun testExecuteAfter0InMainProcessQueue() =
test(ExecuteAfter0, InMain, ProcessQueue)
@Test
fun testExecuteAfter0InWorkerPark() =
test(ExecuteAfter0, InWorker, Park)
@Test
fun testExecuteAfter0InWorkerProcessQueue() =
test(ExecuteAfter0, InWorker, ProcessQueue)
@Test
fun testExecuteAfter0InMainToWorkerNoYield() =
test(ExecuteAfter0, InMainToWorker, NoYield)
@Test
fun testExecuteAfter10InMainPark() =
testExecuteAfter10(InMain, Park)
@Test
fun testExecuteAfter10InMainProcessQueue() =
testExecuteAfter10(InMain, ProcessQueue)
@Test
fun testExecuteAfter10InWorkerPark() =
testExecuteAfter10(InWorker, Park)
@Test
fun testExecuteAfter10InWorkerProcessQueue() =
testExecuteAfter10(InWorker, ProcessQueue)
@Test
fun testExecuteAfter10InMainToWorkerNoYield() =
testExecuteAfter10(InMainToWorker, NoYield)
private fun testExecuteAfter10(context: Context, yieldMethod: Yield) =
test(ExecuteAfter10(yieldMethod), context, yieldMethod)
private fun <F> test(method: ExecuteMethod<F>, context: Context, yieldMethod: Yield) {
context.withWorker { worker ->
fun execute(block: () -> Unit) {
val future = method.submit(worker, block)
yieldMethod.yield()
method.wait(future)
}
val deallocated = CreateAutoreleaseDeallocated()
execute {
CreateAutorelease.createAutorelease(deallocated)
// Object is still in autorelease pool:
assertFalse(deallocated.value)
}
// autorelease pool is processed after the job is finished, so the object should be deallocated;
// Checking in a job to make sure previous job is completely processed:
execute {
assertTrue(deallocated.value)
}
}
}
interface ExecuteMethod<F> {
fun submit(worker: Worker, block: () -> Unit): F
fun wait(future: F)
}
object Execute : ExecuteMethod<Future<Unit>> {
override fun submit(worker: Worker, block: () -> Unit) = worker.execute(TransferMode.SAFE, { block.freeze() }) {
it()
}
override fun wait(future: Future<Unit>) {
future.result // Throws on failure.
}
}
abstract class ExecuteAfter : ExecuteMethod<AtomicReference<Any?>> {
abstract val timeout: Long
abstract fun sleepAndYield()
override fun submit(worker: Worker, block: () -> Unit): AtomicReference<Any?> {
val result = AtomicReference<Any?>(null)
worker.executeAfter(timeout, {
try {
block()
result.value = true
} catch (e: Throwable) {
result.value = e.freeze()
}
}.freeze())
return result
}
override fun wait(future: AtomicReference<Any?>) {
while (true) {
sleepAndYield()
when (val it = future.value) {
null -> continue
true -> return
else -> throw it as Throwable
}
}
}
}
object ExecuteAfter0 : ExecuteAfter() {
override val timeout = 0L
override fun sleepAndYield() {
// No sleep or additional yield required.
}
}
class ExecuteAfter10(val yieldMethod: Yield) : ExecuteAfter() {
override val timeout = 10L
override fun sleepAndYield() {
Worker.current.park(timeout + 1L, process = false)
yieldMethod.yield()
}
}
interface Context {
fun withWorker(block: (worker: Worker) -> Unit)
}
object InMain : Context {
override fun withWorker(block: (worker: Worker) -> Unit) = block(Worker.current)
}
object InMainToWorker : Context {
override fun withWorker(block: (Worker) -> Unit) = kotlin.native.concurrent.withWorker {
block(this)
}
}
object InWorker : Context {
override fun withWorker(block: (Worker) -> Unit) = kotlin.native.concurrent.withWorker {
val method = Execute
method.wait(method.submit(this) {
block(this)
})
}
}
interface Yield {
fun yield()
}
object Park : Yield {
override fun yield() {
Worker.current.park(0L, process = true)
}
}
object ProcessQueue : Yield {
override fun yield() {
Worker.current.processQueue()
}
}
object NoYield : Yield {
override fun yield() {}
}
@@ -0,0 +1,22 @@
#import "workerAutoreleasePool.h"
@implementation CreateAutoreleaseDeallocated
@end;
@implementation CreateAutorelease {
CreateAutoreleaseDeallocated* deallocated;
}
+(instancetype)create:(CreateAutoreleaseDeallocated*)deallocated {
CreateAutorelease* result = [self new];
result->deallocated = deallocated;
return result;
}
+(void)createAutorelease:(CreateAutoreleaseDeallocated*)deallocated {
[self create:deallocated];
}
-(void)dealloc {
deallocated.value = YES;
}
@end;
@@ -31,6 +31,7 @@
#include "Natives.h"
#include "ObjCInterop.h"
#include "ObjCExportPrivate.h"
#include "ObjCMMAPI.h"
#include "Types.h"
#include "Utils.h"
@@ -309,6 +310,13 @@ id objc_allocWithZone(Class clazz);
id objc_retain(id ptr);
void objc_release(id ptr);
konan::AutoreleasePool::AutoreleasePool()
: handle(objc_autoreleasePoolPush()) {}
konan::AutoreleasePool::~AutoreleasePool() {
objc_autoreleasePoolPop(handle);
}
void* Kotlin_objc_autoreleasePoolPush() {
return objc_autoreleasePoolPush();
}
@@ -12,6 +12,22 @@
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject);
namespace konan {
class AutoreleasePool {
public:
AutoreleasePool();
~AutoreleasePool();
AutoreleasePool(const AutoreleasePool&) = delete;
AutoreleasePool(AutoreleasePool&&) = delete;
AutoreleasePool& operator=(const AutoreleasePool&) = delete;
AutoreleasePool& operator=(AutoreleasePool&&) = delete;
private:
void* handle;
};
} // namespace konan
#endif // KONAN_OBJC_INTEROP
#endif // RUNTIME_OBJCMMAPI_H
@@ -31,6 +31,7 @@
#include "Exceptions.h"
#include "KAssert.h"
#include "Memory.h"
#include "ObjCMMAPI.h"
#include "Runtime.h"
#include "Types.h"
#include "Worker.h"
@@ -956,6 +957,9 @@ JobKind Worker::processQueueElement(bool blocking) {
ObjHolder operationHolder, dummyHolder;
KRef obj = DerefStablePointer(job.executeAfter.operation, operationHolder.slot());
try {
#if KONAN_OBJC_INTEROP
konan::AutoreleasePool autoreleasePool;
#endif
WorkerLaunchpad(obj, dummyHolder.slot());
} catch (ExceptionObjHolder& e) {
if (errorReporting())
@@ -969,6 +973,9 @@ JobKind Worker::processQueueElement(bool blocking) {
KNativePtr result = nullptr;
bool ok = true;
try {
#if KONAN_OBJC_INTEROP
konan::AutoreleasePool autoreleasePool;
#endif
job.regularJob.function(argument, resultHolder.slot());
argumentHolder.clear();
// Transfer the result.