Skip to content

Commit

Permalink
Fix race condition in new LazyVals (#16975)
Browse files Browse the repository at this point in the history
Resolve #16806 

In the repro, `Evaluating` was generated as a subtype of `Serializable`,
and the type of lazy value was erased to `Serializable` - these together
broke the optimized condition checking if the value is initialized. For
lazy val of type `A` we were checking if `LazyValControlState <: A`, and
if that was not the case, we assumed it would be safe to just generate
the condition `_value.isInstanceOf[A]`. If that condition was true in
runtime, we assumed that the value was already calculated and returned
it. If it was `Evaluating` and `A =:= Serializable`, then we assumed so
falsely. The easiest fix is to just make the `LazyValControlState <:
Serializable`, I will check if it doesn't affect performance.
  • Loading branch information
szymon-rd authored Feb 27, 2023
2 parents 7869b52 + a43787a commit 2aa2803
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 1 deletion.
3 changes: 2 additions & 1 deletion library/src/scala/runtime/LazyVals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ object LazyVals {

/* ------------- Start of public API ------------- */

sealed trait LazyValControlState
// This trait extends Serializable to fix #16806 that caused a race condition
sealed trait LazyValControlState extends Serializable

/**
* Used to indicate the state of a lazy val that is being
Expand Down
5 changes: 5 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ object MiMaFilters {
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.into"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$into$"),
// end of New experimental features in 3.3.X

// Added java.io.Serializable as LazyValControlState supertype
ProblemFilters.exclude[MissingTypesProblem]("scala.runtime.LazyVals$LazyValControlState"),
ProblemFilters.exclude[MissingTypesProblem]("scala.runtime.LazyVals$Waiting"),

)
val TastyCore: Seq[ProblemFilter] = Seq(
ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.TastyBuffer.reset"),
Expand Down
2 changes: 2 additions & 0 deletions tests/run/i16806.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Success
Success
42 changes: 42 additions & 0 deletions tests/run/i16806.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//scalajs: --skip
import java.util.concurrent.Semaphore

object Repro {

case object DFBit
final class DFError extends Exception("")
final class DFType[+T](val value: T | DFError) extends AnyVal

def asIR(dfType: DFType[DFBit.type]): DFBit.type = dfType.value match
case dfTypeIR: DFBit.type => dfTypeIR
case err: DFError => throw new DFError

object Holder {
val s = new Semaphore(1, false)
final lazy val Bit = {
s.release()
new DFType[DFBit.type](DFBit)
}
}

@main
def Test =
val a = new Thread() {
override def run(): Unit =
Holder.s.acquire()
val x = Holder.Bit.value
assert(x.isInstanceOf[DFBit.type])
println("Success")
}
val b = new Thread() {
override def run(): Unit =
Holder.s.acquire()
val x = Holder.Bit.value
assert(x.isInstanceOf[DFBit.type])
println("Success")
}
a.start()
b.start()
a.join(300)
b.join(300)
}

0 comments on commit 2aa2803

Please sign in to comment.