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

loci.runtime.RemoteConnections.scala Maven / Gradle / Ivy

The newest version!
package loci
package runtime

import communicator.{Connection, Connector, Listener}
import messaging.{ConnectionsBase, Message}
import transmitter.RemoteAccessException

import java.util.concurrent.atomic.AtomicLong

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

class RemoteConnections(peer: Peer.Signature, ties: Map[Peer.Signature, Peer.Tie])
  extends ConnectionsBase[Remote.Reference, Message[Method]] {

  protected def deserializeMessage(message: MessageBuffer) = {
    val result = Message.deserialize[Method](message)
    result.failed foreach { logging.warn("could not parse message", _) }
    result
  }

  protected def serializeMessage(message: Message[Method]) =
    Message.serialize(message)

  private def violatedException =
    new RemoteAccessException("tie constraints violated")

  private def messageException(message: Message[Method]) =
    new Message.Exception(s"unexpected connect message: $message")

  private val multiplicities =
    (ties.keys flatMap { _.bases } map { _ -> Peer.Tie.Multiple }).toMap ++ ties

  protected class State extends BaseState {
    private val counter = new AtomicLong(1)
    def createId() = counter.getAndIncrement()
    val potentials = mutable.ListBuffer.empty[Peer.Signature]
  }

  protected val state = new State

  private val doConstraintsSatisfied = Notice.Stream[Unit]

  private val doConstraintsViolated = Notice.Stream[Unit]

  def constraintsSatisfied: Notice.Stream[Unit] = doConstraintsSatisfied.notice

  def constraintsViolated: Notice.Stream[Unit] = doConstraintsViolated.notice

  def connect(
      connector: Connector[ConnectionsBase.Protocol],
      remotePeer: Peer.Signature): Notice.Steady[Try[Remote.Reference]] = {
    val connected = Notice.Steady[Try[Remote.Reference]]
    connectWithCallback(connector, remotePeer)(connected.set)
    connected.notice
  }

  def connectWithCallback(
      connector: Connector[ConnectionsBase.Protocol],
      remotePeer: Peer.Signature)(
      handler: Try[Remote.Reference] => Unit): Unit = sync {
    if (!isTerminated) {
      if (constraintViolationsConnecting(remotePeer).isEmpty) {
        state.potentials += remotePeer

        connector.connect() {
          case Success(connection) =>
            val remote = Remote.Reference(
              state.createId(), remotePeer)(
              connection.protocol, this)

            var closedHandler: Notice[_] = null
            var receiveHandler: Notice[_] = null

            closedHandler = connection.closed foreach { _ =>
              handler(Failure(terminatedException))
            }

            receiveHandler = connection.receive foreach { data =>
              sync {
                state.potentials -= remotePeer

                if (receiveHandler != null)
                  receiveHandler.remove()
                if (closedHandler != null)
                  closedHandler.remove()

                val handleAccept =
                  handleAcceptMessage(connection, remote)
                val handleRequest =
                  handleRequestMessage(connection, remotePeer) andThen {
                    _ map { case (remote, _) => remote }
                  }

                val result = deserializeMessage(data) flatMap {
                  handleAccept orElse handleRequest orElse
                  handleUnknownMessage
                }

                if (result.isFailure)
                  connection.close()

                afterSync { handler(result) }
              }
            }

            logging.trace(s"connecting to remote $remotePeer")

            connection.send(serializeMessage(
              RequestMessage(
                Peer.Signature.serialize(remotePeer),
                Peer.Signature.serialize(peer))))

          case Failure(exception) =>
            logging.trace(s"connecting to remote failed: $remotePeer", exception)
            handler(Failure(exception))
        }
      }
      else {
        logging.trace(s"connection refused due to tie constraints: $remotePeer")
        handler(Failure(violatedException))
      }
    }
    else {
      logging.trace(s"connection refused after connection system shutdown: $remotePeer")
      handler(Failure(terminatedException))
    }
  }

  def listen(
      listener: Listener[ConnectionsBase.Protocol],
      remotePeer: Peer.Signature,
      createDesignatedInstance: Boolean = false): Try[Unit] =
    listenWithCallback(listener, remotePeer, createDesignatedInstance) { _ => }

  def listenWithCallback(
      listener: Listener[ConnectionsBase.Protocol],
      remotePeer: Peer.Signature,
      createDesignatedInstance: Boolean = false)(
      handler: Try[(Remote.Reference, RemoteConnections)] => Unit): Try[Unit] =
    sync {
      if (!isTerminated) {
        val listening = listener.startListening() {
          case Success(connection) =>
            var receiveHandler: Notice[_] = null

            receiveHandler = connection.receive foreach { data =>
              if (receiveHandler != null)
                receiveHandler.remove()

              val handleRequest = handleRequestMessage(
                connection, remotePeer, createDesignatedInstance)

              val result = deserializeMessage(data) flatMap {
                handleRequest orElse handleUnknownMessage
              }

              if (result.isFailure)
                connection.close()

              handler(result)
            }

          case Failure(exception) =>
            handler(Failure(exception))
        }

        listening foreach addListening

        listening map { _ => () }
      }
      else
        Failure(terminatedException)
    }

  private def handleRequestMessage(
      connection: Connection[ConnectionsBase.Protocol],
      remotePeer: Peer.Signature,
      createDesignatedInstance: Boolean = false)
  : PartialFunction[Message[Method], Try[(Remote.Reference, RemoteConnections)]] = {
    case RequestMessage(requested, requesting) =>
      sync {
        if (!isTerminated)
          Peer.Signature.deserialize(requested) flatMap { requestedPeer =>
            Peer.Signature.deserialize(requesting) flatMap { requestingPeer =>
              if (peer <= requestedPeer &&
                  requestingPeer <= remotePeer &&
                  constraintViolationsConnecting(remotePeer).isEmpty) {
                val instance =
                  if (!createDesignatedInstance) this
                  else new RemoteConnections(peer, ties)

                val remote = Remote.Reference(
                  instance.state.createId(), remotePeer)(
                  connection.protocol, this)

                connection.send(serializeMessage(AcceptMessage()))

                val result = instance.addConnection(remote, connection)

                result map { _ => (remote, instance) }
              }
              else
                Failure(violatedException)
            }
          }
        else
          Failure(terminatedException)
      }
  }

  private def handleAcceptMessage(
      connection: Connection[ConnectionsBase.Protocol],
      remote: Remote.Reference)
  : PartialFunction[Message[Method], Try[Remote.Reference]] = {
    case AcceptMessage() =>
      sync {
        if (!isTerminated)
          addConnection(remote, connection) map { _ => remote }
        else
          Failure(terminatedException)
      }
  }

  private val handleUnknownMessage
    : PartialFunction[Message[Method], Try[Nothing]] = {
      case message => Failure(messageException(message))
    }

  override protected def addConnection(
      remote: Remote.Reference,
      connection: Connection[ConnectionsBase.Protocol]) =
    sync {
      handleConstraintChanges {
        super.addConnection(remote, connection)
      }
    }

  override protected def removeConnection(remote: Remote.Reference) =
    sync {
      handleConstraintChanges {
        super.removeConnection(remote)
      }
    }

  private def handleConstraintChanges[T](changeConnection: => T): T =
    if (!state.isTerminated) {
      val constraintsSatisfiedBefore = constraintViolations.isEmpty
      val result = changeConnection
      val constraintsSatisfiedAfter = constraintViolations.isEmpty

      if (!constraintsSatisfiedBefore && constraintsSatisfiedAfter)
        afterSync { doConstraintsSatisfied.fire() }
      if (constraintsSatisfiedBefore && !constraintsSatisfiedAfter)
        afterSync { doConstraintsViolated.fire() }

      result
    }
    else
      changeConnection

  def constraintViolationsConnecting(peer: Peer.Signature): Option[Peer.Signature] = {
    val peerCounts = connections(includePotentials = true) count { _ == peer }

    if (!checkConstraints(peer, 1 + peerCounts)) {
      logging.trace(s"Constraints violated for single checked peer $peer")
      Some(peer)
    }
    else
      None
  }

  def constraintViolations: Set[Peer.Signature] = {
    val peerCounts =
      (multiplicities map { case (peer, _) => (peer, 0) }) ++
      (connections(includePotentials = false) groupBy identity map {
        case (peer, list) => (peer, list.size)
      })

    val violations =
      (peerCounts collect { case (peer, count)
        if !checkConstraints(peer, count) => peer
      }).toSet

    if (violations.nonEmpty)
      logging.trace(s"Constraints violated for [${violations mkString ", "}]")

    violations
  }

  private def connections(includePotentials: Boolean): Seq[Peer.Signature] = {
    val remotePeers = remotes map { _.signature }
    val potentialPeers =
      if (includePotentials) synchronized { state.potentials }
      else Seq.empty

    logging.trace({
      val connectedPeers =
        s"checking constraints for connected remote peer instances with types [${remotePeers mkString ", "}]"
      if (includePotentials)
        s"$connectedPeers and connecting remote peer instances with types [${potentialPeers mkString ", "}]"
      else
        connectedPeers
    })

    (remotePeers ++ potentialPeers) flatMap { _.bases }
  }

  private def checkConstraints(peer: Peer.Signature, count: Int): Boolean =
    peer.bases collect (Function unlift { peer =>
      multiplicities get peer map {
        case Peer.Tie.Multiple => true
        case Peer.Tie.Optional => count <= 1
        case Peer.Tie.Single => count == 1
      }
    }) reduceOption { _ && _ } getOrElse false
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy