How to catch future failed exception in stream - scala

I am trying to create a stream which polls a rest service and unmarshals the json object.
I have created a source.tick which does a http request every 5 seconds. If this succeeds the HttpResponse will contain an OK. If not the service is unavailable. The result will be send to an actor. See the following code:
def poll(pollActor: ActorRef) {
val source = Source.tick(0.seconds, 3.seconds, HttpRequest(uri = Uri(path = Path("/posts/1"))))
val flow = Http().outgoingConnectionHttps("jsonplaceholder1.typicode.com").mapAsync(1) {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
Unmarshal(entity).to[Item]
case resp # HttpResponse(code, _, _, _) =>
log.warning("Request failed, response code: " + code)
Future.failed(new Exception)
}
source.via(flow).runWith(Sink.actorRef[Equals](pollActor,akka.actor.Status.Success(())))
}
An actor will receive the result from the stream as can be seen in the following code:
def receive = {
case k : Item => println(k)
case f : Failure => {
println("We failed: " + f)
}
}
Where and how should I handle the exception thrown by the future?

One way of approaching this is to make failures an explicit part of your stream.
val flow = Http().outgoingConnectionHttps("jsonplaceholder1.typicode.com").mapAsync(1) {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
Unmarshal(entity).to[Item].map(Right(_))
case resp # HttpResponse(code, _, _, _) =>
Future.successful(Left(MyDomainFailure("some meaningful message/data")))
}
Note that now the type of your flow is
Flow[HttpRequest, Either[MyDomainFailure, Item], Future[OutgoingConnection]]
This has the added value of clarity, making downstream stages aware of the failure and forcing them to handle it (well, in this case not really, because you're using an actor. If you stay within the realm of streams, you will be forced to handle them).
def receive = {
case Right(item) => println(item)
case Left(failure) => {
println("We failed: " + failure.msg)
}
}

This was the fix I used, while it does not produce an exception, the Failure contained in the HttpResponse is simply matched in the receive function.
def poll(pollActor: ActorRef) {
val source = Source.tick(0.seconds, 3.seconds, HttpRequest(uri = Uri(path = Path("/posts/1"))))
val flow = Http().outgoingConnectionHttps("jsonplaceholder1.typicode.com").mapAsync(1) {
// Where able to reach the API.
case HttpResponse(StatusCodes.OK, _, entity, _) =>
// Unmarshal the json response.
Unmarshal(entity).to[Item]
// Failed to reach the API.
case HttpResponse(code, _, _, _) =>
Future.successful(code)
}
source.via(flow).runWith(Sink.actorRef[Any](pollActor,akka.actor.Status.Success(())))
}
Here we match the Failure produced by the HttpResponse.
def receive = {
case item: Item => println(item)
case failure: Failure => {
log.warning("Request failed, response code: " + failure)
}
}

Related

How to implement a retry logic in pattern match?

This is my function that used to call another api, I want to do a retry when I got some specific exception(TimeoutException, etc) for 3 times, how can do that? Thanks for help!
determinationService.getTax(getRequest, runID) match {
case Success(getResponse) =>
LOGGER.info("Following response received from determination service:")
LOGGER.info(getTaxResponse.toString)
...
case Failure(exception) =>
LOGGER.error(
"AppName:{} RunID:{} Grpc error when calling Determination service: {}",
Array(LoggingConstant.APP_NAME, runID, exception): _*
)
//do retry here
}
unit test
val determinationServiceMock = mock[DeterminationService]
doReturn(Failure(new TimeOutException())).when(determinationServiceMock).getTax(any(), any())
Assuming a result type of Unit you could do:
#tailrec
def someDef(retry: Int = 0): Unit = {
determinationService.getTax(getRequest, runID) match {
case Success(getResponse) =>
LOGGER.info("Following response received from determination service:")
LOGGER.info(getTaxResponse.toString)
// ...
case Failure(exception) =>
LOGGER.error(
"AppName:{} RunID:{} Grpc error when calling Determination service: {}",
Array(LoggingConstant.APP_NAME, runID, exception): _*
)
exception match {
case _: TimeoutException if retry < 3 => someDef(retry + 1)
case _ => // ...
}
}
}
Note: your actual code might not be tail-recursive, just this snippet is.

akka- how to ensure all responses of dynamic number of actors are returned to parent actor?

I need to create variable number of actors each time my program starts and then must ensure all responses are return after a period of time. This
link gives a good idea for fixed number of actors but what about dynamic number?
This is my code that creates actor and passes messages to them:
ruleList = ...
val childActorList: Iterable[ActorRef] = ruleList.map(ruleItem =>
context.actorOf(DbActor.props(ruleItem.parameter1, ruleItem.parameter2)))
implicit val timeout = Timeout(10.second)
childActorList.foreach(childActor =>
childActor ? (tempTableName, lastDate)
)
Updated-1
According to #Raman Mishra guides , I updated my code as bellow, this is the code in parent actor:
override val supervisorStrategy: SupervisorStrategy = {
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
case exp: SQLException => //Resume;
throw exp
case exp:AskTimeoutException => throw exp
case other: Exception => throw other
}
}
override def receive: Receive = {
case Start(tempTableName, lastDate) => {
implicit val timeout = Timeout(10.second)
ruleList.foreach { ruleItem =>
val childActor = context.actorOf(DbActor.props(ruleItem._1, query = ruleItem._2))
ask(childActor, (tempTableName, lastDate)).mapTo[Seq[Int]]
onComplete {
lastDate)).mapTo[Seq[Int]] onComplete {
case util.Success(res) => println("done" + res + ruleItem._2)
case util.Failure(exp: AskTimeoutException) => println("Failed query:" + ruleItem._2); throw exp
case other => println(other)
}
}
And in child actor:
case (brokerTableName, lastDate) => {
Logger("Started query by actor" + self.path.name + ':' +
val repo = new Db()
val res = repo.getAggResult(query = (brokerTableName, lastDate))
val resWrapper = res match {
case elem: Future[Any] => elem
case elem:Any => Future(elem)
}
resWrapper pipeTo self
}
case res:List[Map[Any, Any]] => {
// here final result is send to parent actor
repo.insertAggresults(res, aggTableName) pipeTo context.parent
}
Now, whenever I run main app, first, parent actor starts and create child actors and send messages to them using ask method. Child actors do their tasks but the problem here is child actors response never returns back to parent actor and in every run of app, AskTimeoutException occurs. I doubt if the use of onComplete method is correct or not. Any help will be appreciated.
"Updated-2"
I found out the problem is in using context.parent instead of sender(). Also, when I pipe to sender, first part of my result, and the sender ask for second part, the problem is resolved but I don't know what happens here, why Can't I pipe to self and return the final result to parent?
This is the last code:
In parent actor:
override def receive: Receive = {
case Start(tempTableName, lastDate) => {
println("started: called by remote actor")
implicit val timeout = Timeout(5 second)
ruleList.foreach { ruleItem =>
val childActor = context.actorOf(DbActor.props(ruleItem._1, query = ruleItem._2))
ask(childActor, Broker(tempTableName, lastDate)) onComplete {
// (childActor ? Broker(tempTableName, lastDate)).mapTo[Seq[Int]] onComplete {
case util.Success(res: List[Map[Any, Any]]) => (childActor ? res) onComplete {
case util.Success(res: Seq[Any]) => println("Successfull- Num,ber of documents:" + res.length + " " + ruleItem._2)
case util.Failure(exp: AskTimeoutException) => println("Failed for writing - query:" + ruleItem._2); throw exp
}
case util.Failure(exp: AskTimeoutException) => println("Failed for reading - query :" + ruleItem._2); throw exp
case other => println(other)
}
}
}
}
In child actor:
case (brokerTableName, lastDate) => {
Logger("Started query by actor" + self.path.name + ':' +
val repo = new Db()
val res = repo.getAggResult(query = (brokerTableName, lastDate))
val resWrapper = res match {
case elem: Future[Any] => elem
case elem:Any => Future(elem)
}
resWrapper pipeTo sender()
}
case res:List[Map[Any, Any]] => {
// here final result is send to parent actor
repo.insertAggresults(res, aggTableName) pipeTo sender()
}
The reason that replying to sender() works where replying to context.parent does not is that ask creates an temporary actor to handle the response. You need to reply to this temporary actor: the sender (which is different from the parent).
Also it's not clear whether the getAggResult method is blocking. If so this will not help (see here).

Scala warning a type was inferred to be 'AnyVal'

I have this actor with the following recieve code:
override def receive: Receive = {
case RefreshAccessTokenRequest(accountId, Some(refreshToken), _, _) =>
concurApi.refreshAccessToken(accountId, refreshToken) onComplete {
case Success(refreshTokenResult) =>
context.parent ! ConcurResultCallbackInformation(accountId, Right(refreshTokenResult))
accountService.updateAccountOAuth2Credentials(accountId, refreshTokenResult.toOAuth2Credentials) recover {
case ex => logger.error("Error updating account Id: " + accountId + " with new refresh token. message: " + ex.getMessage)
}
context.stop(self)
case Failure(t) =>
context.parent ! ConcurResultCallbackInformation(accountId, Left(t))
context.stop(self)
}
case Cancel => context.stop(self)
case x => logger.error(s"ConcurAccessTokenActor: Got an unknown message $x")
}
I am getting a warning on the bolded part which says:
Warning:(18, 42) a type was inferred to be AnyVal; this may indicate a programming error.
case Success(refreshTokenResult) =>
this is the refreshAccessToken function:
def refreshAccessToken(accountId: String, refreshToken: String)(implicit context: ExecutionContext) : Future[ConcurRefreshTokenResult] = {
val body = s"client_id=${sandboxClientId}"
val futureRes = ws.url(sandboxRefreshTokenFullUrl).withHeaders("Content-Type" -> "application/x-www-form-urlencoded").post(body)
futureRes.flatMap { res =>
res.status match {
case 200 =>
val extract = Try {
res.body.fromJson[ConcurRefreshTokenResult]
}.recover { case ex =>
throw ex
}
Promise.fromTry(extract).future
case 400 =>
Future.failed(new RuntimeException(s"refresh Access Token failed with status 400, and body: ${res.body}"))
case x =>
Future.failed(new RuntimeException(s"refresh Access Token failed with status $x, and body: ${res.body}"))
}
}
}
Any ideas?
Ok I got it..
The warning came from this code part:
case Success(refreshTokenResult) =>
context.parent ! ConcurResultCallbackInformation(accountId, Right(refreshTokenResult))
accountService.updateAccountOAuth2Credentials(accountId, refreshTokenResult.toOAuth2Credentials) recover {
case ex => logger.error("Error updating account Id: " + accountId + " with new refresh token. message: " + ex.getMessage)
}
context.stop(self)
the 'updateAccountOAuth2Credentials' function returns Future[Boolean] yet the recover part returns Unit so it interperts it to AnyVal
I think the only thing that is confusing is the location of the warning arrow since this function appears in the middle between 2 other functions, and has no effects on the return type of the entire case block

http => akka stream => http

I'd like to use akka streams in order to pipe some json webservices together. I'd like to know the best approach to make a stream from an http request and stream chunks to another.
Is there a way to define such a graph and run it instead of the code below?
So far I tried to do it this way, not sure if it is actually really streaming yet:
override def receive: Receive = {
case GetTestData(p, id) =>
// Get the data and pipes it to itself through a message as recommended
// https://doc.akka.io/docs/akka-http/current/client-side/request-level.html
http.singleRequest(HttpRequest(uri = uri.format(p, id)))
.pipeTo(self)
case HttpResponse(StatusCodes.OK, _, entity, _) =>
val initialRes = entity.dataBytes.via(JsonFraming.objectScanner(Int.MaxValue)).map(bStr => ChunkStreamPart(bStr.utf8String))
// Forward the response to next job and pipes the request response to dedicated actor
http.singleRequest(HttpRequest(
method = HttpMethods.POST,
uri = "googl.cm/flow",
entity = HttpEntity.Chunked(ContentTypes.`application/json`,
initialRes)
))
case resp # HttpResponse(code, _, _, _) =>
log.error("Request to test job failed, response code: " + code)
// Discard the flow to avoid backpressure
resp.discardEntityBytes()
case _ => log.warning("Unexpected message in TestJobActor")
}
This should be a graph equivalent to your receive:
Http()
.cachedHostConnectionPool[Unit](uri.format(p, id))
.collect {
case (Success(HttpResponse(StatusCodes.OK, _, entity, _)), _) =>
val initialRes = entity.dataBytes
.via(JsonFraming.objectScanner(Int.MaxValue))
.map(bStr => ChunkStreamPart(bStr.utf8String))
Some(initialRes)
case (Success(resp # HttpResponse(code, _, _, _)), _) =>
log.error("Request to test job failed, response code: " + code)
// Discard the flow to avoid backpressure
resp.discardEntityBytes()
None
}
.collect {
case Some(initialRes) => initialRes
}
.map { initialRes =>
(HttpRequest(
method = HttpMethods.POST,
uri = "googl.cm/flow",
entity = HttpEntity.Chunked(ContentTypes.`application/json`, initialRes)
),
())
}
.via(Http().superPool[Unit]())
The type of this is Flow[(HttpRequest, Unit), (Try[HttpResponse], Unit), HostConnectionPool], where the Unit is a correlation ID you can use if you want to know which request corresponds to the response arrived, and HostConnectionPool materialized value can be used to shut down the connection to the host. Only cachedHostConnectionPool gives you back this materialized value, superPool probably handles this on its own (though I haven't checked). Anyway, I recommend you just use Http().shutdownAllConnectionPools() upon shutdown of your application unless you need otherwise for some reason. In my experience, it's much less error prone (e.g. forgetting the shutdown).
You can also use Graph DSL, to express the same graph:
val graph = Flow.fromGraph(GraphDSL.create() { implicit b =>
import GraphDSL.Implicits._
val host1Flow = b.add(Http().cachedHostConnectionPool[Unit](uri.format(p, id)))
val host2Flow = b.add(Http().superPool[Unit]())
val toInitialRes = b.add(
Flow[(Try[HttpResponse], Unit)]
.collect {
case (Success(HttpResponse(StatusCodes.OK, _, entity, _)), _) =>
val initialRes = entity.dataBytes
.via(JsonFraming.objectScanner(Int.MaxValue))
.map(bStr => ChunkStreamPart(bStr.utf8String))
Some(initialRes)
case (Success(resp # HttpResponse(code, _, _, _)), _) =>
log.error("Request to test job failed, response code: " + code)
// Discard the flow to avoid backpressure
resp.discardEntityBytes()
None
}
)
val keepOkStatus = b.add(
Flow[Option[Source[HttpEntity.ChunkStreamPart, Any]]]
.collect {
case Some(initialRes) => initialRes
}
)
val toOtherHost = b.add(
Flow[Source[HttpEntity.ChunkStreamPart, Any]]
.map { initialRes =>
(HttpRequest(
method = HttpMethods.POST,
uri = "googl.cm/flow",
entity = HttpEntity.Chunked(ContentTypes.`application/json`, initialRes)
),
())
}
)
host1Flow ~> toInitialRes ~> keepOkStatus ~> toOtherHost ~> host2Flow
FlowShape(host1Flow.in, host2Flow.out)
})

How to wrap an REST API client in an Actor

I'm trying to write an actor that calls an HTTP REST API. The rest API needs a query parameter that will be passed from the invoking Actor. The official documentation has an example to achieve the above using a preStart method that pipes the message to itself:
import akka.actor.{ Actor, ActorLogging }
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.{ ActorMaterializer, ActorMaterializerSettings }
import akka.util.ByteString
class Myself extends Actor
with ActorLogging {
import akka.pattern.pipe
import context.dispatcher
final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system))
val http = Http(context.system)
override def preStart() = {
http.singleRequest(HttpRequest(uri = "http://akka.io"))
.pipeTo(self)
}
def receive = {
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body =>
log.info("Got response, body: " + body.utf8String)
}
case resp # HttpResponse(code, _, _, _) =>
log.info("Request failed, response code: " + code)
resp.discardEntityBytes()
}
}
The above works but the URL is hardcoded. What I want to achieve is a REST client actor that I can send parameters as a message to and get back results of the call. I modified the code above to receive parameters as message (pseudo code):
def receive = {
case param: RESTAPIParameter => {
http.singleRequest(HttpRequest(URI("http://my-rest-url").withQuery("name", "value"))
.pipeTo(self)
}
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body =>
log.info("Got response, body: " + body.utf8String)
sender! body.utf8String //Will not work
}
case resp # HttpResponse(code, _, _, _) =>
log.info("Request failed, response code: " + code)
resp.discardEntityBytes()
}
The above should work, but can't really be used to send a response back to the client as the sender reference is lost when the result of REST call is piped back to self.
I guess I could try and store the sender locally in a variable and use it to pass the response back, but I don't think that is a great idea.
So, what is the right way to handle such a scenario?
Edit: The solution suggested below by #PH88 works, but I would like to keep the pattern matching on HttpResponse in the outer loop.
Edit 2: The reason I wanted to pipe the response back to self is because I wanted to implement a state machine ..kind of. The state changes based on the type of message received by the actor. As an example:
The first state could be receiving a query string from a calling actor. The actor invokes the REST api and becomes awaitingResult. Data piped to self for further processing.
When it receives an HTTPResponse with success code, the state becomes dataRecevied. Data is piped to self again for more processing.
The received data is then transformed into internal vendor neutral format and the result is finally sent back to the calling actor.
If the Response code is not successful in 1 above, the state could be changed to HttpError and handled accordingly.
Hope that clarifies the intent. Any other suggestions/designs to achieve a clean/simple design are welcome :-)
You can wrap the HttpResponse with your own case class and bundle the sender with it:
case class ServerResponse(requester: ActorRef, resp: HttpResponse)
then:
def receive = {
case param: RESTAPIParameter => {
val requester = sender
http.singleRequest(HttpRequest(URI("http://my-rest-url").withQuery("name", "value"))
.map(httpResp =>
// This will execute in some other thread, thus
// it's important to NOT use sender directly
ServerResponse(requester, httpResp)
)
.pipeTo(self)
}
case ServerResponse(requester, HttpResponse(...)) =>
val result = ...
requester ! result
...
}
The pipeTo method takes in the sender as an implicit argument:
def pipeTo(recipient: ActorRef)(implicit sender: ActorRef = Actor.noSender): Future[T]
Within an Actor self is defined as an implicit ActorRef:
implicit final val self: ActorRef
Therefore self is the sender that is being specified in pipeTo.
You can simply specify the sender explicitly to be the original sender:
def receive = {
case param: RESTAPIParameter =>
http.singleRequest(HttpRequest(URI("http://my-rest-url").withQuery("name", "value"))
.pipeTo(self)(sender) //specify original sender
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body =>
log.info("Got response, body: " + body.utf8String)
sender! body.utf8String //Works now!
}
case resp # HttpResponse(code, _, _, _) => {
log.info("Request failed, response code: " + code)
resp.discardEntityBytes()
}
}