Wrap worker jobs to autoreleasepool
#KT-42822 Fixed.
This commit is contained in:
committed by
Stanislav Erokhin
parent
96d458e35b
commit
47c606f1eb
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user