Skip to content

Commit 2aa2803

Browse files
authored
Fix race condition in new LazyVals (#16975)
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.
2 parents 7869b52 + a43787a commit 2aa2803

File tree

4 files changed

+51
-1
lines changed

4 files changed

+51
-1
lines changed

library/src/scala/runtime/LazyVals.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ object LazyVals {
4545

4646
/* ------------- Start of public API ------------- */
4747

48-
sealed trait LazyValControlState
48+
// This trait extends Serializable to fix #16806 that caused a race condition
49+
sealed trait LazyValControlState extends Serializable
4950

5051
/**
5152
* Used to indicate the state of a lazy val that is being

project/MiMaFilters.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ object MiMaFilters {
2727
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.into"),
2828
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$into$"),
2929
// end of New experimental features in 3.3.X
30+
31+
// Added java.io.Serializable as LazyValControlState supertype
32+
ProblemFilters.exclude[MissingTypesProblem]("scala.runtime.LazyVals$LazyValControlState"),
33+
ProblemFilters.exclude[MissingTypesProblem]("scala.runtime.LazyVals$Waiting"),
34+
3035
)
3136
val TastyCore: Seq[ProblemFilter] = Seq(
3237
ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.TastyBuffer.reset"),

tests/run/i16806.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Success
2+
Success

tests/run/i16806.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//scalajs: --skip
2+
import java.util.concurrent.Semaphore
3+
4+
object Repro {
5+
6+
case object DFBit
7+
final class DFError extends Exception("")
8+
final class DFType[+T](val value: T | DFError) extends AnyVal
9+
10+
def asIR(dfType: DFType[DFBit.type]): DFBit.type = dfType.value match
11+
case dfTypeIR: DFBit.type => dfTypeIR
12+
case err: DFError => throw new DFError
13+
14+
object Holder {
15+
val s = new Semaphore(1, false)
16+
final lazy val Bit = {
17+
s.release()
18+
new DFType[DFBit.type](DFBit)
19+
}
20+
}
21+
22+
@main
23+
def Test =
24+
val a = new Thread() {
25+
override def run(): Unit =
26+
Holder.s.acquire()
27+
val x = Holder.Bit.value
28+
assert(x.isInstanceOf[DFBit.type])
29+
println("Success")
30+
}
31+
val b = new Thread() {
32+
override def run(): Unit =
33+
Holder.s.acquire()
34+
val x = Holder.Bit.value
35+
assert(x.isInstanceOf[DFBit.type])
36+
println("Success")
37+
}
38+
a.start()
39+
b.start()
40+
a.join(300)
41+
b.join(300)
42+
}

0 commit comments

Comments
 (0)