`IO`s created with `par` fail with `ClassCastException` if performed within loop
ktonga opened this issue · comments
So it seems something is going on wrong with par
implementation, when performed in isolation it seems to work but it starts having unpredictable behavior when performed multiple consecutive times, I realized about this since I have some property-based test with default sampling of 100 performing some IO using par
combinator.
You can find the test i'm talking about here: https://github.com/ktonga/scala-poc/blob/topic/parallel-io/src/test/scala/com/brickx/autoinvest/ProgramSpec.scala#L26
This tests used to run ok when IO
s were combined using apply2
from default Applicative
instance but started failing after I introduced the use of par
in this WIP PR: ktonga/scala-poc#6
I'll try to add a minimal example that reproduces the bug to this project for easy debugging.
Cheers.
@ktonga Thanks, I will investigate and fix (assuming it's in 8 which has some bug fixes relative to this version).
Thanks John! I haven't tested it on 8 since i found it in an app where I'm only using the port.
@jdegoes I added to scalaz8's RTSSpec
and it failed:
[error] ! multiple par of two values
[error] java.lang.ClassCastException: scala.Tuple2 cannot be cast to java.lang.Integer (file:1)
Awesome, thanks—fix coming shortly.
Apparently not? Latest fixes were backported (I think)
Also, it is exceptionally annoying that specs2 swallows these things.
java.lang.ClassCastException: scala.Tuple2 cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
at scalaz.ioeffect.RTSSpec.$anonfun$testPar$2(RTSTest.scala:376)
at scala.runtime.java8.JFunction0$mcI$sp.apply(JFunction0$mcI$sp.java:12)
at org.specs2.matcher.Expectable.value$lzycompute(Expectable.scala:21)
at org.specs2.matcher.Expectable.value(Expectable.scala:21)
at org.specs2.matcher.EqualityMatcher.apply(EqualityMatcher.scala:27)
at org.specs2.matcher.Expectable.applyMatcher(Expectable.scala:51)
at org.specs2.matcher.MustExpectable.must_$eq$eq$eq(MustExpectable.scala:20)
at scalaz.ioeffect.RTSSpec.$anonfun$testPar$1(RTSTest.scala:377)
at scalaz.ioeffect.RTSSpec.$anonfun$testPar$1$adapted(RTSTest.scala:372)
at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
at scala.collection.immutable.Range.foreach(Range.scala:156)
at scala.collection.TraversableLike.map(TraversableLike.scala:234)
at scala.collection.TraversableLike.map$(TraversableLike.scala:227)
at scalaz.ioeffect.RTSSpec.testPar(RTSTest.scala:372)
at scalaz.ioeffect.RTSSpec.$anonfun$is$46(RTSTest.scala:84)
at org.specs2.specification.AroundTimeout$$anon$1.result$lzycompute$1(ExamplesTimeout.scala:37)
at org.specs2.specification.AroundTimeout$$anon$1.result$1(ExamplesTimeout.scala:37)
at org.specs2.specification.AroundTimeout$$anon$1.$anonfun$around$3(ExamplesTimeout.scala:40)
at org.specs2.matcher.MatchResult$$anon$13.asResult(MatchResult.scala:349)
at org.specs2.execute.AsResult$.apply(AsResult.scala:32)
at org.specs2.specification.AroundTimeout$$anon$1.around(ExamplesTimeout.scala:40)
at org.specs2.specification.Around.apply(Context.scala:137)
at org.specs2.specification.Around.apply$(Context.scala:137)
at org.specs2.specification.AroundTimeout$$anon$1.apply(ExamplesTimeout.scala:35)
at org.specs2.specification.AroundTimeout.upTo(ExamplesTimeout.scala:32)
at org.specs2.specification.AroundTimeout.upTo$(ExamplesTimeout.scala:31)
at scalaz.ioeffect.RTSSpec.upTo(RTSTest.scala:15)
at scalaz.ioeffect.RTSSpec.$anonfun$is$45(RTSTest.scala:84)
at org.specs2.execute.ResultExecution.execute(ResultExecution.scala:23)
at org.specs2.execute.ResultExecution.execute$(ResultExecution.scala:21)
at org.specs2.execute.ResultExecution$.execute(ResultExecution.scala:119)
at org.specs2.execute.Result$$anon$11.asResult(Result.scala:246)
at org.specs2.execute.AsResult$.apply(AsResult.scala:32)
at org.specs2.specification.core.AsExecution$$anon$1.$anonfun$execute$1(AsExecution.scala:15)
at org.specs2.execute.ResultExecution.execute(ResultExecution.scala:23)
at org.specs2.execute.ResultExecution.execute$(ResultExecution.scala:21)
at org.specs2.execute.ResultExecution$.execute(ResultExecution.scala:119)
at org.specs2.execute.Result$$anon$11.asResult(Result.scala:246)
at org.specs2.execute.AsResult$.apply(AsResult.scala:32)
at org.specs2.execute.AsResult$.$anonfun$safely$1(AsResult.scala:40)
at org.specs2.execute.ResultExecution.execute(ResultExecution.scala:23)
at org.specs2.execute.ResultExecution.execute$(ResultExecution.scala:21)
at org.specs2.execute.ResultExecution$.execute(ResultExecution.scala:119)
at org.specs2.execute.AsResult$.safely(AsResult.scala:40)
at org.specs2.specification.core.Execution$.$anonfun$result$1(Execution.scala:305)
at org.specs2.specification.core.Execution$.$anonfun$withEnvSync$3(Execution.scala:323)
at org.specs2.execute.ResultExecution.execute(ResultExecution.scala:23)
at org.specs2.execute.ResultExecution.execute$(ResultExecution.scala:21)
at org.specs2.execute.ResultExecution$.execute(ResultExecution.scala:119)
at org.specs2.execute.Result$$anon$11.asResult(Result.scala:246)
at org.specs2.execute.AsResult$.apply(AsResult.scala:32)
at org.specs2.execute.AsResult$.$anonfun$safely$1(AsResult.scala:40)
at org.specs2.execute.ResultExecution.execute(ResultExecution.scala:23)
at org.specs2.execute.ResultExecution.execute$(ResultExecution.scala:21)
at org.specs2.execute.ResultExecution$.execute(ResultExecution.scala:119)
at org.specs2.execute.AsResult$.safely(AsResult.scala:40)
at org.specs2.specification.core.Execution$.$anonfun$withEnvSync$2(Execution.scala:323)
at org.specs2.specification.core.Execution.$anonfun$startExecution$3(Execution.scala:135)
at scala.util.Success.$anonfun$map$1(Try.scala:251)
at scala.util.Success.map(Try.scala:209)
at scala.concurrent.Future.$anonfun$map$1(Future.scala:289)
at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:29)
at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:29)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Hmm, this is really weird since it passes locally and in Travis CI (in fact I have two tests now that test essentially the same thing). I'll work harder to reproduce this locally.
TIL testOnly -- fullstacktrace
is how to surface stack traces in specs2 .
To reproduce for me
ioeffect/testOnly *RTS* -- fullStackTrace
@inline
final def nextInstr[E](value: Any, stack: Stack): IO[E, Any] =
if (!stack.isEmpty()) stack.pop()(value).asInstanceOf[IO[E, Any]] else null
is where it's happening. I'm guessing a tuple is added to the stack incorrectly.
Some printlns to help...
@inline
final def nextInstr[E](value: Any, stack: Stack): IO[E, Any] = try {
if (!stack.isEmpty()) stack.pop()(value).asInstanceOf[IO[E, Any]] else null
} catch {
case t: ClassCastException =>
println(s"GOT $value in stack $stack")
throw t
}
GOT 1 in stack scalaz.ioeffect.RTS$Stack@5ad19e47
GOT (1,\/-(2)) in stack scalaz.ioeffect.RTS$Stack@5e0b8c09
GOT FlatMap(FlatMap(AsyncEffect(scalaz.ioeffect.RTS$FiberContext$$Lambda$863/110484312@431ccd3f),scalaz.ioeffect.IO$$$Lambda$656/285346270@479d6400),scalaz.ioeffect.IO$$Lambda$926/1075799910@120b03a7) in stack scalaz.ioeffect.RTS$Stack@5e0b8c09
btw, should those var
s not be marked @volatile
?
It was really easy for me to reproduce it, i just ran that single test a few times in a row in both repos and the test failed every single time with class cast exception. Do we have real parallelism in CI? some times with the vm has a tiny cpu the thread pool has only a couple threads. This seems to happen only when a lot of stuff is going on at the same time.
Thanks, this is helpful. I'll make sure I actually fix it this time. 😄
Oh my, this was one of the most fun bugs I've fixed in a long time. PR coming soon. 😄
@ktonga Can you verify?
@jdegoes will do! Thanks.
Raise a PR with the backport and I'll release tomorrow as 2.2.0
Can confirm, bug seems to be fixed in z8. Will work on the backport. Thanks John!
Fixed by #34