Akka演员抛出空指针错误,原因未知

时间:2019-05-12 20:12:14

标签: scala null akka actor

我有一个rest控制器,它调用服务,后者又调用actor从模拟数据库中获取查询。该消息将其发送给actor,但是应用程序在actor可以响应之前崩溃了,并且actor出现了空指针异常。我正在使用akka http作为控制器和路由指令来组成响应。这些是我的依赖项:

"com.typesafe.akka" %% "akka-http"   % "10.1.8",
"com.typesafe.akka" %% "akka-actor"  % "2.5.22",
"com.typesafe.akka" %% "akka-stream" % "2.5.22",
"com.typesafe.akka" %% "akka-http-spray-json" % "10.1.8"




class CacheActor extends Actor {

  val tweetRepositoryInMemory: TweetRepositoryInMemory = new TweetRepositoryInMemory()
  val log = Logging(context.system, this)

  var tweetMap: scala.collection.mutable.Map[String, List[String]] =
    scala.collection.mutable.Map[String, List[String]]()

  // consult the in-memory map, if the username is not found, call the repository, update  the map, and return the tweets
  def queryRepo(username: String): Future[Option[List[String]]] = {
    if (tweetMap isDefinedAt username) {

      return Future(tweetMap.get(username))
    } else {
      var listOfTweetTexts: List[String] = List[String]()

      val queryLimit = 10

      val resultTweets: Future[Seq[Tweet]] = tweetRepositoryInMemory.searchByUserName(username, queryLimit)

      onComplete(resultTweets) {
        case Success(tweets) =>
          for (tweet <- tweets) { listOfTweetTexts ::= tweet.text; }

          tweetMap(username) = listOfTweetTexts

          return Future(Option(listOfTweetTexts))

        case Failure(t) =>
          log.error("An error has occurred: " + t.getMessage)
          return null
      }
    }
    return null
  }

  def receive = {

    case message: TweetQuery => // .take(message.limit)

      val queryResult: Future[Option[List[String]]] = queryRepo(message.userName)

      queryResult onComplete {
        case Success(result) => sender() ! result

        case Failure(t) => log.error("An error has occurred: " + t.getMessage)
      }
  }
}

1 个答案:

答案 0 :(得分:0)

堆栈跟踪会有所帮助,但是我怀疑您receive中的这一行会导致NPE:

queryResult onComplete {

如果未在queryRepo上定义null,则您的tweetMap函数将返回username

更新

这就是原因:

queryRepo函数仅在一种情况下返回Furture[Seq[String]]

if (tweetMap isDefinedAt username) {
  return Future(tweetMap.get(username))
}

在else块中,创建一个Future[Seq[String]]

val resultTweets: Future[Seq[String]] = tweetRepositoryInMemory.searchByUserName(username, queryLimit)

但您永远不会返回。 相反,您将传递给Future的回调,该回调在期货完成时异步执行,因此为onComplete。 (我注意到您不是直接在onComplete上调用Future,而是将未来和部分函数作为参数的函数onComplete,我假设该函数注册了常规onComplete回调。)

因此if语句的else块的结果为Unit,而不是 Future[Seq[String]]

代码

 for (tweet <- tweets) { listOfTweetTexts ::= tweet.text; }

 tweetMap(username) = listOfTweetTexts

 return Future(Option(listOfTweetTexts))

最有可能在queryRepo返回null之后执行。

def queryRepo(username: String): Future[Option[List[String]]] = {
  if (tweetMap isDefinedAt username) {
    ...
  } else {
    ...
  }
  return null // <--- here
}

结束更新

如果您更改以下几行:

val resultTweets: Future[Seq[Tweet]] = tweetRepositoryInMemory.searchByUserName(username, queryLimit)

  onComplete(resultTweets) {
    case Success(tweets) =>
      for (tweet <- tweets) { listOfTweetTexts ::= tweet.text; }

      tweetMap(username) = listOfTweetTexts

      return Future(Option(listOfTweetTexts))

    case Failure(t) =>
      log.error("An error has occurred: " + t.getMessage)
      return null
  }

收件人:

tweetRepositoryInMemory.searchByUserName(username, queryLimit).map { tweets =>
  // NOTE: This happens asynchronously in the `Future`. IT is better not to close over local variables  
  val listOfTweetTexts = for (tweet <- tweets) yield { tweet.text }
  // again, access to an actor member from within a `Future` is not wise or rather a bug in the making. 
  // But I will not refactor that much here. The way to do this would be to send a message to self and process the mutable member within `receive`
  tweetMap(username) = listOfTweetTexts
  Option(listOfTweetTexts)
}

NullPointerException应该不再出现。

但是,我的印象是,您通常可以在scala中对未来演员功能编程进行更多的培训。

例如

  • Actor的可变局部状态仅在从其receive内部而不是在异步FutureThread中访问时才起作用
  • 通常一个人在scala中不使用return关键字
  • 如果不是为了实现Java api的互操作性,则无需永远返回null
  • 还有更多要点...