All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ongo.reactivemongo-pekkostream_2.12.1.1.0-RC15.source-code.ResponseStage.scala Maven / Gradle / Ivy

The newest version!
package reactivemongo.pekkostream

import scala.util.{ Failure, Success, Try }

import scala.concurrent.{ ExecutionContext, Future }

import org.apache.pekko.stream.{ Attributes, Outlet, SourceShape }
import org.apache.pekko.stream.stage.{ GraphStage, GraphStageLogic, OutHandler }

import reactivemongo.api.Cursor

import reactivemongo.core.errors.GenericDriverException
import reactivemongo.core.protocol.Response

import Cursor.ErrorHandler

private[pekkostream] final class ResponseStage[T, Out](
    cursor: PekkoStreamCursorImpl[T],
    maxDocs: Int,
    suc: Response => Out,
    err: ErrorHandler[Option[Out]]
  )(implicit
    ec: ExecutionContext)
    extends GraphStage[SourceShape[Out]] {

  override val toString = "ReactiveMongoResponse"
  val out: Outlet[Out] = Outlet(s"${toString}.out")
  val shape: SourceShape[Out] = SourceShape(out)

  private val nextResponse = cursor.nextResponse(maxDocs)

  private val logger =
    reactivemongo.util.LazyLogger("reactivemongo.pekkostream.ResponseStage")

  @inline
  private def next(r: Response): Future[Option[Response]] = nextResponse(ec, r)

  def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
    new GraphStageLogic(shape) with OutHandler {
      private var last = Option.empty[(Response, Out)]

      private var request: () => Future[Option[Response]] = { () =>
        cursor
          .makeRequest(maxDocs)
          .andThen {
            case Success(_) => {
              request = { () =>
                last.fold(Future.successful(Option.empty[Response])) {
                  case (lastResponse, _) =>
                    // println(s"lastResponse = ${lastResponse.reply}")

                    next(lastResponse).andThen {
                      case Success(Some(response)) =>
                        last.foreach {
                          case (lr, _) =>
                            if (lr.reply.cursorID != response.reply.cursorID)
                              kill(lr)
                        }
                    }
                }
              }
            }
          }
          .map(Some(_))
      }

      private def killLast(): Unit = last.foreach { case (r, _) => kill(r) }

      @SuppressWarnings(Array("CatchException"))
      private def kill(r: Response): Unit = {
        try {
          cursor.wrappee.killCursor(r.reply.cursorID)
        } catch {
          case reason: Exception =>
            logger.warn(
              s"fails to kill the cursor (${r.reply.cursorID})",
              reason
            )
        }

        last = None
      }

      private def onFailure(reason: Throwable): Unit = {
        val previous = last.map(_._2)

        killLast()

        err(previous, reason) match {
          case Cursor.Cont(_) =>
            onPull()

          case Cursor.Done(_) =>
            completeStage()

          case Cursor.Fail(error) =>
            fail(out, error)

          case _ =>
            fail(out, new GenericDriverException("Erroneous cursor"))

        }
      }

      private val futureCB =
        getAsyncCallback({ (response: Try[Option[Response]]) =>
          response.map(_.map { r => r -> suc(r) }) match {
            case Failure(reason) => onFailure(reason)

            case Success(state @ Some((_, result))) => {
              last = state
              push(out, result)
            }

            case _ =>
              completeStage()
          }
        }).invoke _

      def onPull(): Unit = request().onComplete(futureCB)

      override def postStop(): Unit = {
        killLast()
        super.postStop()
      }

      setHandler(out, this)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy