summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/CompileSocket.scala
blob: 01c7d72d4f723c47a27947938554d45904849b08 (plain) (tree)
1
2
3
4
5
6
7
8
9
                            
                                

                          
 

                       
                                    
                                 
                                
                                           
                                                       
                          
 
                        
                                  




                                                                               
                                                                   



                                                            
                                          



                                                 
                                   

                            

                                                                 







                          
                                                                
                                                 
                                                                         
                                     
 

                                                                         
 


                                                                   
                                                           
                                             

                                                                       
                                                        
                            




                                                 

                                                   
                                                                 
                                                                                                                                 
 

                                     
                                                              
                                                                                    
 


                                         
     
                                                                

   
                                                     
                                                     
 
                                                                      

                                                                                   
      
                                                               
                                                                                       
 
                            
                                                
                                                     
                                                            





                                                                          
   
 
                                     
                                                          

                                                                    


                                                                                    
                       



                                             
   

                                                                       
                                                   
     
                                      
                      
                       
 
                    
                         
 

                                                                                
                            
     
                                             
                   


                             
                                       
                 
                                                                                       



                                                                         
                          


                                                     
                                    

                                                                   
     
   

                                                                             
                                                     
 
                                                                  
                                                                       

                            

                                                                                                       


                                                      

                                                                 
                                                                                      



                                                              
                                             
                                
                                                                            


                                
                                                                                                  

                                  
                                                                             
 
                                                            
                                 
         

                        

   



                                                   
 
                                                      
                                                                                                                  

                                                                             
 




                                                                                                   
 
                                        


                                 
                                                 


                      
     
                                                                    


                                                        



                             
 

 

                                            
/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala.tools.nsc

import java.io.FileNotFoundException
import java.security.SecureRandom
import io.{ File, Path, Socket }
import scala.tools.util.CompileOutputCommon
import scala.reflect.internal.util.StringOps.splitWhere
import scala.sys.process._

trait HasCompileSocket {
  def compileSocket: CompileSocket

  // This is kind of a suboptimal way to identify error situations.
  val errorMarkers = Set("error:", "error found", "errors found", "bad option")
  def isErrorMessage(msg: String) = errorMarkers exists (msg contains _)

  def compileOnServer(sock: Socket, args: Seq[String]): Boolean = {
    var noErrors = true

    sock.applyReaderAndWriter { (in, out) =>
      out println (compileSocket getPassword sock.getPort())
      out println (args mkString "\u0000")

      def loop(): Boolean = in.readLine() match {
        case null => noErrors
        case line =>
          if (isErrorMessage(line))
            noErrors = false

          // be consistent with scalac: everything goes to stderr
          compileSocket.warn(line)
          loop()
      }
      try loop()
      finally sock.close()
    }
  }
}

/** This class manages sockets for the fsc offline compiler.  */
class CompileSocket extends CompileOutputCommon {
  protected lazy val compileClient: StandardCompileClient = CompileClient
  def verbose = compileClient.verbose

  /* Fixes the port where to start the server, 0 yields some free port */
  var fixPort = 0

  /** The prefix of the port identification file, which is followed
   *  by the port number.
   */
  protected lazy val dirName = "scalac-compile-server-port"
  protected def cmdName = Properties.scalaCmd

  /** The vm part of the command to start a new scala compile server */
  protected val vmCommand = Properties.scalaHome match {
    case ""       => cmdName
    case dirname  =>
      val trial = File(dirname) / "bin" / cmdName
      if (trial.canRead) trial.path
      else cmdName
  }

  /** The class name of the scala compile server */
  protected val serverClass     = "scala.tools.nsc.CompileServer"
  protected def serverClassArgs = (if (verbose) List("-v") else Nil) ::: (if (fixPort > 0) List("-p", fixPort.toString) else Nil)

  /** A temporary directory to use */
  val tmpDir = {
    val udir  = Option(Properties.userName) getOrElse "shared"
    val f     = (Path(Properties.tmpDir) / ("scala-devel" + udir)).createDirectory()

    if (f.isDirectory && f.canWrite) {
      info("[Temp directory: " + f + "]")
      f
    }
    else fatal("Could not find a directory for temporary files")
  }

  /* A directory holding port identification files */
  val portsDir = (tmpDir / dirName).createDirectory()

  /** The command which starts the compile server, given vm arguments.
    *
    *  @param vmArgs  the argument string to be passed to the java or scala command
    */
  private def serverCommand(vmArgs: Seq[String]): Seq[String] =
    Seq(vmCommand) ++ vmArgs ++ Seq(serverClass) ++ serverClassArgs filterNot (_ == "")

  /** Start a new server. */
  private def startNewServer(vmArgs: String) = {
    val cmd = serverCommand((vmArgs split " ").toSeq)
    info("[Executing command: %s]" format cmd.mkString(" "))

    // Hiding inadequate daemonized implementation from public API for now
    Process(cmd) match {
      case x: ProcessBuilder.AbstractBuilder => x.daemonized().run()
      case x                                 => x.run()
    }
  }

  /** The port identification file */
  def portFile(port: Int) = portsDir / File(port.toString)

  /** Poll for a server port number; return -1 if none exists yet */
  private def pollPort(): Int = if (fixPort > 0) {
  	if (portsDir.list.toList.exists(_.name == fixPort.toString)) fixPort else -1
  } else portsDir.list.toList match {
    case Nil      => -1
    case x :: xs  => try x.name.toInt catch {
    	case e: Exception => x.delete()
    	throw e
    }
  }

  /** Get the port number to which a scala compile server is connected;
   *  If no server is running yet, then create one.
   */
  def getPort(vmArgs: String): Int = {
    val maxPolls = 300
    val sleepTime = 25L

    var attempts = 0
    var port = pollPort()

    if (port < 0) {
      info("No compile server running: starting one with args '" + vmArgs + "'")
      startNewServer(vmArgs)
    }
    while (port < 0 && attempts < maxPolls) {
      attempts += 1
      Thread.sleep(sleepTime)
      port = pollPort()
    }
    info("[Port number: " + port + "]")
    if (port < 0)
      fatal("Could not connect to compilation daemon after " + attempts + " attempts.")
    port
  }

  /** Set the port number to which a scala compile server is connected */
  def setPort(port: Int) {
    val file    = portFile(port)
    val secret  = new SecureRandom().nextInt.toString

    try file writeAll secret catch {
      case e @ (_: FileNotFoundException | _: SecurityException) =>
        fatal("Cannot create file: %s".format(file.path))
    }
  }

  /** Delete the port number to which a scala compile server was connected */
  def deletePort(port: Int) = portFile(port).delete()

  /** Get a socket connected to a daemon.  If create is true, then
    * create a new daemon if necessary.  Returns None if the connection
    * cannot be established.
    */
  def getOrCreateSocket(vmArgs: String, create: Boolean = true, fixedPort: Int = 0): Option[Socket] = {
    fixPort = fixedPort
    val maxMillis = 10L * 1000   // try for 10 seconds
    val retryDelay = 50L
    val maxAttempts = (maxMillis / retryDelay).toInt

    def getsock(attempts: Int): Option[Socket] = attempts match {
      case 0    => warn("Unable to establish connection to compilation daemon") ; None
      case num  =>
        val port = if (create) getPort(vmArgs) else pollPort()
        if (port < 0) return None

        Socket.localhost(port).either match {
          case Right(socket)  =>
            info("[Connected to compilation daemon at port %d]" format port)
            Some(socket)
          case Left(err)      =>
            info(err.toString)
            info("[Connecting to compilation daemon at port %d failed; re-trying...]" format port)

            if (attempts % 2 == 0)
              deletePort(port)      // 50% chance to stop trying on this port

            Thread sleep retryDelay // delay before retrying
            getsock(attempts - 1)
        }
    }
    getsock(maxAttempts)
  }

  // XXX way past time for this to be central
  def parseInt(x: String): Option[Int] =
    try   { Some(x.toInt) }
    catch { case _: NumberFormatException => None }

  def getSocket(serverAdr: String): Option[Socket] = (
    for ((name, portStr) <- splitWhere(serverAdr, _ == ':', doDropIndex = true) ; port <- parseInt(portStr)) yield
      getSocket(name, port)
  ) getOrElse fatal("Malformed server address: %s; exiting" format serverAdr)

  def getSocket(hostName: String, port: Int): Option[Socket] = {
    val sock = Socket(hostName, port).opt
    if (sock.isEmpty) warn("Unable to establish connection to server %s:%d".format(hostName, port))
    sock
  }

  def getPassword(port: Int): String = {
    val ff  = portFile(port)
    val f   = ff.bufferedReader()

    // allow some time for the server to start up
    def check = {
      Thread sleep 100
      ff.length
    }
    if ((Iterator continually check take 50 find (_ > 0)).isEmpty) {
      ff.delete()
      fatal("Unable to establish connection to server.")
    }
    val result = f.readLine()
    f.close()
    result
  }
}


object CompileSocket extends CompileSocket {
}