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

org.ensime.server.tcp.TCPConnectionActor.scala Maven / Gradle / Ivy

// Copyright: 2010 - 2016 https://github.com/ensime/ensime-server/graphs
// Licence: http://www.gnu.org/licenses/gpl-3.0.en.html
package org.ensime.server.tcp

import akka.actor._

import akka.actor.{ ActorRef, Props, ActorLogging, Actor }
import akka.io.Tcp
import akka.util.ByteString
import org.ensime.api.{ RpcRequestEnvelope, EnsimeServerError, RpcResponseEnvelope, EnsimeEvent }
import org.ensime.core.{ Broadcaster, Canonised, Protocol }
import org.ensime.server.RequestHandler

import scala.annotation.tailrec
import scala.util.control.NonFatal

class TCPConnectionActor(
    connection: ActorRef,
    protocol: Protocol,
    project: ActorRef,
    broadcaster: ActorRef
) extends Actor with Stash with ActorLogging {

  case object Ack extends Tcp.Event

  // sign death pact: this actor terminates when connection breaks
  context watch connection

  import Tcp._

  // bytes we have seen but have been unable to process yet
  var seen = ByteString()

  def handlePeerClosed(): Unit = {
    context.parent ! ClientConnectionClosed
    context stop self
  }

  override def receive: Receive = idle

  // not Receive, thanks to https://issues.scala-lang.org/browse/SI-8861
  // (fixed in 2.11.7)
  def idle: PartialFunction[Any, Unit] = incoming orElse readyToSend
  def busy: PartialFunction[Any, Unit] = incoming orElse awaitingAck

  def incoming: Receive = {
    case Received(data: ByteString) =>
      seen = seen ++ data
      attemptProcess()
    case PeerClosed =>
      handlePeerClosed()
  }

  def readyToSend: Receive = {
    case outgoing: EnsimeEvent =>
      sendMessage(RpcResponseEnvelope(None, outgoing))
    case outgoing: RpcResponseEnvelope =>
      sendMessage(outgoing)
  }

  def awaitingAck: Receive = {
    case Ack =>
      // we only stash outgoing messages, so this will cause them to be queued for sending
      unstashAll()
      context.become(idle, discardOld = true)
    case outgoing: EnsimeEvent =>
      stash()
    case outgoing: RpcResponseEnvelope =>
      stash()
    case CommandFailed(Write(_, _)) =>
      connection ! ResumeWriting
  }

  def sendMessage(envelope: RpcResponseEnvelope): Unit = {
    val msg = try {
      protocol.encode(envelope)
    } catch {
      case NonFatal(t) =>
        log.error(t, s"Problem serialising $envelope")
        protocol.encode(
          RpcResponseEnvelope(
            envelope.callId,
            EnsimeServerError(s"Server error: ${t.getMessage}")
          )
        )
    }
    connection ! Tcp.Write(msg, Ack)
    context.become(busy, discardOld = true)
  }

  override def preStart(): Unit = {
    broadcaster ! Broadcaster.Register
  }

  final def attemptProcess(): Unit = {
    try {
      repeatedDecode()
    } catch {
      case e: Throwable =>
        log.error(e, "Error seen during message processing, closing client connection")
        context.stop(self)
    }

  }

  @tailrec
  final def repeatedDecode(): Unit = {
    val (envelopeOpt, remainder) = protocol.decode(seen)
    seen = remainder
    envelopeOpt match {
      case Some(rawEnvelope: RpcRequestEnvelope) =>
        val envelope = Canonised(rawEnvelope)
        context.actorOf(RequestHandler(envelope, project, self), s"${envelope.callId}")
        repeatedDecode()
      case None =>
    }
  }
}

object TCPConnectionActor {
  def apply(connection: ActorRef, protocol: Protocol, project: ActorRef, broadcaster: ActorRef): Props =
    Props(new TCPConnectionActor(connection, protocol, project, broadcaster))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy