Search code examples
scalaakkaactor

Akka actors always times out waiting for future


I have the following actor as defined below meant to "login" a user.

object AuthenticationActor {
  def props = Props[AuthenticationActor]

  case class LoginUser(id: UUID)
}

class AuthenticationActor @Inject()(cache: CacheApi, userService: UserService) extends Actor{
  import AuthenticationActor._

  def receive = {
    case LoginEmployee(id: UUID) => {
      userService.getUserById(id).foreach {
        case Some(e) => {
          println("Logged user in")
          val sessionId = UUID.randomUUID()
          cache.set(sessionId.toString, e)
          sender() ! Some(e, sessionId)
        }
        case None => println("No user was found")
      }
    }
  }
}

Note: userService.getUserById returns a Future[Option[User]]

And the following very simplistic API cal to it

class EmployeeController @Inject()(@Named("authentication-actor") authActor: ActorRef)(implicit ec: ExecutionContext) extends Controller {

  override implicit val timeout: Timeout = 5.seconds

  def login(id: UUID) = Action.async { implicit request =>
    (authActor ? LoginUser(id)).mapTo[Option[(User, UUID)]].map {
      case Some(authInfo) =>   Ok("Authenticated").withSession(request.session + ("auth" -> authInfo._2.toString))
      case None => Forbidden("Not Authenticated")
    }
  }
}

Both println calls will execute, but login call will always fail saying that the ask has time out. Any suggestions?


Solution

  • When you do such thing (accessing sender within Futures callback) you need to store sender in a val in outter scope when you receive request because it is very likely change before Future completes.

    def receive = {
        case LoginEmployee(id: UUID) => {
          val recipient = sender
    
          userService.getUserById(id).foreach {
            case Some(e) => {
              ...
              recipient ! Some(e, sessionId)
            }
            ...
          }
        }
      }
    

    You also never send a result when user wasn't found.

    What you actually should do here is pipe the Future result to the sender

    def receive = {
      case LoginEmployee(id: UUID) => {
        userService.getUserById(id) map { _.map { e =>
            val sessionId = UUID.randomUUID()
            cache.set(sessionId.toString, e)
            (e, sessionId)
          }
        } pipeTo sender
      }
    }
    

    or with prints

    def receive = {
      case LoginEmployee(id: UUID) => {
        userService.getUserById(id) map { 
          case Some(e) =>
            println("logged user in")
            val sessionId = UUID.randomUUID()
            cache.set(sessionId.toString, e)
            Some(e, sessionId)
          case None =>
            println("user not found")
            None
        } pipeTo sender
      }
    }