Skip to content
This repository was archived by the owner on Sep 8, 2022. It is now read-only.

Add a mode to reuse JVM for test execution #99

Merged
merged 3 commits into from
Mar 7, 2018

Conversation

retronym
Copy link
Member

@retronym retronym commented Mar 7, 2018

The cost of VM startup and JIT is the predominant reason that
partest --run is slow. We're much better of using one thread
on the main JVM to execute these tests.

This commit uses such a mode under a non-default option.

Unforked test execution requires some care:

  • we need to check the main JVM is started with the mandated
    immutable System properties (e.g. file.encoding)
  • other test-specific system properties (e.g. partest.output)
    need to be patched into the system property map before
    each test is executed.
  • sys.exit calls need to be intercepted with a security manager
  • stdout / stderr need to be intercepted, both in the Java and
    Scala console printing APIs.

Some tests need to be adapted, in particular those that assume
that java.class.path will contain the test classes.

Tests can also force usage of the legacy forked mode by adding
a .javaopts file with a dummy JVM option.

Also improve recording of test durations and flag slow tests in
console output.

The cost of VM startup and JIT is the predominant reason that
`partest --run` is slow. We're much better of using one thread
on the main JVM to execute these tests.

This commit uses such a mode under a non-default option.

Unforked test execution requires some care:

  - we need to check the main JVM is started with the mandated
    immutable System properties (e.g. file.encoding)
  - other test-specific system properties (e.g. partest.output)
    need to be patched into the system property map before
    each test is executed.
  - sys.exit calls need to be intercepted with a security manager
  - stdout / stderr need to be intercepted, both in the Java and
    Scala console printing APIs.

Some tests need to be adapted, in particular those that assume
that java.class.path will contain the test classes.

Tests can also force usage of the legacy forked mode by adding
a .javaopts file with a dummy JVM option.

Also improve recording of test durations and flag slow tests in
console output.
@retronym retronym requested a review from SethTisue March 7, 2018 12:33
@retronym
Copy link
Member Author

retronym commented Mar 7, 2018

My branch of adapted/deleted tests is over in https://github.com/retronym/scala/tree/topic/partest-apace

With this version of partest, that now completes in 11.5 minutes.

@retronym
Copy link
Member Author

retronym commented Mar 7, 2018

Seems like JVM reuse is the main win. I've also been experimenting with "Global reuse" which I haven't got working yet, but only seems to offer another 1-2m speedup. I've parked that effort in my topic/global-reuse branch.

Copy link
Contributor

@adriaanm adriaanm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some small questions/nitpicks. Won't hold up the PR over those.

def withExtraProperties[A](extra: Map[String, String])(action: => A): A = {
val saved = System.getProperties()
val modified = new java.util.Properties()
saved.stringPropertyNames().forEach((k) => modified.setProperty(k, saved.getProperty(k)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

val modified = saved.clone.asInstanceOf[java.util.Properties]
modified.putAll(extra.asJava)

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've settled on modified.putAll(saved) to avoid the cast after cloning.

val savedOut = System.out
val savedErr = System.err
try {
val charset = Charset.defaultCharset()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Just wondering: how do we know System.out was set to a stream with the encoding returned by Charset.defaultCharset()? Does it matter? It doesn't look like we can recover the info anyway.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point System.out will be the default, as the rest of partest hasn't messed with it.

But looking back at this code, I now realise that one other place in the partest is mutating System.{out,err}:

pushTranscript(args mkString " ")
    val captured = StreamCapture(runCommand(args, logFile))
    if (captured.result) genPass() else {
      logFile appendAll captured.stderr
      genFail("java compilation failed")
    }

So we have a potential race condition if a Java compilation step happens concurrently with a non-forked test execution step.

That wrapping of Javac looks to be a no-op, we fork a process to start javac, so we should just rely on the ProcessLogger to redirect its output to a file. I'll add an assertion to that effect and test things out, and hopefully I'll be able to remove that use of StreamCapture.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we still get Javac compilation errors under --verbose without that StreamCapture. Pushed that change.

base = Some(System.nanoTime())
}

def stop(): Long = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add a quick comment to document the unit

@dwijnand
Copy link
Member

dwijnand commented Mar 7, 2018

nice!

supposedly installing a SecurityManager incurs "substantial filesystem performance penalty" (https://github.com/sbt/sbt/blob/v1.1.1/run/src/main/scala/sbt/TrapExit.scala#L425-L432). did you try either using those overrides? alternatively maybe trapping exit can be opt-in by the (I presume) few partests that use sys.exit?

(edit: more details in the associated PR sbt/sbt#1153)

  - Comment for Stopwatch
  - Delete commented out debug code
  - Refactor properties save/restore
  - DRY up console scraping
@retronym
Copy link
Member Author

retronym commented Mar 7, 2018

supposedly installing a SecurityManager incurs "substantial filesystem performance penalty"

I'm not using the default implementations of checkRead etc, but rather using:

class DelegatingSecurityManager(delegate: SecurityManager) extends SecurityManager {
  ...
  override def checkExec(cmd: String): Unit = if (delegate ne null) delegate.checkExec(cmd)
  override def checkWrite(file: String): Unit = if (delegate ne null) delegate.checkWrite(file)
  override def checkDelete(file: String): Unit = if (delegate ne null) delegate.checkDelete(file)
  override def checkRead(file: String): Unit = if (delegate ne null) delegate.checkRead(file)
  override def checkRead(file: String, context: scala.Any): Unit = if (delegate ne null) delegate.checkRead(file, context)

delegate is null in our case, so we don't have the performance penalty of SecurityMangager.{checkRead, ... } creating Permissions and computing canonical file paths.

We fork a process with a process logger that redirects
stdout and stderr to a log file, so I can't see why this
was ever needed.

Tested:

```
Note: test execution will be non-parallel under -Dpartest.exec.in.process
% scalac t1459/InheritingPrinter.scala t1459/JavaPrinter.java t1459/ScalaPrinter.scala t1459/Test.java t1459/VarArg.java
!! 1 - run/t1459                                 [compilation failed]

% scalac t1459/InheritingPrinter.scala t1459/JavaPrinter.java t1459/ScalaPrinter.scala t1459/Test.java t1459/VarArg.java
JavaPrinter.java:1: error: illegal start of type declaration
public cla ss JavaPrinter implements VarArg {
           ^
one error found

partest --update-check \
  /Users/jz/code/scala/test/files/run/t1459
```
@retronym retronym merged commit fbeb205 into scala:1.1.x Mar 7, 2018
@SethTisue SethTisue removed their request for review March 13, 2018 20:24
lrytz pushed a commit to lrytz/scala-partest that referenced this pull request May 9, 2018
Add a mode to reuse JVM for test execution
lrytz pushed a commit to lrytz/scala-partest that referenced this pull request May 9, 2018
Add a mode to reuse JVM for test execution
val captured = StreamCapture(runCommand(args, logFile))
if (captured.result) genPass() else {
logFile appendAll captured.stderr
if (runCommand(args, logFile)) genPass() else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer the question in the commit message: there were two tickets, on the first someone comments that process #> doesn't do stderr. So StreamCapture came first. The proper fix using process logger came second.

That was exactly five years ago this past US Memorial Day weekend.

I said: In about five years, paulp will levy a commit, "Remove commented code from 2013." I still can't believe it's 2013. It is 2013, right?

You said: I don't recall having to enter 2-0-1-3 on a form this year. Usually I write down the previous year the first few times; after that my brain comes to grips with the annus novus.

It turns out there is no coming to grips.

scala/bug#6289
scala/bug#7003
scala/scala#2591 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the nostalgia trip :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants