I am building a scala application. Within the application, we are making a call to an external service, and fetching the data.
When I am hitting the endpoint of this external service using the
postman, I am getting the complete data, around 9000 lines of JSON
data, within 9 seconds.
But when I am hitting the same endpoint through my scala application, I am getting a 200 OK response, but getting the below error:
[WARN] [06/09/2022 18:05:45.765] [default-akka.actor.default-dispatcher-9] [default/Pool(shared->http://ad-manager-api-production.ap-south-1.elasticbeanstalk.com:80)] [4 (WaitingForResponseEntitySubscription)] Response entity was not subscribed after 100 seconds. Make sure to read the response entity body or call `discardBytes()` on it. GET /admin/campaigns Empty -> 200 OK Chunked
I read about it and found that we can set response-entity-subscription-timeout property to a higher value. I set it to about 100 seconds, but this does not seem to help.
My Code:
private val sendAndReceive = customSendAndReceive.getOrElse(HttpClientUtils.singleRequest)
.
.
.
def getActiveCampaigns: GetActiveCampaigns = () => {
val request = HttpRequest(
uri = s"$endpoint/admin/campaigns?status=PUBLISHED", // includes both PUBLISHED_READY and PUBLISHED_PAUSED
method = HttpMethods.GET,
headers = heathers
)
sendAndReceive(request).timed(getActiveCampaignsTimer).flatMap {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
Unmarshal(entity).to[List[CampaignListDetailsDto]]
case response#HttpResponse(_, _, _, _) =>
response.discardEntityBytes()
Future.failed(new RuntimeException(s"Ad manager service exception: $response"))
case response =>
log.error(s"Error calling ad manager service: $response")
response.discardEntityBytes()
Future.failed(new RuntimeException(s"Ad manager service exception: $response"))
}
}
.
.
.
def getCampaignSpendData(getActiveCampaigns: GetActiveCampaigns, getCampaignTotalSpend: GetCampaignTotalSpend)(implicit ec: ExecutionContext): GetCampaignsSpendData = () => {
getActiveCampaigns()
.andThen {
case Failure(t) => log.error("Failed to fetch ads from ad manager", t)
}
.flatMap {
campaignList => Future.sequence(campaignList.map(campaign => budgetSpendPercentage(getCampaignTotalSpend)(campaign)))
}
}
Questions
What does this error exactly mean? Is it that it is able to connect to the endpoint but not able to get the complete data from it before the connection is closed/reset?
How can we address this issue?
I'm using Akka HTTP in a project and for certain flows, we have external infrastructure that can easily become overloaded, throwing back HTTP 500-level errors when we do POST requests (which in our case are idempotent). In these instances, we retry the request like so:
RetryFlow.withBackoff(minBackoff = retryMin.seconds, maxBackoff = retryMax.seconds, randomFactor = 0d, maxRetries = numRetries, httpReqRespFlow)(
decideRetry = { (request, response) =>
response.httpResponse match {
case Success(httpResponse: HttpResponse) =>
if(httpResponse.status.isSuccess()) {
None
} else {
processHttpResponseFailure(request, httpResponse)
}
case Failure(exception: Exception) =>
logger.error(s"Retrying HTTP request from ${response.httpResponse} future failure", exception)
response.httpResponse.map(_.discardEntityBytes())
Some(request)
}
}
)
}.log("Backoff HTTP request")
I understand that Akka HTTP will backpressure if the entityBytes are not consumed or discarded but is there any way for me to force the flow to backpressure when we get these load-related errors? There will be several streams of execution, and ideally, I'd like the whole system to "throttle back" if it starts to overload the downstream system.
I'm using the AsyncHttpClient in http4s-0.19.0-M2 to make a client-call:
for {
resp <- http.expectOr[String](GET(url)){ error =>
error.as[String].map(body => throw new Exception(...)
}
_ <- doSomethingWithResponse(resp)
} yield ()
Occassiaonally the remote end times out, and I see the following in the log:
java.util.concurrent.TimeoutException: Request timeout to remote.server.com after 60000 ms
at org.asynchttpclient.netty.timeout.TimeoutTimerTask.expire(TimeoutTimerTask.java:43)
at org.asynchttpclient.netty.timeout.RequestTimeoutTimerTask.run(RequestTimeoutTimerTask.java:50)
at shade.cda.io.netty.util.HashedWheelTimer$HashedWheelTimeout.expire(HashedWheelTimer.java:670)
at shade.cda.io.netty.util.HashedWheelTimer$HashedWheelBucket.expireTimeouts(HashedWheelTimer.java:745)
at shade.cda.io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:473)
at shade.cda.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
However, it looks like doSomethingWithResponse() is still invoked, but with a partial resp string. Is there a way to change this behavior so that the http.expectOr call fails if it can't retrieve the entire payload?
In my repository function, I'm reading a User, then updating that user:
def update(u: User): Future[Int] = {
this.read(u.id).flatMap {
case Some(existingUser) =>
db.run(
userTable
.filter(_.id === user.id)
.update(user.copy(createdDate = existingUser.createdDate)))
//case None => throw new NotFoundException(); // does this exception exist in spray/akka?
}
}
I'd like to throw some sort of exception here when the user is not found, so that spray/akka will know that exception means to return HTTP 404 Not Found.
Does spray/akka contain some sort of NotFoundException that I can manually throw?
You can throw any exception and then configure an exception handler to convert the exception to 404 response.
Does it have to be an exception, or could you use this:
case None => HttpResponse(StatusCodes.NotFound)
As a learning exercise for Akka FSM, I modeled a simplified order processing flow at a coffee shop. Attached is the state transition diagram. However, one of the test cases I wrote times out and I don't understand why.
FSM (case classes not shown for brevity):
class OrderSystem extends Actor with ActorLogging with LoggingFSM[State, Data] {
startWith(OrderPending, Data(OrderPending, PaymentPending))
when(OrderPending) {
case Event(BaristaIsBusy, _) => stay
case Event(BaristaIsAvailable(_, PaymentPending), _) => goto(OrderPlaced) using Data(stateName, PaymentPending)
case Event(b: BaristaIsAvailable, _) => goto(OrderReady)
}
val waiting = Data(OrderPlaced, PaymentAccepted)
when(OrderPlaced) {
case Event(b: BaristaIsAvailable, `waiting`) => println("1"); goto(OrderReady)
case Event(b: BaristaIsBusy, `waiting`) => println("2"); goto(OrderPending) using `waiting`
case Event(_, Data(_, PaymentDeclined)) => println("3"); goto(OrderClosed)
case Event(_, Data(_, PaymentPending)) => println("4"); stay
}
when(OrderReady) {
case Event(HappyWithOrder, _) => goto(OrderClosed)
case Event(NotHappyWithOrder, _) => goto(OrderPending) using Data(stateName, PaymentAccepted)
}
when(OrderClosed) {
case _ => stay
}
whenUnhandled {
case Event(e, s) => {
// state name is available as 'stateName'
log.warning("Received unhandled request {} in state {}/{}", e, stateName, s)
stay
}
}
// previous state data is available as 'stateData' and next state data as 'nextStateData'
// not necessary as LoggingFSM (if configured) will take care of logging
onTransition {
case _ -> nextState => log.info("Entering state: {} with payment activity: {} from state: {} with payment activity: {}.",
nextState, stateData.paymentActivity, nextStateData.fromState, nextStateData.paymentActivity)
}
initialize()
}
Failing test:
it should "stay in OrderPlaced state as long as customer has not paid" in {
val orderSystem = system.actorOf(Props[OrderSystem])
orderSystem ! BaristaIsAvailable(OrderPending, PaymentPending)
orderSystem ! SubscribeTransitionCallBack(testActor)
expectMsg(CurrentState(orderSystem, OrderPlaced))
orderSystem ! BaristaIsAvailable(OrderPlaced, PaymentPending)
expectMsg(CurrentState(orderSystem, OrderPlaced))
}
Logs:
2015-09-22 23:29:15.236 [order-system-akka.actor.default-dispatcher-2] [DEBUG] n.a.s.o.OrderSystem - processing Event(BaristaIsAvailable(OrderPending,PaymentPending),Data(OrderPending,PaymentPending)) from Actor[akka://order-system/system/testActor1#-2143558060]
2015-09-22 23:29:15.238 [order-system-akka.actor.default-dispatcher-2] [INFO ] n.a.s.o.OrderSystem - Entering state: OrderPlaced with payment activity: PaymentPending from state: OrderPending with payment activity: PaymentPending.
2015-09-22 23:29:15.239 [order-system-akka.actor.default-dispatcher-2] [DEBUG] n.a.s.o.OrderSystem - transition OrderPending -> OrderPlaced
4
2015-09-22 23:29:15.242 [order-system-akka.actor.default-dispatcher-2] [DEBUG] n.a.s.o.OrderSystem - processing Event(BaristaIsAvailable(OrderPlaced,PaymentPending),Data(OrderPending,PaymentPending)) from Actor[akka://order-system/system/testActor1#-2143558060]
[31m- should stay in OrderPlaced state as long as customer has not paid *** FAILED ***[0m
[31m java.lang.AssertionError: assertion failed: timeout (3 seconds)
SubscribeTransitionCallBack will only deliver a CurrentState once, and then only Transition callbacks.
You could try to do this:
it should "stay in OrderPlaced state as long as customer has not paid" in {
val orderSystem = TestFSMRef(new OrderSystem)
orderSystem ! SubscribeTransitionCallBack(testActor)
// fsm first answers with current state
expectMsgPF(1.second. s"OrderPending as current state for $orderSystem") {
case CurrentState('orderSystem', OrderPending) => ok
}
// from now on the subscription will yield 'Transition' messages
orderSystem ! BaristaIsAvailable(OrderPending, PaymentPending)
expectMsgPF(1.second, s"Transition from OrderPending to OrderPlaced for $orderSystem") {
case Transition(`orderSystem`, OrderPending, OrderPlaced) => ok
}
orderSystem ! BaristaIsAvailable(OrderPlaced, PaymentPending)
// there is no transition, so there should not be a callback.
expectNoMsg(1.second)
/*
// alternatively, if your state data changes, using TestFSMRef, you could check state data blocking for some time
awaitCond(
p = orderSystem.stateData == ???,
max = 2.seconds,
interval = 200.millis,
message = "waiting for expected state data..."
)
// awaitCond will throw an exception if the condition is not met within max timeout
*/
success
}