Skip to content

Commit

Permalink
Make Defer#fix safer
Browse files Browse the repository at this point in the history
  • Loading branch information
durban committed May 24, 2024
1 parent 88bbc77 commit bcbc64b
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@ private abstract class RxnCompanionPlatform { this: Rxn.type =>
private[core] final type ExchangerImpl[A, B] = ExchangerImplJs[A, B]

private[core] final type ExStatMap = Map[Exchanger.Key, AnyRef]

@inline
protected[this] final def releaseFence(): Unit =
()

@inline
protected[this] final def acquireFence(): Unit =
()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,21 @@
package dev.tauri.choam
package core

import java.lang.invoke.VarHandle

private abstract class RxnCompanionPlatform { this: Rxn.type =>

private[core] final type ExchangerImpl[A, B] = ExchangerImplJvm[A, B]

private[core] final type ExStatMap = ExchangerImplJvm.StatMap

@inline
protected[this] final def releaseFence(): Unit = {
VarHandle.releaseFence()
}

@inline
protected[this] final def acquireFence(): Unit = {
VarHandle.acquireFence()
}
}
23 changes: 18 additions & 5 deletions core/shared/src/main/scala/dev/tauri/choam/core/Rxn.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1604,12 +1604,25 @@ private sealed abstract class RxnInstances6 extends RxnInstances7 { self: Rxn.ty
final override def defer[A](fa: => Rxn[X, A]): Rxn[X, A] =
self.computed[X, A] { x => fa.provide(x) }
final override def fix[A](fn: Rxn[X, A] => Rxn[X, A]): Rxn[X, A] = {
// This is in effect a "thread-unsafe lazy val";
// we know exactly how `defer` works, so it's safe
// to do this here (and this way we avoid the
// synchronization of an actual lazy val):
// Instead of a `lazy val` (like in the superclass), we just
// do a rel/acq here, because we know exactly how `defer`
// works, and know that `.elem` will be initialized before
// we return from this method. However, we still need the
// fences, to make sure that if the resulting `Rxn[X, A]`
// is published by a race, there is an ordering between
// writing `ref.elem` and reading it.
// TODO: The point of this whole thing is to avoid a
// TODO: `lazy val`, which might block. This way of
// TODO: doing it is correct, but it's unclear if it's
// TODO: faster than a `lazy val`, and also, it could
// TODO: be that in this specific case, a `lazy val`
// TODO: also woudn't block.
val ref = new scala.runtime.ObjectRef[Rxn[X, A]](null)
ref.elem = fn(defer { ref.elem })
ref.elem = fn(defer {
self.acquireFence()
ref.elem
})
self.releaseFence()
ref.elem
}
}
Expand Down

0 comments on commit bcbc64b

Please sign in to comment.