Avoid errors if Corda / Explorer cannot be found at runtime. (#501)

* Avoid NPE if corda.jar cannot be found.
* Log an error if Node Explorer or WebServer fail to start.
* Display an error dialog if we cannot find corda.jar at start-up.
* Use official notUsed() function for unwanted Observables.
* Fix unit tests.
* Rename function to readErrorLines().
This commit is contained in:
Chris Rankin 2017-04-04 17:25:37 +01:00 committed by GitHub
parent 0a60d389d7
commit 4829524244
8 changed files with 73 additions and 19 deletions

View File

@ -4,6 +4,8 @@ import javafx.scene.image.Image
import net.corda.demobench.views.DemoBenchView
import tornadofx.App
import tornadofx.addStageIcon
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets.UTF_8
/**
* README!
@ -49,3 +51,5 @@ class DemoBench : App(DemoBenchView::class) {
addStageIcon(Image("cordalogo.png"))
}
}
fun Process.readErrorLines(): List<String> = InputStreamReader(this.errorStream, UTF_8).readLines()

View File

@ -1,6 +1,7 @@
package net.corda.demobench.explorer
import net.corda.core.utilities.loggerFor
import net.corda.demobench.readErrorLines
import java.io.IOException
import java.util.concurrent.Executors
import net.corda.demobench.model.NodeConfig
@ -40,13 +41,18 @@ class Explorer internal constructor(private val explorerController: ExplorerCont
// Close these streams because no-one is using them.
safeClose(p.outputStream)
safeClose(p.inputStream)
safeClose(p.errorStream)
executor.submit {
val exitValue = p.waitFor()
val errors = p.readErrorLines()
process = null
log.info("Node Explorer for '{}' has exited (value={})", config.legalName, exitValue)
if (errors.isEmpty()) {
log.info("Node Explorer for '{}' has exited (value={})", config.legalName, exitValue)
} else {
log.error("Node Explorer for '{}' has exited (value={}, {})", config.legalName, exitValue, errors)
}
onExit(config)
}
} catch (e: IOException) {

View File

@ -2,6 +2,9 @@ package net.corda.demobench.model
import java.nio.file.Path
import java.nio.file.Paths
import javafx.scene.control.Alert
import javafx.scene.control.Alert.AlertType.ERROR
import javafx.stage.Stage
import tornadofx.Controller
class JVMConfig : Controller() {
@ -24,3 +27,21 @@ class JVMConfig : Controller() {
}
}
typealias atRuntime = (Path, String) -> Unit
fun checkExists(path: Path, header: String) {
if (!path.toFile().exists()) {
val alert = Alert(ERROR)
alert.isResizable = true
alert.headerText = header
alert.contentText = "'$path' does not exist.\n" +
"Please install all of DemoBench's runtime dependencies or run the installer. " +
"See the documentation for more details."
val stage = alert.dialogPane.scene.window as Stage
stage.isAlwaysOnTop = true
alert.show()
}
}

View File

@ -13,7 +13,7 @@ import net.corda.demobench.plugin.PluginController
import net.corda.demobench.pty.R3Pty
import tornadofx.Controller
class NodeController : Controller() {
class NodeController(check: atRuntime = ::checkExists) : Controller() {
companion object {
const val firstPort = 10000
const val minPort = 1024
@ -39,6 +39,10 @@ class NodeController : Controller() {
init {
log.info("Base directory: $baseDir")
log.info("Corda JAR: $cordaPath")
// Check that the Corda capsule is available.
// We do NOT want to do this during unit testing!
check(cordaPath, "Cannot find Corda JAR.")
}
/**

View File

@ -1,6 +1,5 @@
package net.corda.demobench.pty
import com.jediterm.terminal.TtyConnector
import com.jediterm.terminal.ui.*
import com.jediterm.terminal.ui.settings.SettingsProvider
import com.pty4j.PtyProcess
@ -21,13 +20,15 @@ class R3Pty(val name: String, settings: SettingsProvider, dimension: Dimension,
val terminal = JediTermWidget(dimension, settings)
val isConnected: Boolean get() = terminal.ttyConnector?.isConnected ?: false
override fun close() {
log.info("Closing terminal '{}'", name)
executor.shutdown()
terminal.close()
}
private fun createTtyConnector(command: Array<String>, environment: Map<String, String>, workingDir: String?): TtyConnector {
private fun createTtyConnector(command: Array<String>, environment: Map<String, String>, workingDir: String?): PtyProcessTtyConnector {
val process = PtyProcess.exec(command, environment, workingDir)
try {

View File

@ -5,12 +5,13 @@ import com.jediterm.terminal.TextStyle
import com.jediterm.terminal.ui.settings.DefaultSettingsProvider
import java.awt.Dimension
import java.util.logging.Level
import javax.swing.SwingUtilities
import javafx.application.Platform
import javafx.embed.swing.SwingNode
import javafx.scene.control.Button
import javafx.scene.control.Label
import javafx.scene.layout.VBox
import javax.swing.SwingUtilities
import net.corda.client.rpc.notUsed
import net.corda.demobench.explorer.ExplorerController
import net.corda.demobench.model.*
import net.corda.demobench.pty.R3Pty
@ -61,17 +62,28 @@ class NodeTerminalView : Fragment() {
val r3pty = R3Pty(config.legalName, TerminalSettingsProvider(), Dimension(160, 80), onExit)
pty = r3pty
swingTerminal.content = r3pty.terminal
nodeController.runCorda(r3pty, config)
if (nodeController.runCorda(r3pty, config)) {
swingTerminal.content = r3pty.terminal
configureDatabaseButton(config)
configureExplorerButton(config)
configureWebButton(config)
configureDatabaseButton(config)
configureExplorerButton(config)
configureWebButton(config)
/*
* Start RPC client that will update node statistics on UI.
*/
rpc = launchRPC(config)
/*
* Start RPC client that will update node statistics on UI.
*/
rpc = launchRPC(config)
/*
* Check whether the PTY has exited unexpectedly,
* and close the RPC client if it has.
*/
if (!r3pty.isConnected) {
log.severe("Node '${config.legalName}' has failed to start.")
swingTerminal.content = null
rpc?.close()
}
}
})
}
@ -158,7 +170,7 @@ class NodeTerminalView : Fragment() {
// TODO - Will change when we modify RPC Observables handling.
private fun <T> fetchAndDrop(pair: Pair<T, rx.Observable<*>>): T {
pair.second.subscribe().unsubscribe()
pair.second.notUsed()
return pair.first
}

View File

@ -1,6 +1,7 @@
package net.corda.demobench.web
import net.corda.core.utilities.loggerFor
import net.corda.demobench.readErrorLines
import java.io.IOException
import java.util.concurrent.Executors
import net.corda.demobench.model.NodeConfig
@ -34,13 +35,18 @@ class WebServer internal constructor(private val webServerController: WebServerC
// Close these streams because no-one is using them.
safeClose(p.outputStream)
safeClose(p.inputStream)
safeClose(p.errorStream)
executor.submit {
val exitValue = p.waitFor()
val errors = p.readErrorLines()
process = null
log.info("Web Server for '{}' has exited (value={})", config.legalName, exitValue)
if (errors.isEmpty()) {
log.info("Web Server for '{}' has exited (value={})", config.legalName, exitValue)
} else {
log.error("Web Server for '{}' has exited (value={}, {})", config.legalName, exitValue, errors)
}
onExit(config)
}
} catch (e: IOException) {

View File

@ -9,7 +9,7 @@ import org.junit.Test
class NodeControllerTest {
private val baseDir: Path = Paths.get(".").toAbsolutePath()
private val controller = NodeController()
private val controller = NodeController({_,_ ->})
@Test
fun `test unique nodes after validate`() {