1

Here is the outline of my code

There are multiple worker actors and I am collecting some latency stats from them into httpActorLatencies map where latency from each worker actor is tracked separately and then logged on receiving LogQueueLatency message. At this point, all the queues in httpActorLatencies are also cleared.

Is there a way to get rid of mutable Map in a reasonable way?

class StatsActor(workerCount: Int) extends Actor {
  val httpActorLatencies = scala.collection.mutable.Map[Int, scala.collection.mutable.MutableList[Long]]()

  override def preStart(): Unit = {
    Range(0, workerCount).foreach(i => httpActorLatencies.put(i, scala.collection.mutable.MutableList()))
  }

  override def receive = {
    case shardLatency: QueuingLatency =>
      httpActorLatencies(shardLatency.shardNumber) += shardLatency.latency

    case LogQueueLatency =>
      outputCollectedStats()
  }

  private def outputCollectedStats(): Unit = {
    output(StatsActor.computePerShardMeanLatencies(httpActorLatencies))
    httpActorLatencies.foreach(x => x._2.clear())
  }
}
Tim
  • 26,753
  • 2
  • 16
  • 29
Asad Iqbal
  • 3,241
  • 4
  • 32
  • 52
  • Just to start, prefer var of mutable collection over a val of a mutable one, as var will be kept on the scope and not leak by message to another actor... https://stackoverflow.com/questions/11386559/val-mutable-versus-var-immutable-in-scala ;) using become, as proposed by Tim answer you can avoid the var all together – FerranJr Aug 22 '18 at 12:00

1 Answers1

4

One way to do this is to use context.become and a receive function that carries the Map with it, like this:

class StatsActor extends Actor {
  def newMap() = Map[Int, List[Long]]().withDefault(Nil)

  def receive: Receive = countingReceive(newMap())

  def countingReceive(httpActorLatencies: Map[Int, List[Long]]): Receive = {
    case shardLatency: QueuingLatency =>
      val newList = httpActorLatencies(shardLatency.shardNumber) :+ shardLatency.latency
      val newMap = httpActorLatencies.updated(shardLatency.shardNumber, newList)

      context.become(countingReceive(newMap))

    case LogQueueLatency =>
      outputCollectedStats(httpActorLatencies)

      context.become(receive)
  }


  private def outputCollectedStats(httpActorLatencies: Map[Int, List[Long]]): Unit = {
    ...
  }
}

This is untested and probably broken, but it should give an idea of how it might be done.

Also note that I have used withDefault on the Map to make the logic simpler and remove the need for a workerCount.

Tim
  • 26,753
  • 2
  • 16
  • 29