Skip to content

Commit da56454

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 da56454

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-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

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

0 commit comments

Comments
 (0)