I have an actor with two Messages first responsible for inserting data in mongoDB and the second actor is responsible for inserting data in elasticsearch ,InserInMongo and InserInES namely
there will be a case when mongodb insert operation fails or ES insert operation fails due to some exception and i am doing something like this
try {
val mongoFuture: Future[Boolean] = ask(artGroupPersistenceActor, PersistArtGroupInMongo(artGroup)).mapTo[Boolean]
val esFuture: Future[Boolean] = ask(artGroupPersistenceActor, PersistArtGroupInES(artGroup)).mapTo[Boolean]
val resultMongo = Await.result(mongoFuture, timeout.duration)
log.debug("store: Record Inserted inserted in mongoDB {}", resultMongo)
val resultES = Await.result(esFuture, timeout.duration)
log.debug("store: Record Inserted in ES {}", resultES)
}
catch {
case e: Exception =>
log.error("store:While inserting an artgroup Exception in artGroupPersistenceActor actor", e)
throw e
}
here i want if mongoFuture is failed then i catch its exception and it should proceed with esFuture
or if both future failed i get both exception how can i archive this scenario ?
You can try like this.
for {
x <- Future.successful {
try {
code here...
} catch {
case _: Exception =>
println("Error Inserting In Mongo ")
false
}
}
y <- Future.successful {
try {
code here...
// throw new IllegalStateException("Exception thrown")
} catch {
case _: IllegalStateException =>
println("Error Inserting In ES ")
false
}
}
} yield(x, y)
Now you can manipulate if error occurred while doing the process.. Good luck.
You can go with recover, which will handle any matching throwable that the original Future contains:
val mongoFuture = ask(artGroupPersistenceActor, PersistArtGroupInMongo(artGroup))
.mapTo[Boolean]
.recover {
case e =>
println("error in mongo: " + e)
false
}
val esFuture = ask(artGroupPersistenceActor, PersistArtGroupInES(artGroup))
.mapTo[Boolean]
.recover {
case e =>
println("error in ES: " + e)
false
}
val f = Future.sequence(List(mongoFuture, esFuture))
val result: Seq[Boolean] = Await.result(f, Duration.Inf)
println(result)
If you just want to log the exceptions, just do this with each one:
mongoFuture.failed.foreach {ex => logger.error("Mongo error", ex)}
it better to use map and recover instead of Await.result
import akka.pattern.ask
import scala.concurrent.duration._
implicit val timeout : akka.util.Timeout = 20.seconds
for{
mongoResult <- (artGroupPersistenceActor ? PersistArtGroupInMongo(artGroup)).mapTo[Boolean]
.recover{
case _ =>
log.error("mongodb error")
false
}
elasticResult <- (artGroupPersistenceActor ? PersistArtGroupInES(artGroup)).mapTo[Boolean]
.recover{
case _ =>
log.error("elastic error")
false
}
} yield {
(mongoResult, elasticResult)
}
Related
I have ParentActor and 2 ChildActors here is my code
Class ParentActor extends Actor {
val mongoActor = context.of.....
val esActor = context.of ............
def receive {
case InserInMongo(obj) =>
val mFuture = ask(mongoActor, InsertDataInMongo(object)).mapTo[Boolean]
mFuture.onComplete {
case Success(resultMongo) =>
sender ! resultMongo
case Failure(e) =>
sender ! akka.actor.Status.Failure(e)
throw e
}
case InserInES(obj) =>
val eFuture = ask(esActor, InsertDataInES(object)).mapTo[Boolean]
eFuture.onComplete {
case Success(resultES) =>
sender ! resultES
case Failure(e) =>
sender ! akka.actor.Status.Failure(e)
throw e
}
}
}
here is the calling code
class Demo {
val mongoFuture = ask(parentActor, InsertInMongo(obj))
.mapTo[Boolean]
.recover {
case e =>
println("error in mongo: " + e)
false
}
val esFuture = ask(parentActor, InsertInES(obj))
.mapTo[Boolean]
.recover {
case e =>
println("error in ES: " + e)
false
}
val f = Future.sequence(List(mongoFuture, esFuture))
val result: Seq[Boolean] = Await.result(f, Duration.Inf)
log.info ("result {}",result)
}
everything works fine but if i get some MongoException in mongoChildActor i am unable to get the parent actor result for ES
here is what i am getting
17:19:45.782 [MyActorSystem-akka.actor.default-dispatcher-4] INFO akka.actor.DeadLetterActorRef - Message [java.lang.Boolean] from Actor[akka://MyActorSystem/user/ParentActor#1383701267] to Actor[akka://MyActorSystem/deadLetters] was not delivered. [2] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
All i want is if i got exception in mongo i got the exception and then it should proceed with ES and return its result (either its successful value or exception)
Don't throw exceptions in receive, and don't close over sender (sender is a function, that is only valid in the context of receive, you are using it inside .onComplete, on a different thread).
Something like this should do what you want:
import akka.pattern.pipe
def receive: Receive = {
case InserInMongo(obj) =>
ask(mongoActor, InsertDataInMongo(object)).pipeTo(sender)
case InserInES(obj) =>
ask(esActor, InsertDataInES(object)).pipeTo(sender)
}
Or, even easier:
def receive: Receive {
case msg: InsertDataInMongo =>
mongoActor.forward(msg)
case msg: InsertDataInES =>
esActor.forward(msg)
}
I am trying to open a stream, try to decompress it as gzip, if that fails try to decompress it as zlib, and return the stream for further use. The underlying stream must be closed in the case of exceptions creating the wrapping decompression streams, as otherwise I will run into resource exhaustion issues.
pbData is a standard, non-resettable, InputStream
There has to be a cleaner way to do this.
val input = {
var pb = pbData.open()
try {
log.trace("Attempting to create GZIPInputStream")
new GZIPInputStream(pb)
} catch {
case e: ZipException => {
log.trace("Attempting to create InflaterInputStream")
pb.close()
pb = pbData.open()
try {
new InflaterInputStream(pb)
} catch {
case e: ZipException => {
pb.close()
throw e
}
}
}
}
Your process is actually iteration over InputStream instance generators. Check this, far more idiomatic solution compared to a lot of nested try-catch'es:
val bis = new BufferedInputStream(pbData.open())
// allows us to read and reset 16 bytes, as in your answer
bis.mark(16)
// list of functions, because we need lazy evaluation of streams
val streamGens: List[Any => InputStream] = List(
_ => new GZIPInputStream(bis),
_ => new InflaterInputStream(bis),
_ => bis
)
def firstStreamOf(streamGens: List[Any => InputStream]): Try[InputStream] =
streamGens match {
case x :: xs =>
Try(x()).recoverWith {
case NonFatal(_) =>
// reset in case of failure
bis.reset()
firstStreamOf(xs)
}
case Nil =>
// shouldn't get to this line because of last streamGens element
Failure(new Exception)
}
val tryStream = firstStreamOf(streamGens)
tryStream.foreach { stream =>
// do something with stream...
}
As a bonus, if you'll need to add trying more stream generators, you will have to add exactly one line into streamGens initialization. Also, we won't need to add bit.reset() invocation manually.
Something like this, perhaps:
def tryDecompress(pbData: PBData, ds: InputStream => InputStream *): InputStream = {
def tryIt(s: InputStream, dc: InputStream => InputStream) = try {
dc(s)
} catch { case NonFatal(e) =>
close(s)
throw e
}
val (first, rest) = ds.head -> ds.tail
try {
tryIt(pbData.open, first)
} catch {
case _: ZipException if rest.nonEmpty =>
tryDecompress(pbData, rest)
}
}
val stream = tryDecompress(pbData, new GZipInputStream(_), new InflaterInputStream(_))
(Too bad scala's Try does not have onFailure ... this could look much nicer if it did :/)
val result: Try[InflaterInputStream] = Try(new GZIPInputStream(pb)) match {
case res#Success(x) => res
case Failure(e) => e match {
case e: ZipException =>
Try(new InflaterInputStream(pb)) match {
case res2#Success(x2) => res2
case Failure(e2) => e match {
case _ => pb.close()
Failure(e2)
}
}
case ex#_ => pb.close()
Failure(ex)
}
}
I found that using a BufferedInputStream to wrap the underlying stream, then resetting it between each decompression library attempt, looks pretty clean.
val bis = new BufferedInputStream(pbData.open())
// allows us to read and reset 16 bytes
bis.mark(16)
val input: InputStream = {
try {
log.trace("attempting to open as gzip")
new GZIPInputStream(bis)
} catch {
case e: ZipException => try {
bis.reset()
log.trace("attempting to open as zlib")
new InflaterInputStream(bis)
} catch {
case e: ZipException => {
bis.reset()
log.trace("attempting to open as uncompressed")
bis
}
}
}
}
The code below uses Slick 3.1.x to read a row from a table, attempting to catch any SQL errors. UserDB is the Slick representation of the table, and User is the related object.
This code does not compile in the failure statement with the following error:
type mismatch; found : Unit required:
scala.concurrent.Future[Option[user.manage.User]]
How to fix this to catch the SQL error?
def read (sk: Int): Future[Option[User]] = {
val users = TableQuery[UserDB]
val action = users.filter(_.sk === sk).result
val future = db.run(action)
future.onSuccess {
case result =>
if (!result.isEmpty)
Some(result(0))
else
None
}
future.onFailure { // <-- compilation error
case e => println (e.getMessage)
None
}
}
You can use asTry method to catch exception ex into a successful result Failure(ex) and the successful value in Success(v). In your case the following should work.
db.run(action.asTry).map{
case Failure(ex) => {
Log.error(s"error : ${ex.getMessage}")
None
}
case Success(x) => x
}
As mentioned in the slick documentation, asTry is used to pipeline the exceptions for recovery handling logic.
You can use the onComplete method of the future.
future.onComplete{
case Success(r) ⇒ ...
case Failure(t) ⇒ log.error("failure in db query " + t.getMessage)
}
how do I catch an exception inside a future success using play to test?
Here is my code:
"A Validity" should {
"be created based on Client's Mobile" in new WithApplication {
val objectId = UUIDs.timeBased()
CValidityServiceModule.checkValidity(true, "MOBILE", clientId, objectId)
val future = genericService.findByObjectIdAndByClientId(objectId, clientId)
future.onComplete {
case Success(s) => {
s match {
case Some(v) => {
v.clientId mustEqual clientId
v.objectId mustEqual objectId
}
case None => assert(false)
}
}
case Failure(t) => {
assert(false, t.getMessage)
}
}
}
Basically if any matcher fail, it trows me an exception, but the test is green.
Any idea?
You need to wait for the Future to complete before testing it. Using the onComplete callback won't work because the test completes before the Future, and the exception is thrown in a different context.
Await.result should do it for you:
import scala.concurrent.Await
import scala.concurrent.duration.Duration
val future = genericService.findByObjectIdAndByClientId(objectId, clientId)
val s = Await.result(future, Duration.Inf)
s match {
case Some(v) => {
v.clientId mustEqual clientId
v.objectId mustEqual objectId
}
case None => assert(false)
}
I am working on a simple RESTful web service using Play Framework 2.1.5 and ReactiveMongo 0.9 using ReactiveMongo Play plugin. It has been a long time since I used Play Framework for the last time. I am trying to insert a document using:
def create = Action(parse.json) { request =>
Async {
val coll = db.collection[JSONCollection](...)
val obj = Json.obj(
"username" -> ...,
...
)
users.insert(obj).map { err => err match {
case e if !e.ok => InternalServerError(Json.obj("result" -> 0, "error" -> e.message))
case _ => Ok(Json.obj("result" -> 1))
}}
}
}
I have expected that once the query execution fails (e.g. due to the duplicate value in an index), I will handle it without any problem. But it is working differently - in case of failure a DatabaseException is thrown instead of satisfying the Promise[LastError] with an appropriate value. What am I missing please?
When an exception happens in a future any calls to map will be ignored and the exception will be passed along the chain of futures.
Explicitly handling the exceptions in a chain of Futures can be done with recover and recoverWith. You can read more about it in the overview of futures in the scala-lang docs:
http://docs.scala-lang.org/overviews/core/futures.html#exceptions
Try this code-
def insert(coll: BSONCollection, doc: BSONDocument): Future[Unit] = {
val p = Promise[Unit]
val f = coll.insert(doc)
f onComplete {
case Failure(e) => p failure (e)
case Success(lastError) => {
p success ({})
}
}
p.future
}
I hope this simplifies your need...
def create = Action (parse.json) { request =>
Async {
val coll = db.collection[JSONCollection](...)
val obj = Json.obj ("username" -> ...)
users.insert(obj).map {
case ins if ins.ok => OK (...)
case ins => InternalServerError (...)
} recover {
case dex: DatabaseException =>
log.error(..)
InternalServerEror(...)
case e: Throwable =>
log.error (..)
InternalServerError (...)
}
}
}