SBT hangs waiting for orphaned subprocess to complete - scala

In my SBT (0.13.16) build, I have the following task:
startThing := {
var bin_path = s"${file(".").getAbsolutePath}/bin"
val result = s"$bin_path/start_thing".!
if (result != 0)
throw new RuntimeException("Could not start Thing..")
true
}
And start_thing contains:
(run_subprocess &)
and my build hangs.
I can see that start_thing exits (the process table does not have it as an entry) but adding some printlns to the task shows that it's stuck on val result = s"$bin_path/start_thing".!.
If I kill the run_subprocess process then SBT unblocks and runs normally.
In this particular case, run_subprocess has set up some Kubernetes port-forwarding that needs to be there in order for subsequent tests to work.

Try daemonising the background process like so
(run_subprocess >/dev/null 2>&1 &)
The issue could be output from run_subprocess still going to sbt parent as suggested here.
I was able to replicate the issue in both sbt 0.13.17 and 1.0.2. Daemonising worked in both.

Regardless of my comment, in my case the reason for the hanging was ACTUALLY the potential leaving of open STDOUT , STDERR handles in a daemon started by the script , that is OK:
/usr/local/bin/minio server "$minio_data_dir" > /dev/null 2>&1 & # and start the server
and NOT ok:
/usr/local/bin/minio server "$minio_data_dir" 2>&1 & # and start the server
So the hanging occurred randomly EVEN with the accepted answers start in the background ...
Thus this solution needed NOT, any wrapper bash scripts ... This is how the code looked in the build.sbt
lazy val startLocalS3 = inputKey[Unit]("localS3")
lazy val startLocalS3Task = TaskKey[Unit]("localS3", "create local s3")
lazy val core: Project = project
.in(file("."))
.settings(
name := "rfco-core",
startLocalS3Task := {
val cmd: Seq[String] = Seq("bash" , "-c" , "./CI/start-s3-svr.sh")
import sys.process._
cmd.mkString(" ").!!
},
fork in startLocalS3Task := true,
compile.in(Compile) := (compile in Compile).dependsOn((startLocalS3Task)).value
// you might want to use Test scope ^^ here
)

Related

Is there a simple way to fork a server process in sbt before starting test against that server?

my e2e test task sends some http requests to the server. i want to start that server (Play framework based) on a separate jvm, then start the test which hits the server and let it finish, then stop the server.
i looked through many SO threads so far found these options:
use sbt-sequential
use sbt-revolver
use alias
but in my experiments setting fork doesn't work, i.e. it still blocks execution when server is started
fork := true
fork in run := true
fork in Test := true
fork in IntegrationTest := true
The startServer/stopServer examples in sbt docs are also blocking it seems
I also tried just starting the server in background from shell but server is quickly shut down, similar to this question
nohup sbt -Djline.terminal=jline.UnsupportedTerminal web/run < /dev/null > /tmp/sbt.log 2>&1 &
related questions:
scala sbt test run setup and cleanup command once on multi project
How do I start a server before running a test suite in SBT?
fork doesn't run task in parallel - it just makes sure that tests are run in a separate JVM which helps with things like shutdown webhooks or disconnecting from services that doesn't handle resource release properly (e.g. DB connection that never calls disconnect).
If you want to use the same sbt to start server AND run test against that instance (which sounds like easily breakable antipattern BTW) you can use somethings like:
reStart
it:test
reStop
However that would be tricky because reStart yields immediately so tests would start when the server setup started but not necessarily completed. Race condition, failing tests, or blocking all tests until server finishes starting.
This is why nobody does it. Much easier to handle solution is to:
start the server in test in some beforeAll method and make this method complete only after server is responding to queries
shutdown it in some afterAll method (or somehow handle both of these using something like cats.effect.Resource or similar)
depending on situation:
running tests sequentially to avoid starting two instances at the same time or
generating config for each test so that they could be run in parallel without clashing on ports allocations
Anything else is just a hack that is going to fail sooner rather than later.
answering my own question, what we ended up doing is
use "sbt stage" to create standalone server jar & run script for the Play web app (in web/target/universal/stage/bin/)
create run_integration_tests.sh shell script that starts the server, waits 30 sec and starts test
add runIntegrationTests task in build.sbt which calls run_integration_tests.sh, and add it to it:test
run_integration_tests.sh:
#! /bin/bash
CURDIR=$(pwd)
echo "Starting integration/e2e test runner"
date >runner.log
export JAVA_OPTS="-Dplay.server.http.port=9195 -Dconfig.file=$CURDIR/web/conf/application_test.conf -Xmx2G"
rm -f "$CURDIR/web/target/universal/stage/RUNNING_PID"
echo "Starting server"
nohup web/target/universal/stage/bin/myapp >>runner.log 2>&1 &
echo "Webserver PID is $pid"
echo "Waiting for server start"
sleep 30
echo "Running the tests"
sbt "service/test:run-main com.blah.myapp.E2ETest"
ERR="$?"
echo "Tests Done at $(date), killing server"
kill $pid
echo "Waiting for server exit"
wait $pid
echo "All done"
if [ $ERR -ne 0 ]; then
cat runner.log
exit "$ERR"
fi
build.sbt:
lazy val runIntegrationTests = taskKey[Unit]("Run integration tests")
runIntegrationTests := {
val s: TaskStreams = streams.value
s.log.info("Running integration tests...")
val shell: Seq[String] = Seq("bash", "-c")
val runTests: Seq[String] = shell :+ "./run_integration_tests.sh"
if ((runTests !) == 0) {
s.log.success("Integration tests successful!")
} else {
s.log.error("Integration tests failed!")
throw new IllegalStateException("Integration tests failed!")
}
}
lazy val root = project.in(file("."))
.aggregate(service, web, tools)
.configs(IntegrationTest)
.settings(Defaults.itSettings)
.settings(
publishLocal := {},
publish := {},
(test in IntegrationTest) := (runIntegrationTests dependsOn (test in IntegrationTest)).value
)
calling sbt in CI/jenkins:
sh 'sbt clean coverage test stage it:test'

Run a task after run

I start some docker containers before executing run to start my play-framework project:
run in Compile := (run in Compile dependsOn(dockerComposeUp)).evaluated
Now I'd like to tear down all docker containers using dockerComposeDown when play stops. Any ideas on how to accomplish on this?
I've already gone through Doing something after an input task, but that starts the containers and immediatly stops them again. (In fact it even stops the containers before starting them.) Here is what I tried:
run in Compile := {
(run in Compile dependsOn(dockerComposeUp)).evaluated
dockerComposeDown.value
}
A different approach is to call your docker task sequentially to run task. You could achieve this as described below:
lazy val testPrint = taskKey[Unit]("showTime")
testPrint := {
println("Test print.")
}
lazy val testRun = taskKey[Unit]("test build")
testRun := {
Def.sequential((runMain in Compile).toTask(" com.mycompany.MainClass "), testPrint).value
}
First define the testPrint task which in your case could be the dockerTask and then define testRun which will run both tasks sequentially. To run this just do sbt testRun. After execution it should print out "Test print."

Scala Stdin.readLine() does not seem to work as expected

I am trying to write a simple console client application where i can present some options to the user, get their input and act accordingly. If i run the code through intellij or paste it into the scala console, it works. If i run it through sbt (which is how i really need it to run), i run into all sorts of problems.
I have sbt version 0.13.8, OS is Mac, my build.sbt contains:
scalaVersion := "2.11.6"
fork in run := true
EDIT I started with the minimum scala activator template in case that is useful info in this context
I have simplified the code to barebones,
import scala.io.StdIn._
object TestClient {
def main(args: Array[String]): Unit = {
join()
}
def join(): Unit = {
val name = readLine(s"Enter your name.${System.getProperty("line.separator")}")
name match {
case n: String => println(n)
case o => {
println(s"invalid name ${o}")
join()
};
}
}
}
When go into sbt and from the prompt enter run. one of the following seems to happen
1) I get this exception as soon as i run
Exception in thread "Thread-2" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:569)
at java.lang.StringBuffer.append(StringBuffer.java:369)
at java.io.BufferedReader.readLine(BufferedReader.java:370)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at sbt.BasicIO$$anonfun$processFully$1$$anonfun$apply$8.apply(ProcessImpl.scala:58)
at sbt.BasicIO$$anonfun$processFully$1$$anonfun$apply$8.apply(ProcessImpl.scala:58)
at sbt.BasicIO$.readFully$1(ProcessImpl.scala:63)
at sbt.BasicIO$.processLinesFully(ProcessImpl.scala:69)
at sbt.BasicIO$$anonfun$processFully$1.apply(ProcessImpl.scala:58)
at sbt.BasicIO$$anonfun$processFully$1.apply(ProcessImpl.scala:55)
at sbt.SimpleProcessBuilder$$anonfun$3.apply$mcV$sp(ProcessImpl.scala:354)
at sbt.Spawn$$anon$3.run(ProcessImpl.scala:17)
2)
No memory issues, but getting these messages in an infinite loop, so the readline does not seem to be waiting for any input
background log: info: Enter your name.
background log: info: invalid name null
background log: info: Enter your name.
background log: info: invalid name null
...
Either way I am unable to actually enter any input in the console. Not sure what I'm missing or doing wrong
Put
connectInput in run := true
in your build.sbt. See the official docs for more info on how to correctly handle forks in sbt.

Forking and ordering tests in Sbt 0.13.x

Here is how it was configured for Sbt 0.12.x:
parallelExecution in test := false
testGrouping in Test <<= definedTests in Test map { tests =>
tests.map { test =>
import Tests._
import scala.collection.JavaConversions._
new Group(
name = test.name,
tests = Seq(test),
runPolicy = SubProcess(javaOptions = Seq(
"-server", "-Xms4096m", "-Xms4096m", "-XX:NewSize=3584m",
"-Xss256k", "-XX:+UseG1GC", "-XX:+TieredCompilation",
"-XX:+UseNUMA", "-XX:+UseCondCardMark",
"-XX:-UseBiasedLocking", "-XX:+AlwaysPreTouch") ++
System.getProperties.toMap.map {
case (k, v) => "-D" + k + "=" + v
}))
}.sortWith(_.name < _.name)
}
During migration to Sbt 0.13.x I get the following error:
[error] Could not accept connection from test agent: class java.net.SocketException: socket closed
java.net.SocketException: socket closed
at java.net.DualStackPlainSocketImpl.accept0(Native Method)
at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:398)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
at java.net.ServerSocket.implAccept(ServerSocket.java:530)
at java.net.ServerSocket.accept(ServerSocket.java:498)
at sbt.ForkTests$$anonfun$mainTestTask$1$Acceptor$2$.run(ForkTests.scala:48)
at java.lang.Thread.run(Thread.java:745)
Migration changes are just updates in sbt & plugin versions.
Are there any other approaches to forking and ordering of tests in Sbt 0.13.x to overcome that exception?
Works fine on Linux and Mac OS.
Got error on Windows because of limit of classpath length that prevents launching of test agent instance with following error in System.err:
Error: Could not find or load main class sbt.ForkMain
I also got this error when moving to Scala repo to sbt version sbt.version = 1.3.8 (previously 1.2.8 was ok). Strangely worked fine on my mac, but failed on teamcity linux build agents.
Fix for me was to set
fork := false,
in build.sbt.
Not sure why repo had it previously set to fork := true (guess it was cut/paste from somewhere else as no strong reason for this in this repo), but this change resolved the issue. Locally on my mac also runs a few seconds faster now.
See here for background
https://www.scala-sbt.org/1.0/docs/Forking.html

sbt: suppressing logging prefix in stdout

When using sbt with forking (fork in run := true), every output from my application to stdout is prefixed by [info]; output to stderr is prefixed with [error].
This behavior is somewhat annoying when using a Java logging framework which outputs to stderr. The resulting debug messages typically look like this:
[error] [main] INFO MyClass ...
[error] [main] DEBUG MyClass ...
I would like to suppress these prefixes like when running the code without forking. What I tried:
setting sbt -Dsbt.log.noformat=true in the sbt launch script. But this only removes colored ANSI output; prefixes are still there just without color
setting logLevel in run := Level.Error in build.sbt. This does not seem to have any influence on logging with forking.
Is there any way to suppress the prefixes?
You need to set the output strategy of your project.
In my extended build I have the following settings:
settings = Project.defaultSettings ++ Seq(
fork := true, // Fork to separate process
connectInput in run := true, // Connects stdin to sbt during forked runs
outputStrategy := Some(StdoutOutput) // Get rid of output prefix
// ... other settings
)
Can do
sbt -error ...
and also
sbt -warn ...