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
}
Related
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 have a GET request with parameters and a formField.
It works when I use a client like Insomnia/Postman to send the req.
But the route test below fails with the error:
UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
(Rejection created by unmarshallers. Signals that the request was rejected because the requests content-type is unsupported.)
I have tried everything I can think of to fix it but it still returns the same error.
It is the formField that causes the problem, for some reason when called by the test it doesnt like the headers.
Is it something to do with withEntity ?
Code:
path("myurl" ) {
get {
extractRequest { request =>
parameters('var1.as[String], 'var2.as[String], 'var3.as[String], 'var4.as[String]) { (var1, var2, var3, var4) =>
formField('sessionid.as[String]) { (sessionid) =>
complete {
request.headers.foreach(a => println("h=" + a))
}
}
}
}
}
}
Test:
// TESTED WITH THIS - Fails with UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
class GETTest extends FreeSpec with Matchers with ScalatestRouteTest {
val get = HttpRequest(HttpMethods.GET, uri = "/myurl?var1=456&var2=123&var3=789&var4=987")
.withEntity("sessionid:1234567890")
.withHeaders(
scala.collection.immutable.Seq(
RawHeader("Content-Type", "application/x-www-form-urlencoded"), // same problem if I comment out these 2 Content-Type lines
RawHeader("Content-Type", "multipart/form-data"),
RawHeader("Accept", "Application/JSON")
)
)
get ~> route ~> check {
status should equal(StatusCodes.OK)
}
The exception is thrown before the formField line.
Full exception:
ScalaTestFailureLocation: akka.http.scaladsl.testkit.RouteTest$$anonfun$check$1 at (RouteTest.scala:57)
org.scalatest.exceptions.TestFailedException: Request was rejected with rejection UnsupportedRequestContentTypeRejection(Set(application/x-www-form-urlencoded, multipart/form-data))
at akka.http.scaladsl.testkit.TestFrameworkInterface$Scalatest$class.failTest(TestFrameworkInterface.scala:24)
}
You could either use:
val get = HttpRequest(HttpMethods.GET, uri = "/myurl?var1=456&var2=123&var3=789&var4=987", entity = FormData("sessionid" -> "1234567.890").toEntity)
or
val get = Get("/myurl?var1=456&var2=123&var3=789&var4=987", FormData("sessionid" -> "1234567.890"))
I am trying to test actor, that is using Akka Typed Persistence. The scenario is, the database is offline and it should send a notification to another actor, that the database is not available.
During the creation of child actor, that will persist the data to the database, it throws the following error:
akka.actor.ActorInitializationException: akka://StoreTestOffline/user/OfflineStoreActor/StoreChilid: exception during creation
at akka.actor.ActorInitializationException$.apply(Actor.scala:202)
at akka.actor.ActorCell.create(ActorCell.scala:696)
at akka.actor.ActorCell.invokeAll$1(ActorCell.scala:547)
at akka.actor.ActorCell.systemInvoke(ActorCell.scala:569)
at akka.dispatch.Mailbox.processAllSystemMessages(Mailbox.scala:293)
at akka.dispatch.Mailbox.run(Mailbox.scala:228)
at akka.dispatch.Mailbox.exec(Mailbox.scala:241)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Caused by: java.lang.IllegalArgumentException: Default journal plugin is not configured, see 'reference.conf'
at akka.persistence.Persistence$.verifyPluginConfigIsDefined(Persistence.scala:193)
at akka.persistence.typed.internal.EventSourcedSettings$.defaultJournalPluginId$1(EventSourcedSettings.scala:57)
at akka.persistence.typed.internal.EventSourcedSettings$.journalConfigFor(EventSourcedSettings.scala:61)
at akka.persistence.typed.internal.EventSourcedSettings$.apply(EventSourcedSettings.scala:39)
at akka.persistence.typed.internal.EventSourcedSettings$.apply(EventSourcedSettings.scala:22)
at akka.persistence.typed.internal.EventSourcedBehaviorImpl.apply(EventSourcedBehaviorImpl.scala:83)
at akka.actor.typed.Behavior$.start(Behavior.scala:331)
at akka.actor.typed.internal.InterceptorImpl$$anon$1.start(InterceptorImpl.scala:45)
at akka.actor.typed.internal.AbstractSupervisor.aroundStart(Supervision.scala:72)
at akka.actor.typed.internal.InterceptorImpl.preStart(InterceptorImpl.scala:68)
at akka.actor.typed.internal.InterceptorImpl$.$anonfun$apply$1(InterceptorImpl.scala:25)
at akka.actor.typed.Behavior$DeferredBehavior$$anon$1.apply(Behavior.scala:264)
at akka.actor.typed.Behavior$.start(Behavior.scala:331)
at akka.actor.typed.internal.adapter.ActorAdapter.preStart(ActorAdapter.scala:238)
at akka.actor.Actor.aroundPreStart(Actor.scala:550)
at akka.actor.Actor.aroundPreStart$(Actor.scala:550)
at akka.actor.typed.internal.adapter.ActorAdapter.aroundPreStart(ActorAdapter.scala:51)
at akka.actor.ActorCell.create(ActorCell.scala:676)
... 9 more
[ERROR] [08/30/2019 12:28:15.391] [StoreTestOffline-akka.actor.default-dispatcher-6] [akka://StoreTestOffline/user] death pact with Actor[akka://StoreTestOffline/user/OfflineStoreActor/StoreChilid#-1253371311] was triggered
akka.actor.typed.DeathPactException: death pact with Actor[akka://StoreTestOffline/user/OfflineStoreActor/StoreChilid#-1253371311] was triggered
at akka.actor.typed.Behavior$.interpretSignal(Behavior.scala:402)
at akka.actor.typed.internal.adapter.ActorAdapter.handleSignal(ActorAdapter.scala:128)
at akka.actor.typed.internal.adapter.ActorAdapter.aroundReceive(ActorAdapter.scala:87)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:612)
at akka.actor.dungeon.DeathWatch.$anonfun$receivedTerminated$1(DeathWatch.scala:67)
at akka.actor.dungeon.DeathWatch.$anonfun$receivedTerminated$1$adapted(DeathWatch.scala:65)
at scala.Option.foreach(Option.scala:438)
at akka.actor.dungeon.DeathWatch.receivedTerminated(DeathWatch.scala:65)
at akka.actor.dungeon.DeathWatch.receivedTerminated$(DeathWatch.scala:64)
at akka.actor.ActorCell.receivedTerminated(ActorCell.scala:447)
at akka.actor.ActorCell.autoReceiveMessage(ActorCell.scala:597)
at akka.actor.ActorCell.invoke(ActorCell.scala:580)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:268)
at akka.dispatch.Mailbox.run(Mailbox.scala:229)
at akka.dispatch.Mailbox.exec(Mailbox.scala:241)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
The way, how I spawn the child is:
object MessageSupervisorSpec {
def create(communicator: Option[ActorRef[RpcCmd]], logger: Option[ActorRef[LogCmd]]): Behavior[MessageCmd] =
Behaviors.setup { context =>
context.log.info("=============> Start MessageSupervisorSpec <=============")
val fault = Behaviors
.supervise(Persistence.create(communicator, logger))
.onFailure[ActorInitializationException](SupervisorStrategy.stop)
val store = context.spawn(fault, "StoreChilid")
context.watch(store)
def loop(): Behavior[MessageCmd] =
Behaviors.receiveMessage {
case SaveMessage(v) =>
println(v)
store ! SaveMessage(v)
Behavior.same
}
loop()
}
}
The code of the child actor:
object Persistence {
val storeName = "connector-store"
/*
* Persist all incoming messages from KAFKA or SAP
*/
private val commandHandler: Option[ActorRef[RpcCmd]] => (MessageState, MessageCmd) => Effect[MessageEvent, MessageState]
= communicator => { (_, command) =>
command match {
case SaveMessage(data) =>
Effect
.persist(MessageSaved(data))
.thenRun(state => communicator.foreach(actor => actor ! SendMessage(state.value)))
}
}
private val eventHandler: (MessageState, MessageEvent) => MessageState
= { (_, event) =>
event match {
case MessageSaved(data) => MessageState(data)
}
}
def create(communicator: Option[ActorRef[RpcCmd]], logger: Option[ActorRef[LogCmd]]): Behavior[MessageCmd] =
Behaviors.setup { context =>
context.log.info("=============> Start PersistenceMessageActor <=============")
EventSourcedBehavior[MessageCmd, MessageEvent, MessageState](
persistenceId = PersistenceId(storeName),
emptyState = MessageState(),
commandHandler = commandHandler(communicator),
eventHandler = eventHandler)
.onPersistFailure(SupervisorStrategy.restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1))
.receiveSignal {
case (_, _) =>
println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
context.log.error("PersistenceMessageActor has been terminated. Please check the persistence.")
logger.foreach(actor => actor ! SaveLog(Log(Error, "Message store actor was stopped!")))
}
}
}
I expect to receive a Signal that I can handle accordingly but I did not get anyone.
The question is, how to handle the case, when the actor can not be started, to receive the signal.
looks like the config is missing akka.persistence.journal.plugin in application.conf
where you want to store the journal for the persistent actor.
I have a following Actor class that is responsible for sending a JSON message to a URL using POST.
import play.api.libs.ws._
class Worker extends Actor {
val logger: Logger = Logger("superman")
val supermanURL = "http://localhost:9000/superman/send"
def receive = {
case message: JsValue => {
val transactionID = (message \ "transactionID").get
println("Received JSON Object =>" + message)
val responseFromSuperman = WS.url(supermanURL).withHeaders("Content-Type" -> "application/json").post(message)
responseFromSuperman.map(
result => {
//TODO: Make sure to only log if response status is 200 OK
logger.info("""Message="ACK received from Superman" for transactionID=""" + transactionID)}
).recover { case error: Throwable =>
logger.error("""Message="NACK received from Superman" for transactionID=""" + transactionID) + " errorMessage:" + error.getLocalizedMessage()
}
}
}
}
So, if you look into my TODO above, I would like to add a check for a response type 200 OK. The current implementation is not doing that and it logs the message even if I manually send in a BadRequest. I tried checking for result.allHeaders which returns:
Map(Date -> Buffer(Wed, 27 Jan 2016 21:45:31 GMT), Content-Type -> Buffer(text/plain; charset=utf-8), Content-Length -> Buffer(7))
but no information about response status 200 OK
Simply:
import play.api.http.Status
if(result.status == Status.OK) {
// ...
}
Maybe I am missing here something but you have "status" on the response.
So you can do:
WS.url(url).withHeaders("Content-Type" -> "application/json").post(message).map{
case response if ( response.status == OK) => //DO SOMETHING?
}
I'm trying to limit life of observable by timeout:
def doLongOperation() = {
Thread.sleep(duration)
"OK"
}
def firstStep = Observable.create(
(observer: Observer[String]) => {
observer.onNext(doLongOperation())
observer.onCompleted()
Subscription()
}
)
firstStep
.timeout(1 second)
.subscribe(
item => println(item),
throwable => throw throwable,
() => println("complete")
)
I would like to distinguish between following results:
Observable finished by timeout, no result obtained
Exception thrown during execution
Execution finished successfully, return value
I can process cases 2 and 3 with no problem in partials onNext and onError, but how do I detect if observable finished by timeout?
One more thing: I've never get into block onComplete, though there is a call to obeserver.onCompleted() in my code. Why?
If a timeout happens, that TimeoutException is emitted on the computation thread where a throw throwable is ends up being ignored and your main thread won't and can't see it. You can add toBlocking after the timeout so any exception will end up on the same thread:
firstStep
.timeout(1 second)
.toBlocking()
.subscribe(
item => println(item),
throwable => println(throwable),
() => println("complete")
)
TimeoutException gets thrown indeed. The problem was caused by using wrong libraries. I had "com.netflix.rxjava" in my dependencies, instead of "io.reactivex"