Make WorkerBoundReference initially unfrozen (#4288)

This commit is contained in:
Alexander Shabalin
2020-08-03 14:44:45 +03:00
committed by GitHub
parent 34c48f69e3
commit d3d8c8040d
5 changed files with 265 additions and 65 deletions
@@ -14,6 +14,7 @@ import kotlin.text.Regex
class A(var a: Int)
@SharedImmutable
val global1: WorkerBoundReference<A> = WorkerBoundReference(A(3))
@Test
@@ -32,6 +33,7 @@ fun testGlobal() {
worker.requestTermination().result
}
@SharedImmutable
val global2: WorkerBoundReference<A> = WorkerBoundReference(A(3))
@Test
@@ -52,6 +54,7 @@ fun testGlobalDenyAccessOnWorker() {
worker.requestTermination().result
}
@SharedImmutable
val global3: WorkerBoundReference<A> = WorkerBoundReference(A(3).freeze())
@Test
@@ -69,6 +72,7 @@ fun testGlobalAccessOnWorkerFrozenInitially() {
worker.requestTermination().result
}
@SharedImmutable
val global4: WorkerBoundReference<A> = WorkerBoundReference(A(3))
@Test
@@ -86,6 +90,7 @@ fun testGlobalAccessOnWorkerFrozenBeforePassing() {
worker.requestTermination().result
}
@SharedImmutable
val global5: WorkerBoundReference<A> = WorkerBoundReference(A(3))
@Test
@@ -113,6 +118,7 @@ fun testGlobalAccessOnWorkerFrozenBeforeAccess() {
worker.requestTermination().result
}
@SharedImmutable
val global6: WorkerBoundReference<A> = WorkerBoundReference(A(3))
@Test
@@ -140,6 +146,7 @@ fun testGlobalModification() {
worker.requestTermination().result
}
@SharedImmutable
val global7: WorkerBoundReference<A> = WorkerBoundReference(A(3))
@Test
@@ -162,6 +169,13 @@ fun testLocal() {
val local = WorkerBoundReference(A(3))
assertEquals(3, local.value.a)
assertEquals(3, local.valueOrNull?.a)
}
@Test
fun testLocalFrozen() {
val local = WorkerBoundReference(A(3)).freeze()
assertEquals(3, local.value.a)
assertEquals(3, local.valueOrNull?.a)
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
@@ -175,8 +189,8 @@ fun testLocal() {
}
@Test
fun testLocalDenyAccessOnWorker() {
val local = WorkerBoundReference(A(3))
fun testLocalDenyAccessOnWorkerFrozen() {
val local = WorkerBoundReference(A(3)).freeze()
assertEquals(3, local.value.a)
val worker = Worker.start()
@@ -193,8 +207,8 @@ fun testLocalDenyAccessOnWorker() {
}
@Test
fun testLocalAccessOnWorkerFrozenInitially() {
val local = WorkerBoundReference(A(3).freeze())
fun testLocalAccessOnWorkerFrozenInitiallyFrozen() {
val local = WorkerBoundReference(A(3).freeze()).freeze()
assertEquals(3, local.value.a)
assertEquals(3, local.valueOrNull?.a)
@@ -209,8 +223,8 @@ fun testLocalAccessOnWorkerFrozenInitially() {
}
@Test
fun testLocalAccessOnWorkerFrozenBeforePassing() {
val local = WorkerBoundReference(A(3))
fun testLocalAccessOnWorkerFrozenBeforePassingFrozen() {
val local = WorkerBoundReference(A(3)).freeze()
assertEquals(3, local.value.a)
local.value.freeze()
@@ -225,10 +239,10 @@ fun testLocalAccessOnWorkerFrozenBeforePassing() {
}
@Test
fun testLocalAccessOnWorkerFrozenBeforeAccess() {
fun testLocalAccessOnWorkerFrozenBeforeAccessFrozen() {
val semaphore: AtomicInt = AtomicInt(0)
val local = WorkerBoundReference(A(3))
val local = WorkerBoundReference(A(3)).freeze()
assertEquals(3, local.value.a)
val worker = Worker.start()
@@ -250,14 +264,25 @@ fun testLocalAccessOnWorkerFrozenBeforeAccess() {
worker.requestTermination().result
}
@Test
fun testLocalDenyAccessOnMainThread() {
fun testLocalAccessOnMainThread() {
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, {}) {
WorkerBoundReference(A(3))
}
assertEquals(3, future.result.value.a)
worker.requestTermination().result
}
@Test
fun testLocalDenyAccessOnMainThreadFrozen() {
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, {}) {
WorkerBoundReference(A(3)).freeze()
}
val value = future.result
assertFailsWith<IncorrectDereferenceException> {
value.value
@@ -268,10 +293,10 @@ fun testLocalDenyAccessOnMainThread() {
}
@Test
fun testLocalModification() {
fun testLocalModificationFrozen() {
val semaphore: AtomicInt = AtomicInt(0)
val local = WorkerBoundReference(A(3))
val local = WorkerBoundReference(A(3)).freeze()
assertEquals(3, local.value.a)
val worker = Worker.start()
@@ -294,8 +319,8 @@ fun testLocalModification() {
}
@Test
fun testLocalGetWorker() {
val local = WorkerBoundReference(A(3))
fun testLocalGetWorkerFrozen() {
val local = WorkerBoundReference(A(3)).freeze()
val ownerId = Worker.current.id
assertEquals(ownerId, local.worker.id)
@@ -326,9 +351,60 @@ fun testLocalForeignGetWorker() {
worker.requestTermination().result
}
fun getOwnerAndWeaks(initial: Int): Triple<AtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
@Test
fun testLocalForeignGetWorkerFrozen() {
val worker = Worker.start()
val ownerId = worker.id
val future = worker.execute(TransferMode.SAFE, { ownerId }) { ownerId ->
val local = WorkerBoundReference(A(3)).freeze()
assertEquals(ownerId, local.worker.id)
local
}
val value = future.result
assertEquals(ownerId, value.worker.id)
worker.requestTermination().result
}
class Wrapper(val ref: WorkerBoundReference<A>)
@Test
fun testLocalWithWrapperFrozen() {
val local = Wrapper(WorkerBoundReference(A(3))).freeze()
assertEquals(3, local.ref.value.a)
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
local
}
val value = future.result
assertEquals(3, value.ref.value.a)
worker.requestTermination().result
}
@Test
fun testLocalDenyAccessWithWrapperFrozen() {
val local = Wrapper(WorkerBoundReference(A(3))).freeze()
assertEquals(3, local.ref.value.a)
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
assertFailsWith<IncorrectDereferenceException> {
local.ref.value
}
assertEquals(null, local.ref.valueOrNull)
Unit
}
future.result
worker.requestTermination().result
}
fun getOwnerAndWeaks(initial: Int): Triple<FreezableAtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
val ref = WorkerBoundReference(A(initial))
val refOwner: AtomicReference<WorkerBoundReference<A>?> = AtomicReference(ref)
val refOwner: FreezableAtomicReference<WorkerBoundReference<A>?> = FreezableAtomicReference(ref)
val refWeak = WeakReference(ref)
val refValueWeak = WeakReference(ref.value)
@@ -347,8 +423,29 @@ fun testCollect() {
assertNull(refValueWeak.value)
}
fun collectInWorker(worker: Worker, semaphore: AtomicInt): Pair<WeakReference<A>, Future<Unit>> {
val (refOwner, _, refValueWeak) = getOwnerAndWeaks(3)
fun getOwnerAndWeaksFrozen(initial: Int): Triple<AtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
val ref = WorkerBoundReference(A(initial)).freeze()
val refOwner: AtomicReference<WorkerBoundReference<A>?> = AtomicReference(ref)
val refWeak = WeakReference(ref)
val refValueWeak = WeakReference(ref.value)
return Triple(refOwner, refWeak, refValueWeak)
}
@Test
fun testCollectFrozen() {
val (refOwner, refWeak, refValueWeak) = getOwnerAndWeaksFrozen(3)
refOwner.value = null
GC.collect()
// Last reference to WorkerBoundReference is gone, so it and it's referent are destroyed.
assertNull(refWeak.value)
assertNull(refValueWeak.value)
}
fun collectInWorkerFrozen(worker: Worker, semaphore: AtomicInt): Pair<WeakReference<A>, Future<Unit>> {
val (refOwner, _, refValueWeak) = getOwnerAndWeaksFrozen(3)
val future = worker.execute(TransferMode.SAFE, { Pair(refOwner, semaphore) }) { (refOwner, semaphore) ->
semaphore.increment()
@@ -370,25 +467,24 @@ fun collectInWorker(worker: Worker, semaphore: AtomicInt): Pair<WeakReference<A>
}
@Test
fun testCollectInWorker() {
fun testCollectInWorkerFrozen() {
val semaphore: AtomicInt = AtomicInt(0)
val worker = Worker.start()
val (refValueWeak, future) = collectInWorker(worker, semaphore)
val (refValueWeak, future) = collectInWorkerFrozen(worker, semaphore)
semaphore.increment()
future.result
// At this point WorkerBoundReference no longer has a reference, so it's referent is destroyed.
// WorkerBoundReference, so referent is kept alive.
GC.collect()
assertNull(refValueWeak.value)
worker.requestTermination().result
}
fun doNotCollectInWorker(worker: Worker, semaphore: AtomicInt): Future<WorkerBoundReference<A>> {
val ref = WorkerBoundReference(A(3))
fun doNotCollectInWorkerFrozen(worker: Worker, semaphore: AtomicInt): Future<WorkerBoundReference<A>> {
val ref = WorkerBoundReference(A(3)).freeze()
return worker.execute(TransferMode.SAFE, { Pair(ref, semaphore) }) { (ref, semaphore) ->
semaphore.increment()
@@ -401,12 +497,12 @@ fun doNotCollectInWorker(worker: Worker, semaphore: AtomicInt): Future<WorkerBou
}
@Test
fun testDoNotCollectInWorker() {
fun testDoNotCollectInWorkerFrozen() {
val semaphore: AtomicInt = AtomicInt(0)
val worker = Worker.start()
val future = doNotCollectInWorker(worker, semaphore)
val future = doNotCollectInWorkerFrozen(worker, semaphore)
while (semaphore.value < 1) {
}
GC.collect()
@@ -423,9 +519,9 @@ class B1 {
data class B2(val b1: WorkerBoundReference<B1>)
fun createCyclicGarbage(): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
fun createCyclicGarbage(): Triple<FreezableAtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
val ref1 = WorkerBoundReference(B1())
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
val ref1Owner: FreezableAtomicReference<WorkerBoundReference<B1>?> = FreezableAtomicReference(ref1)
val ref1Weak = WeakReference(ref1.value)
val ref2 = WorkerBoundReference(B2(ref1))
@@ -437,26 +533,50 @@ fun createCyclicGarbage(): Triple<AtomicReference<WorkerBoundReference<B1>?>, We
}
@Test
fun doesNotCollectCyclicGarbage() {
fun collectCyclicGarbage() {
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbage()
ref1Owner.value = null
GC.collect()
assertNull(ref1Weak.value)
assertNull(ref2Weak.value)
}
fun createCyclicGarbageFrozen(): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
val ref1 = WorkerBoundReference(B1()).freeze()
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
val ref1Weak = WeakReference(ref1.value)
val ref2 = WorkerBoundReference(B2(ref1)).freeze()
val ref2Weak = WeakReference(ref2.value)
ref1.value.b2 = ref2
return Triple(ref1Owner, ref1Weak, ref2Weak)
}
@Test
fun doesNotCollectCyclicGarbageFrozen() {
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageFrozen()
ref1Owner.value = null
GC.collect()
// If these asserts fail, that means WorkerBoundReference managed to clean up cyclic garbage all by itself.
assertNotNull(ref1Weak.value)
assertNotNull(ref2Weak.value)
}
fun createCrossThreadCyclicGarbage(
fun createCrossThreadCyclicGarbageFrozen(
worker: Worker
): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
val ref1 = WorkerBoundReference(B1())
val ref1 = WorkerBoundReference(B1()).freeze()
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
val ref1Weak = WeakReference(ref1.value)
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
val ref2 = WorkerBoundReference(B2(ref1))
val ref2 = WorkerBoundReference(B2(ref1)).freeze()
Pair(ref2, WeakReference(ref2.value))
}
val (ref2, ref2Weak) = future.result
@@ -467,10 +587,10 @@ fun createCrossThreadCyclicGarbage(
}
@Test
fun doesNotCollectCrossThreadCyclicGarbage() {
fun doesNotCollectCrossThreadCyclicGarbageFrozen() {
val worker = Worker.start()
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbage(worker)
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageFrozen(worker)
ref1Owner.value = null
GC.collect()
@@ -493,11 +613,11 @@ class C1 {
data class C2(val c1: AtomicReference<WorkerBoundReference<C1>>)
fun createCyclicGarbageWithAtomics(): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
val ref1 = WorkerBoundReference(C1())
fun createCyclicGarbageWithAtomicsFrozen(): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
val ref1 = WorkerBoundReference(C1()).freeze()
val ref1Weak = WeakReference(ref1.value)
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1)))
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1))).freeze()
val ref2Weak = WeakReference(ref2.value)
ref1.value.c2 = AtomicReference(ref2)
@@ -511,8 +631,8 @@ fun dispose(refOwner: AtomicReference<WorkerBoundReference<C1>?>) {
}
@Test
fun doesNotCollectCyclicGarbageWithAtomics() {
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomics()
fun doesNotCollectCyclicGarbageWithAtomicsFrozen() {
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomicsFrozen()
ref1Owner.value = null
GC.collect()
@@ -523,8 +643,8 @@ fun doesNotCollectCyclicGarbageWithAtomics() {
}
@Test
fun collectCyclicGarbageWithAtomics() {
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomics()
fun collectCyclicGarbageWithAtomicsFrozen() {
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomicsFrozen()
dispose(ref1Owner)
GC.collect()
@@ -533,14 +653,14 @@ fun collectCyclicGarbageWithAtomics() {
assertNull(ref2Weak.value)
}
fun createCrossThreadCyclicGarbageWithAtomics(
fun createCrossThreadCyclicGarbageWithAtomicsFrozen(
worker: Worker
): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
val ref1 = WorkerBoundReference(C1())
val ref1 = WorkerBoundReference(C1()).freeze()
val ref1Weak = WeakReference(ref1.value)
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1)))
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1))).freeze()
Pair(ref2, WeakReference(ref2.value))
}
val (ref2, ref2Weak) = future.result
@@ -551,10 +671,10 @@ fun createCrossThreadCyclicGarbageWithAtomics(
}
@Test
fun doesNotCollectCrossThreadCyclicGarbageWithAtomics() {
fun doesNotCollectCrossThreadCyclicGarbageWithAtomicsFrozen() {
val worker = Worker.start()
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomics(worker)
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomicsFrozen(worker)
ref1Owner.value = null
GC.collect()
@@ -568,10 +688,10 @@ fun doesNotCollectCrossThreadCyclicGarbageWithAtomics() {
}
@Test
fun collectCrossThreadCyclicGarbageWithAtomics() {
fun collectCrossThreadCyclicGarbageWithAtomicsFrozen() {
val worker = Worker.start()
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomics(worker)
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomicsFrozen(worker)
dispose(ref1Owner)
// This marks C2 as gone on the main thread
@@ -588,11 +708,11 @@ fun collectCrossThreadCyclicGarbageWithAtomics() {
}
@Test
fun concurrentAccess() {
fun concurrentAccessFrozen() {
val workerCount = 10
val workerUnlocker = AtomicInt(0)
val ref = WorkerBoundReference(A(3))
val ref = WorkerBoundReference(A(3)).freeze()
assertEquals(3, ref.value.a)
val workers = Array(workerCount) {
@@ -621,10 +741,10 @@ fun concurrentAccess() {
}
@Test
fun testExceptionMessage() {
fun testExceptionMessageFrozen() {
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, {}) {
WorkerBoundReference(A(3))
WorkerBoundReference(A(3)).freeze()
}
val value = future.result
@@ -638,3 +758,23 @@ fun testExceptionMessage() {
worker.requestTermination().result
}
@Test
fun testDoubleFreeze() {
val ref = WorkerBoundReference(A(3))
val wrapper = Wrapper(ref)
ref.freeze()
ref.freeze()
wrapper.freeze()
}
@Test
fun testDoubleFreezeWithFreezeBlocker() {
val ref = WorkerBoundReference(A(3))
val wrapper = Wrapper(ref)
wrapper.ensureNeverFrozen()
assertFailsWith<FreezingException> {
wrapper.freeze()
}
ref.freeze()
}
+44 -4
View File
@@ -748,17 +748,23 @@ inline void traverseReferredObjects(ObjHeader* obj, func process) {
}
template <typename func>
inline void traverseContainerObjectFields(ContainerHeader* container, func process) {
inline void traverseContainerObjects(ContainerHeader* container, func process) {
RuntimeAssert(!isAggregatingFrozenContainer(container), "Must not be called on such containers");
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
for (int object = 0; object < container->objectCount(); object++) {
traverseObjectFields(obj, process);
for (int i = 0; i < container->objectCount(); ++i) {
process(obj);
obj = reinterpret_cast<ObjHeader*>(
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
}
}
template <typename func>
inline void traverseContainerObjectFields(ContainerHeader* container, func process) {
traverseContainerObjects(container, [process](ObjHeader* obj) {
traverseObjectFields(obj, process);
});
}
template <typename func>
inline void traverseContainerReferredObjects(ContainerHeader* container, func process) {
traverseContainerObjectFields(container, [process](ObjHeader** location) {
@@ -2582,6 +2588,34 @@ void freezeCyclic(ObjHeader* root,
}
}
// These hooks are only allowed to modify `obj` subgraph.
void runFreezeHooks(ObjHeader* obj) {
if (obj->type_info() == theWorkerBoundReferenceTypeInfo) {
WorkerBoundReferenceFreezeHook(obj);
}
}
void runFreezeHooksRecursive(ObjHeader* root) {
KStdUnorderedSet<KRef> seen;
KStdVector<KRef> toVisit;
seen.insert(root);
toVisit.push_back(root);
while (!toVisit.empty()) {
KRef obj = toVisit.back();
toVisit.pop_back();
runFreezeHooks(obj);
traverseReferredObjects(obj, [&seen, &toVisit](ObjHeader* field) {
auto wasNotSeenYet = seen.insert(field).second;
// Only iterating on unseen objects which containers will get frozen by freezeCyclic or freezeAcyclic.
if (wasNotSeenYet && canFreeze(field->container())) {
toVisit.push_back(field);
}
});
}
}
/**
* Theory of operations.
*
@@ -2612,6 +2646,12 @@ void freezeSubgraph(ObjHeader* root) {
ContainerHeader* rootContainer = root->container();
if (isPermanentOrFrozen(rootContainer)) return;
MEMORY_LOG("Run freeze hooks on subgraph of %p\n", root);
// Note: Actual freezing can fail, but these hooks won't be undone, and moreover
// these hooks will run again on a repeated freezing attempt.
runFreezeHooksRecursive(root);
MEMORY_LOG("Freeze subgraph of %p\n", root)
#if USE_GC
+12 -3
View File
@@ -24,9 +24,18 @@ WorkerBoundReference* asWorkerBoundReference(KRef thiz) {
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) {
// DisposeSharedRef is only called when all references to thiz are gone.
auto* holder = asWorkerBoundReference(thiz)->holder;
holder->dispose();
konanDestructInstance(holder);
// Can be null if WorkerBoundReference wasn't frozen.
if (auto* holder = asWorkerBoundReference(thiz)->holder) {
holder->dispose();
konanDestructInstance(holder);
}
}
// Defined in WorkerBoundReference.kt
extern "C" void Kotlin_WorkerBoundReference_freezeHook(KRef thiz);
RUNTIME_NOTHROW void WorkerBoundReferenceFreezeHook(KRef thiz) {
Kotlin_WorkerBoundReference_freezeHook(thiz);
}
extern "C" {
@@ -13,4 +13,6 @@
// all types.
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) NO_INLINE;
RUNTIME_NOTHROW void WorkerBoundReferenceFreezeHook(KRef thiz);
#endif // RUNTIME_SHAREDREF_H
@@ -17,22 +17,22 @@ external private fun derefWorkerBoundReference(ref: NativePtr): Any?
external private fun describeWorkerBoundReference(ref: NativePtr): String
/**
* A frozen shared reference to a Kotlin object.
* A shared reference to a Kotlin object that doesn't freeze the referred object when it gets frozen itself.
*
* Can be safely passed between workers, but [value] can only be accessed on the worker [WorkerBoundReference] was created on,
* unless the referred object is frozen too.
* After freezing can be safely passed between workers, but [value] can only be accessed on
* the worker [WorkerBoundReference] was created on, unless the referred object is frozen too.
*
* Note: Garbage collector currently cannot free any reference cycles with [WorkerBoundReference] in them.
* Note: Garbage collector currently cannot free any reference cycles with frozen [WorkerBoundReference] in them.
* To resolve such cycles consider using [AtomicReference<WorkerBoundReference?>] which can be explicitly
* nulled out.
*/
@Frozen
@NoReorderFields
@ExportTypeInfo("theWorkerBoundReferenceTypeInfo")
public class WorkerBoundReference<out T : Any>(value: T) {
private val ptr = createWorkerBoundReference(value)
private var ptr = NativePtr.NULL
private val ownerName = Worker.current.name
private var valueBeforeFreezing: T? = value
private val valueDescription
get() = describeWorkerBoundReference(ptr)
@@ -48,10 +48,19 @@ public class WorkerBoundReference<out T : Any>(value: T) {
* The referenced value or null if referred object is not frozen and current worker is different from the one created [this].
*/
val valueOrNull: T?
get() = @Suppress("UNCHECKED_CAST") (derefWorkerBoundReference(ptr) as T?)
get() = valueBeforeFreezing ?: @Suppress("UNCHECKED_CAST") (derefWorkerBoundReference(ptr) as T?)
/**
* Worker that [value] is bound to.
*/
val worker: Worker = Worker.current
@ExportForCppRuntime("Kotlin_WorkerBoundReference_freezeHook")
private fun freezeHook() {
// If this hook was already run, do nothing.
if (valueBeforeFreezing == null)
return
ptr = createWorkerBoundReference(valueBeforeFreezing!!)
valueBeforeFreezing = null
}
}