1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 08:27:12 -07:00

Make Kobalt reentrant.

This commit is contained in:
Cedric Beust 2016-04-25 02:04:17 -08:00
parent fdcca3a1aa
commit bace815481
7 changed files with 228 additions and 36 deletions

View file

@ -26,6 +26,10 @@ class Args {
"actually running them") "actually running them")
var dryRun: Boolean = false var dryRun: Boolean = false
@Parameter(names = arrayOf("--force"), description = "Force a new server to be launched even if another one" +
" is already running")
var force: Boolean = false
@Parameter(names = arrayOf("--gc"), description = "Delete old files") @Parameter(names = arrayOf("--gc"), description = "Delete old files")
var gc: Boolean = false var gc: Boolean = false

View file

@ -6,6 +6,9 @@ import com.beust.kobalt.Plugins
import com.beust.kobalt.ProxyConfig import com.beust.kobalt.ProxyConfig
import com.google.inject.Injector import com.google.inject.Injector
import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.Proxy
import com.beust.kobalt.internal.PluginInfo
import com.google.inject.Guice
import com.google.inject.Module
import java.io.InputStream import java.io.InputStream
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.time.Duration import java.time.Duration
@ -15,6 +18,18 @@ class Kobalt {
companion object { companion object {
lateinit var INJECTOR : Injector lateinit var INJECTOR : Injector
fun init(module: Module) {
Kobalt.INJECTOR = Guice.createInjector(module)
//
// Add all the plugins read in kobalt-plugin.xml to the Plugins singleton, so that code
// in the build file that calls Plugins.findPlugin() can find them (code in the
// build file do not have access to the KobaltContext).
//
val pluginInfo = Kobalt.INJECTOR.getInstance(PluginInfo::class.java)
pluginInfo.plugins.forEach { Plugins.addPluginInstance(it) }
}
var context: KobaltContext? = null var context: KobaltContext? = null
val proxyConfig = with(Kobalt.context?.settings?.proxy) { val proxyConfig = with(Kobalt.context?.settings?.proxy) {

View file

@ -6,6 +6,7 @@ import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.api.PluginTask import com.beust.kobalt.api.PluginTask
import com.beust.kobalt.api.Project import com.beust.kobalt.api.Project
import com.beust.kobalt.app.* import com.beust.kobalt.app.*
import com.beust.kobalt.app.remote.DependencyData
import com.beust.kobalt.app.remote.KobaltClient import com.beust.kobalt.app.remote.KobaltClient
import com.beust.kobalt.app.remote.KobaltServer import com.beust.kobalt.app.remote.KobaltServer
import com.beust.kobalt.internal.Gc import com.beust.kobalt.internal.Gc
@ -18,7 +19,6 @@ import com.beust.kobalt.maven.Http
import com.beust.kobalt.maven.dependency.FileDependency import com.beust.kobalt.maven.dependency.FileDependency
import com.beust.kobalt.misc.* import com.beust.kobalt.misc.*
import com.google.common.collect.HashMultimap import com.google.common.collect.HashMultimap
import com.google.inject.Guice
import java.io.File import java.io.File
import java.net.URLClassLoader import java.net.URLClassLoader
import java.nio.file.Paths import java.nio.file.Paths
@ -42,7 +42,7 @@ private fun parseArgs(argv: Array<String>): Main.RunInfo {
fun mainNoExit(argv: Array<String>): Int { fun mainNoExit(argv: Array<String>): Int {
val (jc, args) = parseArgs(argv) val (jc, args) = parseArgs(argv)
Kobalt.INJECTOR = Guice.createInjector(MainModule(args, KobaltSettings.readSettingsXml())) Kobalt.init(MainModule(args, KobaltSettings.readSettingsXml()))
val result = Kobalt.INJECTOR.getInstance(Main::class.java).run { val result = Kobalt.INJECTOR.getInstance(Main::class.java).run {
val runResult = run(jc, args, argv) val runResult = run(jc, args, argv)
pluginInfo.shutdown() pluginInfo.shutdown()
@ -64,8 +64,8 @@ private class Main @Inject constructor(
val github: GithubApi2, val github: GithubApi2,
val updateKobalt: UpdateKobalt, val updateKobalt: UpdateKobalt,
val client: KobaltClient, val client: KobaltClient,
val server: KobaltServer,
val pluginInfo: PluginInfo, val pluginInfo: PluginInfo,
val dependencyData: DependencyData,
val projectGenerator: ProjectGenerator, val projectGenerator: ProjectGenerator,
val resolveDependency: ResolveDependency) { val resolveDependency: ResolveDependency) {
@ -91,21 +91,12 @@ private class Main @Inject constructor(
} }
fun run(jc: JCommander, args: Args, argv: Array<String>): Int { fun run(jc: JCommander, args: Args, argv: Array<String>): Int {
// //
// Install plug-ins requested from the command line // Install plug-ins requested from the command line
// //
val pluginClassLoader = installCommandLinePlugins(args) val pluginClassLoader = installCommandLinePlugins(args)
//
// Add all the plugins read in kobalt-plugin.xml to the Plugins singleton, so that code
// in the build file that calls Plugins.findPlugin() can find them (code in the
// build file do not have access to the KobaltContext).
//
pluginInfo.plugins.forEach { Plugins.addPluginInstance(it) }
// val data = dependencyData.dependenciesDataFor(homeDir("kotlin/klaxon/kobalt/src/Build.kt"), Args())
// println("Data: $data")
// --listTemplates // --listTemplates
if (args.listTemplates) { if (args.listTemplates) {
Templates().list(pluginInfo) Templates().list(pluginInfo)
@ -166,7 +157,7 @@ private class Main @Inject constructor(
} else if (args.usage) { } else if (args.usage) {
jc.usage() jc.usage()
} else if (args.serverMode) { } else if (args.serverMode) {
server.run() val port = KobaltServer(args.force, { pluginInfo.shutdown()}).call()
} else { } else {
// Options that don't need Build.kt to be parsed first // Options that don't need Build.kt to be parsed first
if (args.gc) { if (args.gc) {
@ -213,6 +204,9 @@ private class Main @Inject constructor(
// //
plugins.applyPlugins(Kobalt.context!!, allProjects) plugins.applyPlugins(Kobalt.context!!, allProjects)
// DONOTCOMMIT
// val data = dependencyData.dependenciesDataFor(homeDir("kotlin/klaxon/kobalt/src/Build.kt"), Args())
// println("Data: $data")
if (args.projectInfo) { if (args.projectInfo) {
// --projectInfo // --projectInfo

View file

@ -1,18 +1,128 @@
package com.beust.kobalt.app.remote package com.beust.kobalt.app.remote
import com.beust.kobalt.Args
import com.beust.kobalt.SystemProperties import com.beust.kobalt.SystemProperties
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.app.MainModule
import com.beust.kobalt.homeDir
import com.beust.kobalt.internal.KobaltSettings
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.log import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.warn
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import java.io.BufferedReader import com.google.inject.Guice
import java.io.InputStreamReader import java.io.*
import java.io.PrintWriter
import java.net.ConnectException import java.net.ConnectException
import java.net.Socket import java.net.Socket
import java.nio.file.Paths import java.nio.file.Paths
import java.util.*
import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
fun main(argv: Array<String>) {
Kobalt.INJECTOR = Guice.createInjector(MainModule(Args(), KobaltSettings.readSettingsXml()))
val port = ServerProcess().launch()
println("SERVER RUNNING ON PORT $port")
}
class ServerProcess {
val SERVER_FILE = KFiles.joinDir(homeDir(KFiles.KOBALT_DOT_DIR, "kobaltServer.properties"))
val KEY_PORT = "port"
val executor = Executors.newFixedThreadPool(5)
fun launch() : Int {
var port = launchPrivate()
while (port == 0) {
executor.submit {
KobaltServer(force = true, shutdownCallback = {}).call()
}
// launchServer(ProcessUtil.findAvailablePort())
port = launchPrivate()
}
return port
}
private fun launchPrivate() : Int {
var result = 0
File(SERVER_FILE).let { serverFile ->
if (serverFile.exists()) {
val properties = Properties().apply {
load(FileReader(serverFile))
}
try {
val socket = Socket("localhost", result)
val outgoing = PrintWriter(socket.outputStream, true)
val c: String = """{ "name": "ping"}"""
outgoing.println(c)
val ins = BufferedReader(InputStreamReader(socket.inputStream))
var line = ins.readLine()
val jo = JsonParser().parse(line) as JsonObject
val jsonData = jo["data"]?.asString
val dataObject = JsonParser().parse(jsonData) as JsonObject
val received = JsonParser().parse(dataObject["received"].asString) as JsonObject
if (received["name"].asString == "ping") {
result = properties.getProperty(KEY_PORT).toInt()
}
} catch(ex: IOException) {
log(1, "Couldn't connect to current server, launching a new one")
Thread.sleep(1000)
}
}
}
return result
}
private fun launchServer(port: Int) {
val kobaltJar = File(KFiles().kobaltJar[0])
log(1, "Kobalt jar: $kobaltJar")
if (! kobaltJar.exists()) {
warn("Can't find the jar file " + kobaltJar.absolutePath + " can't be found")
} else {
val args = listOf("java",
"-classpath", KFiles().kobaltJar.joinToString(File.pathSeparator),
"com.beust.kobalt.MainKt",
"--dev", "--server", "--port", port.toString())
val pb = ProcessBuilder(args)
// pb.directory(File(directory))
pb.inheritIO()
// pb.environment().put("JAVA_HOME", ProjectJdkTable.getInstance().allJdks[0].homePath)
val tempFile = createTempFile("kobalt")
pb.redirectOutput(tempFile)
warn("Launching " + args.joinToString(" "))
warn("Server output in: $tempFile")
val process = pb.start()
val errorCode = process.waitFor()
if (errorCode == 0) {
log(1, "Server exiting")
} else {
log(1, "Server exiting with error")
}
}
}
private fun createServerFile(port: Int, force: Boolean) : Boolean {
if (File(SERVER_FILE).exists() && ! force) {
log(1, "Server file $SERVER_FILE already exists, is another server running?")
return false
} else {
Properties().apply {
put(KEY_PORT, port.toString())
}.store(FileWriter(SERVER_FILE), "")
log(2, "KobaltServer created $SERVER_FILE")
return true
}
}
private fun deleteServerFile() {
log(1, "KobaltServer deleting $SERVER_FILE")
File(SERVER_FILE).delete()
}
}
class KobaltClient @Inject constructor() : Runnable { class KobaltClient @Inject constructor() : Runnable {
var outgoing: PrintWriter? = null var outgoing: PrintWriter? = null

View file

@ -0,0 +1,39 @@
package com.beust.kobalt.app.remote
import com.beust.kobalt.Args
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.app.MainModule
import com.beust.kobalt.internal.KobaltSettings
import com.beust.kobalt.internal.remote.ICommand
import com.google.gson.Gson
enum class Command(val n: Int, val command: ICommand) {
GET_DEPENDENCIES(1, Kobalt.INJECTOR.getInstance(GetDependenciesCommand::class.java));
companion object {
val commandMap = hashMapOf<Int, ICommand>()
fun commandFor(n: Int) = values().filter { it.n == n }[0].command
}
}
class KobaltHub(val dependencyData: DependencyData) {
val args = Args()
fun runCommand(n: Int) : String {
val data =
when(n) {
1 -> Gson().toJson(
dependencyData.dependenciesDataFor("/Users/beust/kotlin/klaxon/kobalt/src/Build.kt", args))
else -> throw RuntimeException("Unknown command")
}
println("Data: $data")
return data
}
}
fun main(argv: Array<String>) {
Kobalt.init(MainModule(Args(), KobaltSettings.readSettingsXml()))
val dependencyData = Kobalt.INJECTOR.getInstance(DependencyData::class.java)
val json = KobaltHub(dependencyData).runCommand(1)
val dd = Gson().fromJson(json, DependencyData.GetDependenciesData::class.java)
println("Data2: $dd")
}

View file

@ -1,9 +1,7 @@
package com.beust.kobalt.app.remote package com.beust.kobalt.app.remote
import com.beust.kobalt.Args
import com.beust.kobalt.api.Kobalt import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.homeDir import com.beust.kobalt.homeDir
import com.beust.kobalt.internal.PluginInfo
import com.beust.kobalt.internal.remote.CommandData import com.beust.kobalt.internal.remote.CommandData
import com.beust.kobalt.internal.remote.ICommandSender import com.beust.kobalt.internal.remote.ICommandSender
import com.beust.kobalt.internal.remote.PingCommand import com.beust.kobalt.internal.remote.PingCommand
@ -12,15 +10,14 @@ import com.beust.kobalt.misc.log
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import com.google.inject.Singleton
import java.io.* import java.io.*
import java.lang.management.ManagementFactory
import java.net.ServerSocket import java.net.ServerSocket
import java.net.SocketException import java.net.SocketException
import java.util.* import java.util.*
import javax.inject.Inject import java.util.concurrent.Callable
@Singleton class KobaltServer(val force: Boolean, val shutdownCallback: () -> Unit) : Callable<Int>, ICommandSender {
class KobaltServer @Inject constructor(val args: Args, val pluginInfo: PluginInfo) : Runnable, ICommandSender {
// var outgoing: PrintWriter? = null // var outgoing: PrintWriter? = null
val pending = arrayListOf<CommandData>() val pending = arrayListOf<CommandData>()
@ -29,28 +26,34 @@ class KobaltServer @Inject constructor(val args: Args, val pluginInfo: PluginInf
Kobalt.INJECTOR.getInstance(it).let { Pair(it.name, it) } Kobalt.INJECTOR.getInstance(it).let { Pair(it.name, it) }
}.toMap() }.toMap()
override fun run() { override fun call() : Int {
val port = ProcessUtil.findAvailablePort()
try { try {
if (createServerFile(args.port)) { if (createServerFile(port, force)) {
privateRun() privateRun(port)
} }
} catch(ex: Exception) { } catch(ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
} finally { } finally {
deleteServerFile() deleteServerFile()
} }
return port
} }
val SERVER_FILE = KFiles.joinDir(homeDir(KFiles.KOBALT_DOT_DIR, "kobaltServer.properties")) val SERVER_FILE = KFiles.joinDir(homeDir(KFiles.KOBALT_DOT_DIR, "kobaltServer.properties"))
val KEY_PORT = "port" val KEY_PORT = "port"
val KEY_PID = "pid"
private fun createServerFile(port: Int) : Boolean { private fun createServerFile(port: Int, force: Boolean) : Boolean {
if (File(SERVER_FILE).exists()) { if (File(SERVER_FILE).exists() && ! force) {
log(1, "Server file $SERVER_FILE already exists, is another server running?") log(1, "Server file $SERVER_FILE already exists, is another server running?")
return false return false
} else { } else {
val processName = ManagementFactory.getRuntimeMXBean().name
val pid = processName.split("@")[0]
Properties().apply { Properties().apply {
put(KEY_PORT, port.toString()) put(KEY_PORT, port.toString())
put(KEY_PID, pid)
}.store(FileWriter(SERVER_FILE), "") }.store(FileWriter(SERVER_FILE), "")
log(2, "KobaltServer created $SERVER_FILE") log(2, "KobaltServer created $SERVER_FILE")
return true return true
@ -84,12 +87,10 @@ class KobaltServer @Inject constructor(val args: Args, val pluginInfo: PluginInf
} }
} }
private fun privateRun() { private fun privateRun(port: Int) {
val portNumber = args.port log(1, "Listening to port $port")
log(1, "Listening to port $portNumber")
var quit = false var quit = false
serverInfo = ServerInfo(portNumber) serverInfo = ServerInfo(port)
while (!quit) { while (!quit) {
if (pending.size > 0) { if (pending.size > 0) {
log(1, "Emptying the queue, size $pending.size()") log(1, "Emptying the queue, size $pending.size()")
@ -114,10 +115,13 @@ class KobaltServer @Inject constructor(val args: Args, val pluginInfo: PluginInf
// Done, send a quit to the client // Done, send a quit to the client
sendData(CommandData("quit", "")) sendData(CommandData("quit", ""))
// Clean up all the plug-in actors
shutdownCallback()
line = serverInfo.reader.readLine() line = serverInfo.reader.readLine()
} }
} }
if (line == null) { if (line == null) {
log(1, "Received null line, resetting the server")
serverInfo.reset() serverInfo.reset()
} }
} catch(ex: SocketException) { } catch(ex: SocketException) {
@ -130,8 +134,6 @@ class KobaltServer @Inject constructor(val args: Args, val pluginInfo: PluginInf
} }
log(1, "Command failed: ${ex.message}") log(1, "Command failed: ${ex.message}")
} }
pluginInfo.shutdown()
} }
} }
@ -155,4 +157,5 @@ class KobaltServer @Inject constructor(val args: Args, val pluginInfo: PluginInf
} }
} }
} }
} }

View file

@ -0,0 +1,27 @@
package com.beust.kobalt.app.remote
import java.io.IOException
import java.net.Socket
class ProcessUtil {
companion object {
fun findAvailablePort(): Int {
for (i in 1234..65000) {
if (isPortAvailable(i)) return i
}
throw IllegalArgumentException("Couldn't find any port available, something is very wrong")
}
private fun isPortAvailable(port: Int): Boolean {
var s: Socket? = null
try {
s = Socket("localhost", port)
return false
} catch(ex: IOException) {
return true
} finally {
s?.close()
}
}
}
}