diff options
author | Prashant Sharma <prashant.s@imaginea.com> | 2015-05-06 16:26:03 +0530 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2015-05-26 18:46:37 +1000 |
commit | a3bb887e0200cf47a1fa2382a18948b3c553cf26 (patch) | |
tree | 8a01ed0ffec7fea0b8bb499bb1a1964d260fc7f3 /test | |
parent | e12ba55589192fc3a3cc7b441569fbcabc04dd33 (diff) | |
download | scala-a3bb887e0200cf47a1fa2382a18948b3c553cf26.tar.gz scala-a3bb887e0200cf47a1fa2382a18948b3c553cf26.tar.bz2 scala-a3bb887e0200cf47a1fa2382a18948b3c553cf26.zip |
SI-7747 Make REPL wrappers serialization friendly
Spark has been shipping a forked version of our REPL for
sometime. We have been trying to fold the patches back into
the mainline so they can defork. This is the last outstanding
issue.
Consider this REPL session:
```
scala> val x = StdIn.readInt
scala> class A(a: Int)
scala> serializedAndExecuteRemotely {
() => new A(x)
}
```
As shown by the enclosed test, the REPL, even with the
Spark friendly option `-Yrepl-class-based`, will re-initialize
`x` on the remote system.
This test simulates this by running a REPL session, and then
deserializing the resulting closure into a fresh classloader
based on the class files generated by that session. Before this
patch, it printed "evaluating x" twice.
This is based on the Spark change described:
https://github.com/mesos/spark/pull/535#discussion_r3541925
A followup commit will avoid the `val lineN$read = ` part if we
import classes or type aliases only.
[Original commit from Prashant Sharma, test case from Jason Zaugg]
Diffstat (limited to 'test')
-rw-r--r-- | test/files/run/repl-serialization.check | 21 | ||||
-rw-r--r-- | test/files/run/repl-serialization.scala | 65 | ||||
-rw-r--r-- | test/files/run/t7747-repl.check | 32 | ||||
-rw-r--r-- | test/files/run/t7747-repl.scala | 2 |
4 files changed, 111 insertions, 9 deletions
diff --git a/test/files/run/repl-serialization.check b/test/files/run/repl-serialization.check new file mode 100644 index 0000000000..c8439ea023 --- /dev/null +++ b/test/files/run/repl-serialization.check @@ -0,0 +1,21 @@ +== evaluating lines +extract: AnyRef => Unit = <function1> + evaluating x +x: Int = 0 +y: Int = <lazy> + evaluating z + evaluating zz +defined class D +z: Int = 0 +zz: Int = 0 +defined object O +defined class A +defined type alias AA +== evaluating lambda + evaluating y + evaluating O + constructing A +== reconstituting into a fresh classloader + evaluating O +== evaluating reconstituted lambda + constructing A diff --git a/test/files/run/repl-serialization.scala b/test/files/run/repl-serialization.scala new file mode 100644 index 0000000000..64915ce51e --- /dev/null +++ b/test/files/run/repl-serialization.scala @@ -0,0 +1,65 @@ +import java.io._ + +import scala.reflect.io.AbstractFile +import scala.tools.nsc.Settings +import scala.tools.nsc.interpreter.IMain +import scala.tools.nsc.util._ +import scala.reflect.internal.util.AbstractFileClassLoader + +object Test { + def main(args: Array[String]) { + run() + } + + def run(): Unit = { + val settings = new Settings() + settings.Yreplclassbased.value = true + settings.usejavacp.value = true + + var imain: IMain = null + object extract extends ((AnyRef) => Unit) with Serializable { + var value: AnyRef = null + + def apply(a: AnyRef) = value = a + } + + val code = + """val x = {println(" evaluating x"); 0 } + |lazy val y = {println(" evaluating y"); 0 } + |class D; val z = {println(" evaluating z"); 0}; val zz = {println(" evaluating zz"); 0} + |object O extends Serializable { val apply = {println(" evaluating O"); 0} } + |class A(i: Int) { println(" constructing A") } + |type AA = A + |extract(() => new AA(x + y + z + zz + O.apply)) + """.stripMargin + + imain = new IMain(settings) + println("== evaluating lines") + imain.directBind("extract", "(AnyRef => Unit)", extract) + code.lines.foreach(imain.interpret) + + val virtualFile: AbstractFile = extract.value.getClass.getClassLoader.asInstanceOf[AbstractFileClassLoader].root + val newLoader = new AbstractFileClassLoader(virtualFile, getClass.getClassLoader) + + def deserializeInNewLoader(string: Array[Byte]): AnyRef = { + val bis = new ByteArrayInputStream(string) + val in = new ObjectInputStream(bis) { + override def resolveClass(desc: ObjectStreamClass) = Class.forName(desc.getName, false, newLoader) + } + in.readObject() + } + def serialize(o: AnyRef): Array[Byte] = { + val bos = new ByteArrayOutputStream() + val out = new ObjectOutputStream(bos) + out.writeObject(o) + out.close() + bos.toByteArray + } + println("== evaluating lambda") + extract.value.asInstanceOf[() => Any].apply() + println("== reconstituting into a fresh classloader") + val reconstituted = deserializeInNewLoader(serialize(extract.value)).asInstanceOf[() => Any] + println("== evaluating reconstituted lambda") + reconstituted.apply() // should not print("evaluating x") a second time + } +} diff --git a/test/files/run/t7747-repl.check b/test/files/run/t7747-repl.check index 105b238d01..6eb8c8cd99 100644 --- a/test/files/run/t7747-repl.check +++ b/test/files/run/t7747-repl.check @@ -112,7 +112,7 @@ scala> 55 ; ((2 + 2)) ; (1, 2, 3) res15: (Int, Int, Int) = (1,2,3) scala> 55 ; (x: Int) => x + 1 ; () => ((5)) -<console>:8: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses +<console>:9: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses 55 ; (x: Int) => x + 1 ;; ^ res16: () => Int = <function0> @@ -258,12 +258,15 @@ class $read extends Serializable { super.<init>; () }; - import $line44.$read.$iw.$iw.BippyBups; - import $line44.$read.$iw.$iw.BippyBups; - import $line45.$read.$iw.$iw.PuppyPups; - import $line45.$read.$iw.$iw.PuppyPups; - import $line46.$read.$iw.$iw.Bingo; - import $line46.$read.$iw.$iw.Bingo; + val $line44$read = $line44.$read.INSTANCE; + import $line44$read.$iw.$iw.BippyBups; + import $line44$read.$iw.$iw.BippyBups; + val $line45$read = $line45.$read.INSTANCE; + import $line45$read.$iw.$iw.PuppyPups; + import $line45$read.$iw.$iw.PuppyPups; + val $line46$read = $line46.$read.INSTANCE; + import $line46$read.$iw.$iw.Bingo; + import $line46$read.$iw.$iw.Bingo; class $iw extends Serializable { def <init>() = { super.<init>; @@ -275,12 +278,23 @@ class $read extends Serializable { }; val $iw = new $iw.<init> } -object $read extends $read { +object $read extends scala.AnyRef { def <init>() = { super.<init>; () - } + }; + val INSTANCE = new $read.<init> } res3: List[Product with Serializable] = List(BippyBups(), PuppyPups(), Bingo()) +scala> :power +** Power User mode enabled - BEEP WHIR GYVE ** +** :phase has been set to 'typer'. ** +** scala.tools.nsc._ has been imported ** +** global._, definitions._ also imported ** +** Try :help, :vals, power.<tab> ** + +scala> intp.lastRequest +res4: $r.intp.Request = Request(line=def $ires3 = intp.global, 1 trees) + scala> :quit diff --git a/test/files/run/t7747-repl.scala b/test/files/run/t7747-repl.scala index 0e64210460..a681eb8851 100644 --- a/test/files/run/t7747-repl.scala +++ b/test/files/run/t7747-repl.scala @@ -65,5 +65,7 @@ object Test extends ReplTest { |case class PuppyPups() |case class Bingo() |List(BippyBups(), PuppyPups(), Bingo()) // show + |:power + |intp.lastRequest |""".stripMargin } |