konquestor
konquestor

Reputation: 1308

Understanding play/scala error handling

I have a web application in Scala/Play with Cassandra as the database. I ran into following issue while testing for potential errors.

Below are the components of my project

  1. Application.scala -> Basic controller

  2. Model.scala -> Holds the business logic

  3. CassandraClient.scala -> Logic to connect to cassandra and run query on cassandra

CassandraClient.scala looks like below

object CassandraClient {

private val cluster = Cluster.builder()
    .withLoadBalancingPolicy(
      new WhiteListPolicy(new RoundRobinPolicy(), nodes))
    .withSocketOptions(socketOptions)
    .addContactPointsWithPorts(nodes)
    .withCredentials(CASSANDRA_USERNAME, CASSANDRA_PWD).build()
}

 private val session = cluster.connect()

def getValueFromCassandraTable(token:String) = {
 var query = QueryBuilder.select.all()...
 seesion.execute(query)
}

The Cassandra connection is first established when i call getValueFromCassandraTable the first time. And since CassandraClient is an Object, the logic to connect to Cassandra only gets called once.

Model.Scala has some code to handle future returned by session.execute.

For eg: Look at sample code below in Model.scala.

    def getTitles(titles: String)(implicit ctxt: ExecutionContext): Future[List[<Sometype>]] = {
        try{
            CassandraClient.getValueFromCassandraTable(token).toScalaFuture.map { rows =>
                  rows.map(row => row("value").toList
                }.recover { case e: Exception =>List()}
              }
        } catch{case e:Exception => 
           Logger.info("THIS DOES NOT EXECUTE??")
           Future{List()}}
    }

Now when the above sample code gets executed the first time, session.execute(), gets executed. And then getValueFromCassandraTable is executed. So i thought that the flow of execution is Code snippet above in Models.scala -> session.execute() -> getValueFromCassandraTable()

So, if session.execute fails, i should be able to capture it in try catch exception block. But to my surprise catch block is not executed when session.execute fails. Instead play framework throws an exception. Can someone explain this behavior.

Exception Stack

Caused by: java.lang.RuntimeException: java.lang.ExceptionInInitializerError
    at play.api.mvc.ActionBuilder$$anon$1.apply(Action.scala:498) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4$$anonfun$apply$5.apply(Action.scala:105) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4$$anonfun$apply$5.apply(Action.scala:105) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.utils.Threads$.withContextClassLoader(Threads.scala:21) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4.apply(Action.scala:104) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.Action$$anonfun$apply$1$$anonfun$apply$4.apply(Action.scala:103) ~[play_2.10-2.4.2.jar:2.4.2]
    at scala.Option.map(Option.scala:145) ~[scala-library-2.10.5.jar:na]
    at play.api.mvc.Action$$anonfun$apply$1.apply(Action.scala:103) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.Action$$anonfun$apply$1.apply(Action.scala:96) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.libs.iteratee.DoneIteratee$$anonfun$mapM$2.apply(Iteratee.scala:741) ~[play-iteratees_2.10-2.4.2.jar:2.4.2]
Caused by: java.lang.ExceptionInInitializerError: null
    at models.Model$.getTitles(Model.scala:121) ~[classes/:na]
    at models.Model$.getMatchingTitles(Model.scala:48) ~[classes/:na]
    at models.Model$.getMatchingTitles(Model.scala:56) ~[classes/:na]
    at controllers.Application$$anonfun$searchTitle$1.apply(Application.scala:15) ~[classes/:na]
    at controllers.Application$$anonfun$searchTitle$1.apply(Application.scala:15) ~[classes/:na]
    at play.api.mvc.ActionBuilder$$anonfun$async$1.apply(Action.scala:456) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.ActionBuilder$$anonfun$async$1.apply(Action.scala:456) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.Action$.invokeBlock(Action.scala:533) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.Action$.invokeBlock(Action.scala:530) ~[play_2.10-2.4.2.jar:2.4.2]
    at play.api.mvc.ActionBuilder$$anon$1.apply(Action.scala:493) ~[play_2.10-2.4.2.jar:2.4.2]
Caused by: com.datastax.driver.core.exceptions.AuthenticationException: Authentication error on host /10.65.5.44:9042: Username and/or password are incorrect
    at com.datastax.driver.core.Connection$8.apply(Connection.java:368) ~[cassandra-driver-core-2.1.6.jar:na]
    at com.datastax.driver.core.Connection$8.apply(Connection.java:338) ~[cassandra-driver-core-2.1.6.jar:na]
    at com.google.common.util.concurrent.Futures$ChainingListenableFuture.run(Futures.java:861) ~[guava-16.0.1.jar:na]
    at com.google.common.util.concurrent.MoreExecutors$SameThreadExecutorService.execute(MoreExecutors.java:297) ~[guava-16.0.1.jar:na]
    at com.google.common.util.concurrent.ExecutionList.executeListener(ExecutionList.java:156) ~[guava-16.0.1.jar:na]
    at com.google.common.util.concurrent.ExecutionList.execute(ExecutionList.java:145) ~[guava-16.0.1.jar:na]
    at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:185) ~[guava-16.0.1.jar:na]
    at com.datastax.driver.core.Connection$Future.onSet(Connection.java:1170) ~[cassandra-driver-core-2.1.6.jar:na]
    at com.datastax.driver.core.Connection$Dispatcher.channelRead0(Connection.java:1000) ~[cassandra-driver-core-2.1.6.jar:na]
    at com.datastax.driver.core.Connection$Dispatcher.channelRead0(Connection.java:922) ~[cassandra-driver-core-2.1.6.jar:na]

Lin# 121 in Modle.scala is the one which calls CassandrClient.getValueFromCassandraTable. I am mimicking the exception by passing a wrong password connecting to cassandra, hence getting AuthroizationException. I expect this exception to be caught in Try catch. But it is not caught.

(Note that this is has nothing to do with Future, as session.executAysnc is not even called when the exception occurs. So futures have yet not come into play.)

--EDIT--

Looks like Play is swallowing Exception and throwing an Error object. Not sure why is that happening.

Upvotes: 3

Views: 1871

Answers (2)

Andriy Kuba
Andriy Kuba

Reputation: 8263

session.execute() could fail with the error like IOError. And you catch only Exception.

Exception hierarchy

Check if you got no error, just trivial exception. Would be nice if you will add stack trace to your question.

You can try to catch any Throwable (do not do it in real project, you can try it only for the debug)

try {
  .... your code ...
} catch {
  case _ => errorHandler(e)
}

it will not catch error only in the case if it in the Future.

EDIT:

Live example on my cassandra database. It shows a list of users or "something going wrong" string in the case of exception during connection to the cassandra. For example wrong node or cassandra is shutting down.

    package controllers

    import com.datastax.driver.core.Cluster
    import com.datastax.driver.core.Session
    import scala.collection.JavaConversions._

    import play.api._
    import play.api.mvc._

    class Application extends Controller {

      def index = Action {
        try{
          var b = new StringBuilder
          for (row <- CassandraClient.getValueFromCassandraTable()) {
            b ++= row.getString("user_id")
            b ++= "\n"
          }
          Ok(b.toString())
        } catch {
          case _ => InternalServerError("something going wrong")
        }
      }

    }

    object CassandraClient {
       private val cluster = Cluster.builder()
        .addContactPoint("localhost")
        .withPort(9042)
        .build()

      val session = cluster.connect()

      def getValueFromCassandraTable() = {
        session.execute("SELECT * FROM mykeyspace.users")
      }
    }

Upvotes: 1

al32
al32

Reputation: 109

Maybe you have InterruptedException

For example code dont print "InterruptedException test" if you dont use Await.

 try {
   val exception = Future {
     throw new InterruptedException("exception")
   }.recover { case e: Exception => "ok" }

  exception.onComplete {
    case Success(a) => println(a)
    case Failure(err) => println(err)
  }
  //Await.result(exception, 1 seconds) 

 } catch {
   case e: Exception => println("InterruptedException test")
 }

Upvotes: 0

Related Questions