Skip to content

Scala 3 lazy vals are not serialization-safe #20856

Closed
@lbialy

Description

@lbialy

Description

I'm reporting a vulnerability in Scala 3's lazy val implementation concerning Java serialization. JVM can serialize a lazy val field in the "Waiting" state should rhs evaluation take too long. However, because the thread that calls .countDown() isn't present on the recipient machine (the one deserializing the value), the lazy val remains set to Waiting forever, blocking all threads accessing the lazy val field on CountDownLatch#await() call:

"ClusterSystem-akka.actor.default-dispatcher-4" #31 [41987] prio=5 os_prio=31 cpu=52.91ms elapsed=15.05s tid=0x0000000120046a00 nid=41987 waiting on condition  [0x000000017399d000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@21.0.2/Native Method)
	- parking to wait for  <0x000000061ff1fcc0> (a java.util.concurrent.CountDownLatch$Sync)
	at java.util.concurrent.locks.LockSupport.park(java.base@21.0.2/LockSupport.java:221)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@21.0.2/AbstractQueuedSynchronizer.java:754)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(java.base@21.0.2/AbstractQueuedSynchronizer.java:1099)
	at java.util.concurrent.CountDownLatch.await(java.base@21.0.2/CountDownLatch.java:230)
	at ClusterApp$Message.bomb$lzyINIT1(ClusterApp.scala:25)
	at ClusterApp$Message.bomb(ClusterApp.scala:24)
	at ClusterApp$WorkerNode$.apply$$anonfun$2$$anonfun$1(ClusterApp.scala:52)
	at ClusterApp$WorkerNode$$$Lambda/0x00000088014197d8.apply(Unknown Source)
	at akka.actor.typed.internal.BehaviorImpl$ReceiveMessageBehavior.receive(BehaviorImpl.scala:152)
	at akka.actor.typed.Behavior$.interpret(Behavior.scala:282)
	at akka.actor.typed.Behavior$.interpretMessage(Behavior.scala:238)
	at akka.actor.typed.internal.InterceptorImpl$$anon$2.apply(InterceptorImpl.scala:57)
	at akka.actor.typed.internal.adapter.GuardianStopInterceptor.aroundReceive(GuardianStartupBehavior.scala:59)
	at akka.actor.typed.internal.InterceptorImpl.receive(InterceptorImpl.scala:85)
	at akka.actor.typed.Behavior$.interpret(Behavior.scala:282)
	at akka.actor.typed.Behavior$.interpretMessage(Behavior.scala:238)
	at akka.actor.typed.internal.adapter.ActorAdapter.handleMessage(ActorAdapter.scala:133)
	at akka.actor.typed.internal.adapter.ActorAdapter.aroundReceive(ActorAdapter.scala:109)
	at akka.actor.ActorCell.receiveMessage(ActorCell.scala:577)
	at akka.actor.ActorCell.invoke(ActorCell.scala:545)
	at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270)
	at akka.dispatch.Mailbox.run(Mailbox.scala:231)
	at akka.dispatch.Mailbox.exec(Mailbox.scala:243)
	at java.util.concurrent.ForkJoinTask.doExec(java.base@21.0.2/ForkJoinTask.java:387)
	at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(java.base@21.0.2/ForkJoinPool.java:1312)
	at java.util.concurrent.ForkJoinPool.scan(java.base@21.0.2/ForkJoinPool.java:1843)
	at java.util.concurrent.ForkJoinPool.runWorker(java.base@21.0.2/ForkJoinPool.java:1808)
	at java.util.concurrent.ForkJoinWorkerThread.run(java.base@21.0.2/ForkJoinWorkerThread.java:188)

Compiler version

Scala 3.3.1+, probably Scala 3.3.0 too

Minimized code

https://github.com/VirtusLab/scala-3-lazy-val-vs-java-serialization

Workaround

It is possible to resolve this by annotating the lazy val field with @transient.

Expected outcome

We think that it would be a good idea for the compiler to generate all lazy val fields with transient modifier as this implementation will always be prone to this problem, especially given that LazyValControlState extends Serializable since #16806.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions