From b2aaae4d4da8c96174c9bb8c8d0d5c7c67a148af Mon Sep 17 00:00:00 2001 From: Alexis Hernandez Date: Sun, 16 Dec 2018 22:25:07 -0700 Subject: [PATCH] server: Be resilient while loading a transaction For loading a transaction, we need to make several calls to the RPC API for retrieving the inputs, when the server gets overloaded by this, we try to get the inputs sequentially. --- .../services/TransactionService.scala | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/server/app/com/xsn/explorer/services/TransactionService.scala b/server/app/com/xsn/explorer/services/TransactionService.scala index 2be6862f..aac894be 100644 --- a/server/app/com/xsn/explorer/services/TransactionService.scala +++ b/server/app/com/xsn/explorer/services/TransactionService.scala @@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory import play.api.libs.json.{JsObject, JsString, JsValue} import scala.concurrent.{ExecutionContext, Future} +import scala.util.control.NonFatal class TransactionService @Inject() ( paginatedQueryValidator: PaginatedQueryValidator, @@ -63,23 +64,46 @@ class TransactionService @Inject() ( def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction] = { val result = for { tx <- xsnService.getTransaction(txid).toFutureOr - transactionVIN <- tx.vin.map { vin => - getTransactionValue(vin) - .map { - case Good(transactionValue) => - val newVIN = vin.copy(address = Some(transactionValue.address), value = Some(transactionValue.value)) - Good(newVIN) - - case Bad(_) => Good(vin) - } - }.toFutureOr - + transactionVIN <- getTransactionVIN(tx.vin).toFutureOr rpcTransaction = tx.copy(vin = transactionVIN) } yield Transaction.fromRPC(rpcTransaction) result.toFuture } + private def getTransactionVIN(list: List[TransactionVIN]): FutureApplicationResult[List[TransactionVIN]] = { + def getVIN(vin: TransactionVIN) = { + getTransactionValue(vin) + .map { + case Good(transactionValue) => + val newVIN = vin.copy(address = Some(transactionValue.address), value = Some(transactionValue.value)) + Good(newVIN) + + case Bad(_) => Good(vin) + } + } + + def loadVINSequentially(pending: List[TransactionVIN]): FutureOr[List[TransactionVIN]] = pending match { + case x :: xs => + for { + tx <- getVIN(x).toFutureOr + next <- loadVINSequentially(xs) + } yield tx :: next + + case _ => Future.successful(Good(List.empty)).toFutureOr + } + + list + .map(getVIN) + .toFutureOr + .toFuture + .recoverWith { + case NonFatal(ex) => + logger.warn(s"Failed to load VIN, trying sequentially, error = ${ex.getMessage}") + loadVINSequentially(list).toFuture + } + } + def getTransactions(ids: List[TransactionId]): FutureApplicationResult[List[Transaction]] = { def loadTransactionsSlowly(pending: List[TransactionId]): FutureOr[List[Transaction]] = pending match { case x :: xs => @@ -99,6 +123,11 @@ class TransactionService @Inject() ( loadTransactionsSlowly(ids) } .toFuture + .recoverWith { + case NonFatal(ex) => + logger.warn(s"Unable to load transactions due to server error, loading them sequentially, error = ${ex.getMessage}") + loadTransactionsSlowly(ids).toFuture + } } def getTransactions(