Консольное приложение Scala никогда не завершается в ожидании будущего

Всякий раз, когда я запускаю процесс Scala, который использует результат Future (либо через Await, map, onCompleteи т. д.), он никогда не завершается, вынуждая нас завершить процесс вручную. Это происходит ли я использую extends App или просто стандарт def main(args: Array[String]) метод.

Мне кажется, что это связано с ThreadPoolExecutor что скала раскрутится, чтобы выполнить Future висит в конце функции, но я не могу получить ручку, чтобы закрыть его.

Например, следующий код не сможет выйти:

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))
}

В то время как этот код завершается просто отлично (единственное изменение - версия Async, которая возвращает будущее, которое затем ожидается):

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))
}

Код в AmazonAsyncApiGatewayHelper в конечном итоге создает Future, выполняя HTTP-клиент библиотеки Play. Однако мы видели это при выполнении Futures и другими способами:

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

2 ответа

Решение

Мы закончили тем, что нашли проблему. Для Play wsClient требуется система актера (начиная с Play 2.5). Нам нужно было вручную завершить эту систему акторов, прежде чем мы вышли из нашего основного класса. Выходящий код выглядит так:

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()
 }
}

Вы можете отобразить будущую стоимость и добавив Thread.sleep() в конце.

val result = layersResponse  map { futureValue =>
 Json.parse(layerResponse)
}

Thread.sleep(10000)
println(Json.prettyPrint(result))
Другие вопросы по тегам