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

ch.epfl.scala.debugadapter.DebugServer.scala Maven / Gradle / Ivy

The newest version!
package ch.epfl.scala.debugadapter

import ch.epfl.scala.debugadapter.internal.DebugSession

import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.URI
import java.util.concurrent.atomic.AtomicReference
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.util.control.NonFatal

final class DebugServer private (
    debuggee: Debuggee,
    resolver: DebugToolsResolver,
    logger: Logger,
    address: DebugServer.Address,
    config: DebugConfig
)(implicit ec: ExecutionContext) {
  private var closedServer = false
  private val onGoingSession = new AtomicReference[DebugSession]()
  private val lock = new Object()

  private val serverSocket = address.serverSocket
  val uri = address.uri

  /**
   * Wait for a connection then start a session
   * If the session returns `DebugSession.Restarted`, wait for a new connection and start a new session
   * Until the session returns `DebugSession.Terminated` or `DebugSession.Disconnected`
   */
  def run(): Future[Unit] = start().map(_ => this.close())

  private def start(): Future[Unit] = {
    for {
      session <- Future(connect())
      exitStatus <- session.exitStatus
      _ = session.close()
      _ <- exitStatus match {
        case DebugSession.Restarted => start()
        case _ => Future.successful(())
      }
    } yield ()
  }

  /**
   * Connect once and return a running session
   * In case of race condition with the [[close]] method, the session can be closed before returned
   */
  private[debugadapter] def connect(): DebugSession = {
    val socket = serverSocket.accept()
    val session = DebugSession(socket, debuggee, resolver, logger, config)
    lock.synchronized {
      if (closedServer) {
        session.close()
      } else {
        onGoingSession.set(session)
        session.start()
      }
      session
    }
  }

  def close(): Unit = {
    lock.synchronized {
      if (!closedServer) {
        closedServer = true
        logger.info(s"Closing debug server $uri")
        try {
          onGoingSession.getAndUpdate { session =>
            if (session != null) session.close()
            null
          }
          serverSocket.close()
        } catch {
          case NonFatal(e) =>
            logger.warn(s"Could not close debug server listening on $uri due to: ${e.getMessage}]")
        }
      }
    }
  }
}

object DebugServer {
  final class Handler(val uri: URI, val running: Future[Unit])

  final class Address() {
    private val address = new InetSocketAddress(0)
    /*
     * Set backlog to 1 to recommend the OS to process one connection at a time,
     * which can happen when a restart is request and the client immediately
     * connects without waiting for the other session to finish.
     */
    val serverSocket = new ServerSocket(address.getPort, 1, address.getAddress)

    def uri: URI = URI.create(s"tcp://${address.getHostString}:${serverSocket.getLocalPort}")
  }

  def apply(
      debuggee: Debuggee,
      resolver: DebugToolsResolver,
      logger: Logger,
      address: Address = new Address,
      config: DebugConfig = DebugConfig.default
  )(implicit ec: ExecutionContext): DebugServer =
    new DebugServer(debuggee, resolver, logger, address, config)

  def run(
      debuggee: Debuggee,
      resolver: DebugToolsResolver,
      logger: Logger,
      gracePeriod: Duration = 5.seconds
  )(implicit ec: ExecutionContext): Handler = {
    val config = DebugConfig.default.copy(gracePeriod = gracePeriod)
    val server = DebugServer(debuggee, resolver, logger, config = config)
    new Handler(server.uri, server.run())
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy