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 "Natives.h"
|
||||||
#include "ObjCInterop.h"
|
#include "ObjCInterop.h"
|
||||||
#include "ObjCExportPrivate.h"
|
#include "ObjCExportPrivate.h"
|
||||||
|
#include "ObjCMMAPI.h"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
@@ -309,6 +310,13 @@ id objc_allocWithZone(Class clazz);
|
|||||||
id objc_retain(id ptr);
|
id objc_retain(id ptr);
|
||||||
void objc_release(id ptr);
|
void objc_release(id ptr);
|
||||||
|
|
||||||
|
konan::AutoreleasePool::AutoreleasePool()
|
||||||
|
: handle(objc_autoreleasePoolPush()) {}
|
||||||
|
|
||||||
|
konan::AutoreleasePool::~AutoreleasePool() {
|
||||||
|
objc_autoreleasePoolPop(handle);
|
||||||
|
}
|
||||||
|
|
||||||
void* Kotlin_objc_autoreleasePoolPush() {
|
void* Kotlin_objc_autoreleasePoolPush() {
|
||||||
return objc_autoreleasePoolPush();
|
return objc_autoreleasePoolPush();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,22 @@
|
|||||||
|
|
||||||
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject);
|
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 // KONAN_OBJC_INTEROP
|
||||||
|
|
||||||
#endif // RUNTIME_OBJCMMAPI_H
|
#endif // RUNTIME_OBJCMMAPI_H
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "Exceptions.h"
|
#include "Exceptions.h"
|
||||||
#include "KAssert.h"
|
#include "KAssert.h"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
|
#include "ObjCMMAPI.h"
|
||||||
#include "Runtime.h"
|
#include "Runtime.h"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Worker.h"
|
#include "Worker.h"
|
||||||
@@ -956,6 +957,9 @@ JobKind Worker::processQueueElement(bool blocking) {
|
|||||||
ObjHolder operationHolder, dummyHolder;
|
ObjHolder operationHolder, dummyHolder;
|
||||||
KRef obj = DerefStablePointer(job.executeAfter.operation, operationHolder.slot());
|
KRef obj = DerefStablePointer(job.executeAfter.operation, operationHolder.slot());
|
||||||
try {
|
try {
|
||||||
|
#if KONAN_OBJC_INTEROP
|
||||||
|
konan::AutoreleasePool autoreleasePool;
|
||||||
|
#endif
|
||||||
WorkerLaunchpad(obj, dummyHolder.slot());
|
WorkerLaunchpad(obj, dummyHolder.slot());
|
||||||
} catch (ExceptionObjHolder& e) {
|
} catch (ExceptionObjHolder& e) {
|
||||||
if (errorReporting())
|
if (errorReporting())
|
||||||
@@ -969,6 +973,9 @@ JobKind Worker::processQueueElement(bool blocking) {
|
|||||||
KNativePtr result = nullptr;
|
KNativePtr result = nullptr;
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
try {
|
try {
|
||||||
|
#if KONAN_OBJC_INTEROP
|
||||||
|
konan::AutoreleasePool autoreleasePool;
|
||||||
|
#endif
|
||||||
job.regularJob.function(argument, resultHolder.slot());
|
job.regularJob.function(argument, resultHolder.slot());
|
||||||
argumentHolder.clear();
|
argumentHolder.clear();
|
||||||
// Transfer the result.
|
// Transfer the result.
|
||||||
|
|||||||
Reference in New Issue
Block a user