Ryan Gross Ryan Gross - 1 month ago 11
Scala Question

Scala console application never exits when awaiting Futures

Whenever I run a scala process that uses the result of a

Future
(either through
Await
,
map
,
onComplete
, etc.), it never exits, forcing us to kill the process manually. This occurs whether I use
extends App
or just a standard
def main(args: Array[String])
method.

It seems to me that it is related to the
ThreadPoolExecutor
that scala will spin up to execute the
Future
is hanging around at the end of the function, but I can't seem to get a handle to it to close it out.

For example, the following code will fail to exit:

object ExecuteApi extends App with StrictLogging{

lazy val config = StratumConfiguration.setupConfiguration()
lazy val apiEndpoint = config.getProperty("APIEndpoint").split("/").head
lazy val packetApiPath = "packets/getpackets"

val resourceNames = sys.env.getOrElse("ResourceNames", "").stripMargin.split("[\n\\s]+").map(_.trim).filterNot(_.isEmpty)
val searchBody =
s"""{
| "resourceNames": [
| "${(resourceNames).mkString("\",\"")}"
| ]
|}""".stripMargin
logger.info(searchBody)
val responseFuture = AmazonAsyncApiGatewayHelper.executeHttpRequest(apiEndpoint, searchBody, Some(packetApiPath), Some("api"))
val response = Await.result(responseFuture, Duration(25, TimeUnit.SECONDS))
val layerDefinitions = Json.parse(response)
println(Json.prettyPrint(layerDefinitions))
}


While this code exits just fine (the only change is the Async version that returns a future, which is then Awaited) :

object ExecuteAPI extends App with StrictLogging{

lazy val config = Configuration.setupConfiguration()
lazy val apiEndpoint = config.getProperty("APIEndpoint").split("/").head
lazy val packetApiPath = "packets/getpackets"

val resourceNames = sys.env.getOrElse("ResourceNames", "").stripMargin.split("\n").map(_.trim).filterNot(_.isEmpty)

val searchBody =
s"""{
| "resourceNames": [
| "${(resourceNames).mkString("\",\"")}"
| ]
|}""".stripMargin
logger.info(searchBody)
val layersResponse = AmazonapiGatewayHelper.executeHttpRequest(apiEndpoint, searchBody, Some(packetApiPath), Some("api"))
val layerDefinitions = Json.parse(layersResponse)
println(Json.prettyPrint(layerDefinitions))
}


The code in
AmazonAsyncApiGatewayHelper
eventually creates the Future by executing the Play library HTTP client. However, we have seen this when executing Futures in other ways as well:

val request = wsClient.url(fullUrl)
.withRequestTimeout(readTimeout)
val requestWithHeaders = headers.foldLeft(request) { (r, h) =>
r.withHeaders(h)
}
val playResponseFuture = requestWithHeaders.post(requestBody)

Answer

We ended up finding the issue. The Play wsClient requires an actor system (starting in Play 2.5). We needed to manually terminate this actor system before we exited our main class. The code that exits looks like:

object ExecuteAPI extends App with StrictLogging{
 try {
  lazy val config = Configuration.setupConfiguration()
  lazy val apiEndpoint = config.getProperty("APIEndpoint").split("/").head
  lazy val packetApiPath = "packets/getpackets"

  val resourceNames = sys.env.getOrElse("ResourceNames", "").stripMargin.split("\n").map(_.trim).filterNot(_.isEmpty)

  val searchBody =
  s"""{
    |  "resourceNames": [
    |    "${(resourceNames).mkString("\",\"")}"
    |  ]
    |}""".stripMargin
    logger.info(searchBody)
    val responseFuture = AmazonAsyncApiGatewayHelper.executeHttpRequest(apiEndpoint, searchBody, Some(packetApiPath), Some("api"))
    val response = Await.result(responseFuture, Duration(25, TimeUnit.SECONDS))
    val layerDefinitions = Json.parse(response)
    println(Json.prettyPrint(layerDefinitions))
 } finally {
    AmazonAsyncApiGatewayHelper.client.actorSystem.terminate()
 }
}
Comments