Search code examples
scalaconcurrencyakkaakka-httpwrk

Hits more than total requests


So I started learning Scala and Akka actors, Akka-Http. I tried to implement a simple hits counter using Akka Http which counts every hit on the localhost page. I used wrk tool to run 10 threads with 100 connections, after which there is a mismatch between the count and the total requests(Seen on wrk).

This is my code :


object WebServer3 {

  var number: Int = 0


  final case class Inc()
  class ActorClass extends Actor with ActorLogging {

    def receive = {
      case Inc => number = number + 1
    }
  }


  def main(args: Array[String]) {
    implicit val system = ActorSystem("my-system")
    implicit val materializer = ActorMaterializer()
    implicit val executionContext = system.dispatcher

    val actor1 = system.actorOf(Props[ActorClass], "SimpleActor")
    val route =
      path("Counter") {

        get {
          actor1 ! Inc
         complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, s"<h1>You visited $number times</h1>"))
        }
      }

    val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
    println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
    StdIn.readLine() // let it run until user presses return
    bindingFuture
      .flatMap(_.unbind()) // trigger unbinding from the port
      .onComplete(_ => system.terminate()) // and shutdown when done
  }
}

Pardon my immature/amateurish coding skills. I am still learning and I know this has to do with concurrency. But I cannot find a solution yet. Please help.

edit#1 : I tried AtomicInteger too. That did not help. edit#2 : I tried the complete akka-http way with ask and await too. that did not help either.


Solution

  • There are few problems with your code.

    You're defining a case class final case class Inc() but you're sending a companion object actor1 ! Inc. Though, you still match companion object case Inc => and your code works. But it's should not be done this way.

    Other problem, we're storing and modifying and retrieving var number: Int = 0 outside of actor boundaries. And I think this is why you have miscounts. Actor must change only internal state.

    I modified your code by introducing ask pattern so a value can be retrieved from within an actor.

    import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
    import akka.http.scaladsl.Http
    import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
    import akka.http.scaladsl.server.Directives._
    import akka.pattern.ask
    import akka.stream.ActorMaterializer
    import akka.util.Timeout
    
    import scala.concurrent.duration._
    import scala.io.StdIn
    
    object WebServer3 {
    
    
      final case object IncAndGet //not a case class anymore
    
      class ActorClass extends Actor with ActorLogging {
        private var number: Int = 0 //inner state must be private and not accessible from outside of an actor
    
        def receive = {
          case IncAndGet =>
            number += 1
            context.sender() ! number // send current value back to sender
        }
      }
    
      def main(args: Array[String]) {
        implicit val system = ActorSystem("my-system")
        implicit val materializer = ActorMaterializer()
        implicit val executionContext = system.dispatcher
        implicit val timeout: Timeout = 2.seconds
    
        val actor1 = system.actorOf(Props[ActorClass], "SimpleActor")
        val route =
          path("counter") {
            get {
              onComplete((actor1 ? IncAndGet).mapTo[Int]) { number =>
                complete(
                  HttpEntity(ContentTypes.`text/html(UTF-8)`,
                             s"<h1>You visited $number times</h1>"))
              }
            }
          }
    
        val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
        println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
        StdIn.readLine() // let it run until user presses return
        val _ = bindingFuture
          .flatMap(_.unbind()) // trigger unbinding from the port
      }
    }