Hello,
I have recently worked on some piece of multithreaded code that access
SessionVar concurrently. No rocket science here - one thread sets
SessionVar’s value and other threads possibly reading it at the same time. We can also treat these threads just as actors.
What hit me was that *sometimes*
SessionVar’s value "magically" comes back to the default one although it was already set to some other. We’ve looked at this issue with Antonio and we think that there is a race condition between
is and
apply methods.
doSync wraps the work in
is method but there is no such wrap in
apply. As a result, when
is and
apply executions interleaves,
SessionVar’s value can be brought back to the default one. Here is a spec that occasionally fails because of this issue. It uses 11 threads but we were able to reproduce this issue with just two threads in our code.
import net.liftweb.actor.LAFuture
import net.liftweb.common.Empty
import net.liftweb.http.{SessionVar, S, LiftSession}
class SessionVarRaceSpec extends WebSpec {
object TestSessionVar extends SessionVar[String]("Uninitialized")
val session = new LiftSession("/", "", Empty)
"SessionVar" should {
"let to safely update its state from multiple threads" withSFor("/", session) in {
for (_ <- 1 to 10) {
LAFuture.build {
S.initIfUninitted(session) {
println("TestSessionVar is " + TestSessionVar.is)
}
}
}
LAFuture.build {
S.initIfUninitted(session) {
println("SET TestSessionVar to " + TestSessionVar("SomeValue"))
println("TestSessionVar is " + TestSessionVar.is)
}
}
TestSessionVar.is must eventually(beEqualTo("SomeValue"))
}
}
}
Example of failed execution:
> test:testOnly *.SessionVarRaceSpec
SET TestSessionVar to SomeValue
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
TestSessionVar is Uninitialized
[info] SessionVarRaceSpec
[info]
[info] SessionVar should
[error] x let to safely update its state from multiple threads
[error] 'Uninitialized' is not equal to 'SomeValue' (SessionVarRaceSpec.scala:31)
[info]
[info]
[info]
[info] Total for specification SessionVarRaceSpec
[info] Finished in 4 seconds, 325 ms
[info] 1 example, 1 failure, 0 error
[info]
[error] Failed: Total 1, Failed 1, Errors 0, Passed 0
[error] Failed tests:
[error] com.ontheserverside.lib.SessionVarRaceSpec
[error] (test:testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 7 s, completed Sep 16, 2016 11:01:53 AM
Thanks,
Piotr