diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt index 22d45218ac..2d61040598 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt @@ -1,6 +1,5 @@ package net.corda.demobench.views -import com.google.common.util.concurrent.RateLimiter import com.jediterm.terminal.TerminalColor import com.jediterm.terminal.TextStyle import com.jediterm.terminal.ui.settings.DefaultSettingsProvider @@ -13,6 +12,8 @@ import javafx.scene.layout.StackPane import javafx.scene.layout.VBox import javafx.util.Duration import net.corda.client.rpc.notUsed +import net.corda.core.success +import net.corda.core.then import net.corda.demobench.explorer.ExplorerController import net.corda.demobench.model.NodeConfig import net.corda.demobench.model.NodeController @@ -24,12 +25,9 @@ import net.corda.demobench.web.DBViewer import net.corda.demobench.web.WebServerController import tornadofx.* import java.awt.Dimension -import java.io.IOException -import java.net.SocketException -import java.net.URL +import java.net.URI import java.util.logging.Level import javax.swing.SwingUtilities -import kotlin.concurrent.thread class NodeTerminalView : Fragment() { override val root by fxml() @@ -131,6 +129,8 @@ class NodeTerminalView : Fragment() { } } + private var webURL: URI? = null + /* * We only want to run one web server for each node. * So disable the "launch" button when we have @@ -139,32 +139,20 @@ class NodeTerminalView : Fragment() { */ fun configureWebButton(config: NodeConfig) { launchWebButton.setOnAction { + if (webURL != null) { + app.hostServices.showDocument(webURL.toString()) + return@setOnAction + } launchWebButton.isDisable = true - webServer.open(config, onExit = { - launchWebButton.isDisable = false - }) - - openBrowserWhenWebServerHasStarted(config) - } - } - - private fun openBrowserWhenWebServerHasStarted(config: NodeConfig) { - thread { - log.info("Waiting for web server to start ...") - val url = URL("http://localhost:${config.webPort}/") - val rateLimiter = RateLimiter.create(1.0) - while (true) { - try { - rateLimiter.acquire() - val conn = url.openConnection() - conn.connectTimeout = 1000 // msec - conn.connect() - log.info("Web server started") - app.hostServices.showDocument(url.toString()) - break - } catch(e: SocketException) { - } catch(e: IOException) { + log.info("Starting web server for ${config.legalName}") + webServer.open(config) then { + Platform.runLater { launchWebButton.isDisable = false } + } success { + log.info("Web server for ${config.legalName} started on $it") + Platform.runLater { + webURL = it + app.hostServices.showDocument(it.toString()) } } } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt index 1154c9456f..aad88033bc 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt @@ -1,10 +1,21 @@ package net.corda.demobench.web +import com.google.common.util.concurrent.ListenableFuture +import com.google.common.util.concurrent.RateLimiter +import com.google.common.util.concurrent.SettableFuture +import net.corda.core.catch +import net.corda.core.minutes +import net.corda.core.until import net.corda.core.utilities.loggerFor import net.corda.demobench.model.NodeConfig import net.corda.demobench.readErrorLines import java.io.IOException +import java.net.HttpURLConnection +import java.net.URI +import java.time.Instant import java.util.concurrent.Executors +import java.util.concurrent.TimeoutException +import kotlin.concurrent.thread class WebServer internal constructor(private val webServerController: WebServerController) : AutoCloseable { private companion object { @@ -15,13 +26,12 @@ class WebServer internal constructor(private val webServerController: WebServerC private var process: Process? = null @Throws(IOException::class) - fun open(config: NodeConfig, onExit: (NodeConfig) -> Unit) { + fun open(config: NodeConfig): ListenableFuture { val nodeDir = config.nodeDir.toFile() if (!nodeDir.isDirectory) { log.warn("Working directory '{}' does not exist.", nodeDir.absolutePath) - onExit(config) - return + return SettableFuture.create() } try { @@ -46,12 +56,18 @@ class WebServer internal constructor(private val webServerController: WebServerC } else { log.error("Web Server for '{}' has exited (value={}, {})", config.legalName, exitValue, errors) } - - onExit(config) } + + val future = SettableFuture.create() + thread { + future.catch { + log.info("Waiting for web server for ${config.legalName} to start ...") + waitForStart(config.webPort) + } + } + return future } catch (e: IOException) { log.error("Failed to launch Web Server for '{}': {}", config.legalName, e.message) - onExit(config) throw e } } @@ -68,4 +84,24 @@ class WebServer internal constructor(private val webServerController: WebServerC log.error("Failed to close stream: '{}'", e.message) } } + + private fun waitForStart(port: Int): URI { + val url = URI("http://localhost:$port/") + val rateLimiter = RateLimiter.create(2.0) + val start = Instant.now() + val timeout = 1.minutes + while ((start until Instant.now()) < timeout) { + try { + rateLimiter.acquire() + val conn = url.toURL().openConnection() as HttpURLConnection + conn.connectTimeout = 500 // msec + conn.requestMethod = "HEAD" + conn.connect() + conn.disconnect() + return url + } catch(e: IOException) { + } + } + throw TimeoutException("Web server did not start within ${timeout.seconds} seconds") + } }