diff --git a/backend.native/tests/runtime/concurrent/worker_bound_reference0.kt b/backend.native/tests/runtime/concurrent/worker_bound_reference0.kt
index f090c776f08..108ab0a5a43 100644
--- a/backend.native/tests/runtime/concurrent/worker_bound_reference0.kt
+++ b/backend.native/tests/runtime/concurrent/worker_bound_reference0.kt
@@ -14,6 +14,7 @@ import kotlin.text.Regex
class A(var a: Int)
+@SharedImmutable
val global1: WorkerBoundReference = WorkerBoundReference(A(3))
@Test
@@ -32,6 +33,7 @@ fun testGlobal() {
worker.requestTermination().result
}
+@SharedImmutable
val global2: WorkerBoundReference = WorkerBoundReference(A(3))
@Test
@@ -52,6 +54,7 @@ fun testGlobalDenyAccessOnWorker() {
worker.requestTermination().result
}
+@SharedImmutable
val global3: WorkerBoundReference = WorkerBoundReference(A(3).freeze())
@Test
@@ -69,6 +72,7 @@ fun testGlobalAccessOnWorkerFrozenInitially() {
worker.requestTermination().result
}
+@SharedImmutable
val global4: WorkerBoundReference = WorkerBoundReference(A(3))
@Test
@@ -86,6 +90,7 @@ fun testGlobalAccessOnWorkerFrozenBeforePassing() {
worker.requestTermination().result
}
+@SharedImmutable
val global5: WorkerBoundReference = WorkerBoundReference(A(3))
@Test
@@ -113,6 +118,7 @@ fun testGlobalAccessOnWorkerFrozenBeforeAccess() {
worker.requestTermination().result
}
+@SharedImmutable
val global6: WorkerBoundReference = WorkerBoundReference(A(3))
@Test
@@ -140,6 +146,7 @@ fun testGlobalModification() {
worker.requestTermination().result
}
+@SharedImmutable
val global7: WorkerBoundReference = 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 {
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?>, WeakReference>, WeakReference> {
+@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)
+
+@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 {
+ local.ref.value
+ }
+ assertEquals(null, local.ref.valueOrNull)
+ Unit
+ }
+
+ future.result
+ worker.requestTermination().result
+}
+
+fun getOwnerAndWeaks(initial: Int): Triple?>, WeakReference>, WeakReference> {
val ref = WorkerBoundReference(A(initial))
- val refOwner: AtomicReference?> = AtomicReference(ref)
+ val refOwner: FreezableAtomicReference?> = 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, Future> {
- val (refOwner, _, refValueWeak) = getOwnerAndWeaks(3)
+fun getOwnerAndWeaksFrozen(initial: Int): Triple?>, WeakReference>, WeakReference> {
+ val ref = WorkerBoundReference(A(initial)).freeze()
+ val refOwner: AtomicReference?> = 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, Future> {
+ 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
}
@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> {
- val ref = WorkerBoundReference(A(3))
+fun doNotCollectInWorkerFrozen(worker: Worker, semaphore: AtomicInt): Future> {
+ 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)
-fun createCyclicGarbage(): Triple?>, WeakReference, WeakReference> {
+fun createCyclicGarbage(): Triple?>, WeakReference, WeakReference> {
val ref1 = WorkerBoundReference(B1())
- val ref1Owner: AtomicReference?> = AtomicReference(ref1)
+ val ref1Owner: FreezableAtomicReference?> = FreezableAtomicReference(ref1)
val ref1Weak = WeakReference(ref1.value)
val ref2 = WorkerBoundReference(B2(ref1))
@@ -437,26 +533,50 @@ fun createCyclicGarbage(): Triple?>, 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?>, WeakReference, WeakReference> {
+ val ref1 = WorkerBoundReference(B1()).freeze()
+ val ref1Owner: AtomicReference?> = 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?>, WeakReference, WeakReference> {
- val ref1 = WorkerBoundReference(B1())
+ val ref1 = WorkerBoundReference(B1()).freeze()
val ref1Owner: AtomicReference?> = 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>)
-fun createCyclicGarbageWithAtomics(): Triple?>, WeakReference, WeakReference> {
- val ref1 = WorkerBoundReference(C1())
+fun createCyclicGarbageWithAtomicsFrozen(): Triple?>, WeakReference, WeakReference> {
+ 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?>) {
}
@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?>, WeakReference, WeakReference> {
- 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 {
+ wrapper.freeze()
+ }
+ ref.freeze()
+}
diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp
index a2ffc3e82cf..5f9945b4659 100644
--- a/runtime/src/main/cpp/Memory.cpp
+++ b/runtime/src/main/cpp/Memory.cpp
@@ -748,17 +748,23 @@ inline void traverseReferredObjects(ObjHeader* obj, func process) {
}
template
-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(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(
reinterpret_cast(obj) + objectSize(obj));
}
}
+template
+inline void traverseContainerObjectFields(ContainerHeader* container, func process) {
+ traverseContainerObjects(container, [process](ObjHeader* obj) {
+ traverseObjectFields(obj, process);
+ });
+}
+
template
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 seen;
+ KStdVector 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
diff --git a/runtime/src/main/cpp/WorkerBoundReference.cpp b/runtime/src/main/cpp/WorkerBoundReference.cpp
index 2399f11e136..0012212b5d6 100644
--- a/runtime/src/main/cpp/WorkerBoundReference.cpp
+++ b/runtime/src/main/cpp/WorkerBoundReference.cpp
@@ -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" {
diff --git a/runtime/src/main/cpp/WorkerBoundReference.h b/runtime/src/main/cpp/WorkerBoundReference.h
index 1c950dec3f9..eff74e8c0ca 100644
--- a/runtime/src/main/cpp/WorkerBoundReference.h
+++ b/runtime/src/main/cpp/WorkerBoundReference.h
@@ -13,4 +13,6 @@
// all types.
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) NO_INLINE;
+RUNTIME_NOTHROW void WorkerBoundReferenceFreezeHook(KRef thiz);
+
#endif // RUNTIME_SHAREDREF_H
diff --git a/runtime/src/main/kotlin/kotlin/native/concurrent/WorkerBoundReference.kt b/runtime/src/main/kotlin/kotlin/native/concurrent/WorkerBoundReference.kt
index f47378a4d74..ca6e15e88a0 100644
--- a/runtime/src/main/kotlin/kotlin/native/concurrent/WorkerBoundReference.kt
+++ b/runtime/src/main/kotlin/kotlin/native/concurrent/WorkerBoundReference.kt
@@ -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] which can be explicitly
* nulled out.
*/
-@Frozen
@NoReorderFields
@ExportTypeInfo("theWorkerBoundReferenceTypeInfo")
public class WorkerBoundReference(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(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
+ }
}