mirror of
https://github.com/corda/corda.git
synced 2025-02-20 09:26:41 +00:00
CORDA-1686: Make Node Explorer release its RPC connection on shutdown. (#3457)
* Make Node Explorer release its RPC connection on shutdown. * Declare Explorer's login() function as tail-recursive. * Replace lateinit rpcConnection with oridinary var. * Notify the node when closing an RPC connection gracefully.
This commit is contained in:
parent
82f79387a3
commit
76c114502e
@ -40,7 +40,7 @@ data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val mess
|
||||
/**
|
||||
* This model exposes raw event streams to and from the node.
|
||||
*/
|
||||
class NodeMonitorModel {
|
||||
class NodeMonitorModel : AutoCloseable {
|
||||
|
||||
private val retryableStateMachineUpdatesSubject = PublishSubject.create<StateMachineUpdate>()
|
||||
private val stateMachineUpdatesSubject = PublishSubject.create<StateMachineUpdate>()
|
||||
@ -49,6 +49,7 @@ class NodeMonitorModel {
|
||||
private val stateMachineTransactionMappingSubject = PublishSubject.create<StateMachineTransactionMapping>()
|
||||
private val progressTrackingSubject = PublishSubject.create<ProgressTrackingEvent>()
|
||||
private val networkMapSubject = PublishSubject.create<MapChange>()
|
||||
private var rpcConnection: CordaRPCConnection? = null
|
||||
|
||||
val stateMachineUpdates: Observable<StateMachineUpdate> = stateMachineUpdatesSubject
|
||||
val vaultUpdates: Observable<Vault.Update<ContractState>> = vaultUpdatesSubject
|
||||
@ -84,6 +85,17 @@ class NodeMonitorModel {
|
||||
*/
|
||||
class CordaRPCOpsWrapper(val cordaRPCOps: CordaRPCOps)
|
||||
|
||||
/**
|
||||
* Disconnects from the Corda node for a clean client shutdown.
|
||||
*/
|
||||
override fun close() {
|
||||
try {
|
||||
rpcConnection?.notifyServerAndClose()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error closing RPC connection to node", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register for updates to/from a given vault.
|
||||
* TODO provide an unsubscribe mechanism
|
||||
@ -145,9 +157,10 @@ class NodeMonitorModel {
|
||||
}
|
||||
|
||||
private fun performRpcReconnect(nodeHostAndPort: NetworkHostAndPort, username: String, password: String, shouldRetry: Boolean): List<StateMachineInfo> {
|
||||
|
||||
val connection = establishConnectionWithRetry(nodeHostAndPort, username, password, shouldRetry)
|
||||
val proxy = connection.proxy
|
||||
val proxy = establishConnectionWithRetry(nodeHostAndPort, username, password, shouldRetry).let { connection ->
|
||||
rpcConnection = connection
|
||||
connection.proxy
|
||||
}
|
||||
|
||||
val (stateMachineInfos, stateMachineUpdatesRaw) = proxy.stateMachinesFeed()
|
||||
|
||||
@ -162,7 +175,7 @@ class NodeMonitorModel {
|
||||
// It is good idea to close connection to properly mark the end of it. During re-connect we will create a new
|
||||
// client and a new connection, so no going back to this one. Also the server might be down, so we are
|
||||
// force closing the connection to avoid propagation of notification to the server side.
|
||||
connection.forceClose()
|
||||
rpcConnection?.forceClose()
|
||||
// Perform re-connect.
|
||||
performRpcReconnect(nodeHostAndPort, username, password, shouldRetry = true)
|
||||
})
|
||||
@ -175,18 +188,17 @@ class NodeMonitorModel {
|
||||
}
|
||||
|
||||
private fun establishConnectionWithRetry(nodeHostAndPort: NetworkHostAndPort, username: String, password: String, shouldRetry: Boolean): CordaRPCConnection {
|
||||
|
||||
val retryInterval = 5.seconds
|
||||
|
||||
val client = CordaRPCClient(
|
||||
nodeHostAndPort,
|
||||
CordaRPCClientConfiguration.DEFAULT.copy(
|
||||
connectionMaxRetryInterval = retryInterval
|
||||
)
|
||||
)
|
||||
do {
|
||||
val connection = try {
|
||||
logger.info("Connecting to: $nodeHostAndPort")
|
||||
val client = CordaRPCClient(
|
||||
nodeHostAndPort,
|
||||
CordaRPCClientConfiguration.DEFAULT.copy(
|
||||
connectionMaxRetryInterval = retryInterval
|
||||
)
|
||||
)
|
||||
val _connection = client.start(username, password)
|
||||
// Check connection is truly operational before returning it.
|
||||
val nodeInfo = _connection.proxy.nodeInfo()
|
||||
@ -195,7 +207,7 @@ class NodeMonitorModel {
|
||||
} catch (throwable: Throwable) {
|
||||
if (shouldRetry) {
|
||||
// Deliberately not logging full stack trace as it will be full of internal stacktraces.
|
||||
logger.info("Exception upon establishing connection: " + throwable.message)
|
||||
logger.info("Exception upon establishing connection: {}", throwable.message)
|
||||
null
|
||||
} else {
|
||||
throw throwable
|
||||
|
@ -10,6 +10,7 @@ import javafx.stage.Stage
|
||||
import jfxtras.resources.JFXtrasFontRoboto
|
||||
import joptsimple.OptionParser
|
||||
import net.corda.client.jfx.model.Models
|
||||
import net.corda.client.jfx.model.NodeMonitorModel
|
||||
import net.corda.client.jfx.model.observableValue
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.explorer.model.CordaViewModel
|
||||
@ -21,6 +22,7 @@ import org.controlsfx.dialog.ExceptionDialog
|
||||
import tornadofx.App
|
||||
import tornadofx.addStageIcon
|
||||
import tornadofx.find
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Main class for Explorer, you will need Tornado FX to run the explorer.
|
||||
@ -34,6 +36,8 @@ class Main : App(MainView::class) {
|
||||
}
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
var nodeModel: NodeMonitorModel? = null
|
||||
|
||||
// Login to Corda node
|
||||
super.start(stage)
|
||||
stage.minHeight = 600.0
|
||||
@ -43,27 +47,29 @@ class Main : App(MainView::class) {
|
||||
val button = Alert(Alert.AlertType.CONFIRMATION, "Are you sure you want to exit Corda explorer?").apply {
|
||||
initOwner(stage.scene.window)
|
||||
}.showAndWait().get()
|
||||
if (button != ButtonType.OK) it.consume()
|
||||
if (button == ButtonType.OK) {
|
||||
nodeModel?.close()
|
||||
} else {
|
||||
it.consume()
|
||||
}
|
||||
}
|
||||
|
||||
val hostname = parameters.named["host"]
|
||||
val port = asInteger(parameters.named["port"])
|
||||
val username = parameters.named["username"]
|
||||
val password = parameters.named["password"]
|
||||
var isLoggedIn = false
|
||||
|
||||
if ((hostname != null) && (port != null) && (username != null) && (password != null)) {
|
||||
try {
|
||||
loginView.login(hostname, port, username, password)
|
||||
isLoggedIn = true
|
||||
nodeModel = loginView.login(hostname, port, username, password)
|
||||
} catch (e: Exception) {
|
||||
ExceptionDialog(e).apply { initOwner(stage.scene.window) }.showAndWait()
|
||||
}
|
||||
}
|
||||
|
||||
if (!isLoggedIn) {
|
||||
if (nodeModel == null) {
|
||||
stage.hide()
|
||||
loginView.login()
|
||||
nodeModel = loginView.login()
|
||||
stage.show()
|
||||
}
|
||||
}
|
||||
@ -84,7 +90,7 @@ class Main : App(MainView::class) {
|
||||
runInFxApplicationThread {
|
||||
// [showAndWait] need to be in the FX thread.
|
||||
ExceptionDialog(throwable).showAndWait()
|
||||
System.exit(1)
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
// Do this first before creating the notification bar, so it can autosize itself properly.
|
||||
|
@ -27,11 +27,14 @@ class LoginView : View(WINDOW_TITLE) {
|
||||
private val port by objectProperty(SettingsModel::portProperty)
|
||||
private val fullscreen by objectProperty(SettingsModel::fullscreenProperty)
|
||||
|
||||
fun login(host: String, port: Int, username: String, password: String) {
|
||||
getModel<NodeMonitorModel>().register(NetworkHostAndPort(host, port), username, password)
|
||||
fun login(host: String, port: Int, username: String, password: String): NodeMonitorModel {
|
||||
return getModel<NodeMonitorModel>().apply {
|
||||
register(NetworkHostAndPort(host, port), username, password)
|
||||
}
|
||||
}
|
||||
|
||||
fun login() {
|
||||
tailrec fun login(): NodeMonitorModel? {
|
||||
var nodeModel: NodeMonitorModel? = null
|
||||
val status = Dialog<LoginStatus>().apply {
|
||||
dialogPane = root
|
||||
setResultConverter {
|
||||
@ -39,7 +42,7 @@ class LoginView : View(WINDOW_TITLE) {
|
||||
ButtonBar.ButtonData.OK_DONE -> try {
|
||||
root.isDisable = true
|
||||
// TODO : Run this async to avoid UI lockup.
|
||||
login(hostTextField.text, portProperty.value, usernameTextField.text, passwordTextField.text)
|
||||
nodeModel = login(hostTextField.text, portProperty.value, usernameTextField.text, passwordTextField.text)
|
||||
if (!rememberMe.value) {
|
||||
username.value = ""
|
||||
host.value = ""
|
||||
@ -64,12 +67,13 @@ class LoginView : View(WINDOW_TITLE) {
|
||||
initOwner(root.scene.window)
|
||||
}.showAndWait().get()
|
||||
if (button == ButtonType.OK) {
|
||||
nodeModel?.close()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.showAndWait().get()
|
||||
if (status != LoginStatus.loggedIn) login()
|
||||
return if (status == LoginStatus.loggedIn) nodeModel else login()
|
||||
}
|
||||
|
||||
init {
|
||||
|
Loading…
x
Reference in New Issue
Block a user