Make WorkerBoundReference initially unfrozen (#4288)
This commit is contained in:
committed by
GitHub
parent
34c48f69e3
commit
d3d8c8040d
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user