Skip to content

Commit e4a6b62

Browse files
committed
Fix #20856: Serialize Waiting and Evaluating as if null.
This strategy ensures the "serializability" condition of parallel programs--not to be confused with the data being `java.io.Serializable`. Indeed, if thread A is evaluating the lazy val while thread B attempts to serialize its owner object, there is also an alternative schedule where thread B serializes the owner object *before* A starts evaluating the lazy val. Therefore, forcing B to see the non-evaluating state is correct.
1 parent 4c9cf0a commit e4a6b62

File tree

3 files changed

+84
-2
lines changed

3 files changed

+84
-2
lines changed

library/src/scala/runtime/LazyVals.scala

+18-2
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,29 @@ object LazyVals {
5252
* Used to indicate the state of a lazy val that is being
5353
* evaluated and of which other threads await the result.
5454
*/
55-
final class Waiting extends CountDownLatch(1) with LazyValControlState
55+
final class Waiting extends CountDownLatch(1) with LazyValControlState {
56+
/* #20856 If not fully evaluated yet, serialize as if not-evaluat*ing* yet.
57+
* This strategy ensures the "serializability" condition of parallel
58+
* programs--not to be confused with the data being `java.io.Serializable`.
59+
* Indeed, if thread A is evaluating the lazy val while thread B attempts
60+
* to serialize its owner object, there is also an alternative schedule
61+
* where thread B serializes the owner object *before* A starts evaluating
62+
* the lazy val. Therefore, forcing B to see the non-evaluating state is
63+
* correct.
64+
*/
65+
private def writeReplace(): Any = null
66+
}
5667

5768
/**
5869
* Used to indicate the state of a lazy val that is currently being
5970
* evaluated with no other thread awaiting its result.
6071
*/
61-
object Evaluating extends LazyValControlState
72+
object Evaluating extends LazyValControlState {
73+
/* #20856 If not fully evaluated yet, serialize as if not-evaluat*ing* yet.
74+
* See longer comment in `Waiting.writeReplace()`.
75+
*/
76+
private def writeReplace(): Any = null
77+
}
6278

6379
/**
6480
* Used to indicate the state of a lazy val that has been evaluated to

tests/run/i20856.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
succeeded: BOMB: test

tests/run/i20856.scala

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// scalajs: --skip
2+
3+
import java.io.*
4+
5+
class Message(content: String) extends Serializable:
6+
//@transient
7+
lazy val bomb: String =
8+
Thread.sleep(200)
9+
"BOMB: " + content
10+
11+
object Test:
12+
def serialize(obj: Message): Array[Byte] =
13+
val byteStream = ByteArrayOutputStream()
14+
val objectStream = ObjectOutputStream(byteStream)
15+
try
16+
objectStream.writeObject(obj)
17+
byteStream.toByteArray
18+
finally
19+
objectStream.close()
20+
byteStream.close()
21+
22+
def deserialize(bytes: Array[Byte]): Message =
23+
val byteStream = ByteArrayInputStream(bytes)
24+
val objectStream = ObjectInputStream(byteStream)
25+
try
26+
objectStream.readObject().asInstanceOf[Message]
27+
finally
28+
objectStream.close()
29+
byteStream.close()
30+
31+
def main(args: Array[String]): Unit =
32+
val bytes = locally:
33+
val msg = Message("test")
34+
35+
val touch = Thread(() => {
36+
msg.bomb // start evaluation before serialization
37+
()
38+
})
39+
touch.start()
40+
41+
Thread.sleep(50) // give some time for the fork to start lazy val rhs eval
42+
43+
serialize(msg) // serialize in the meantime so that we capture Waiting state
44+
45+
val deserializedMsg = deserialize(bytes)
46+
47+
@volatile var msg = ""
48+
@volatile var started = false
49+
val read = Thread(() => {
50+
started = true
51+
msg = deserializedMsg.bomb
52+
()
53+
})
54+
read.start()
55+
56+
Thread.sleep(1000)
57+
if !started then throw Exception("wtf")
58+
59+
if !msg.isEmpty() then
60+
println(s"succeeded: $msg")
61+
else
62+
read.interrupt()
63+
throw new AssertionError("failed to read bomb in 1s!")
64+
end main
65+
end Test

0 commit comments

Comments
 (0)