Node: if the node is started twice, abort and tell the sysadmin the PID of the other instance.

This commit is contained in:
Mike Hearn 2016-02-05 17:32:00 +01:00
parent 7a70cdd4de
commit 5d0d926568

View File

@ -14,8 +14,12 @@ import core.messaging.*
import core.serialization.deserialize
import core.serialization.serialize
import core.utilities.loggerFor
import java.io.RandomAccessFile
import java.lang.management.ManagementFactory
import java.nio.channels.FileLock
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.util.*
@ -65,17 +69,27 @@ class Node(val dir: Path, val myNetAddr: HostAndPort, val configuration: NodeCon
override val identityService: IdentityService get() = identity
}
// TODO: Implement mutual exclusion so we can't start the node twice by accident.
val storage = makeStorageService(dir)
val smm = StateMachineManager(services, serverThread)
val net = ArtemisMessagingService(dir, myNetAddr)
val wallet: WalletService = E2ETestWalletService(services)
val keyManagement = E2ETestKeyManagementService()
val storage: StorageService
val smm: StateMachineManager
val net: ArtemisMessagingService
val wallet: WalletService
val keyManagement: E2ETestKeyManagementService
val inNodeTimestampingService: TimestamperNodeService?
val identity: IdentityService
// Avoid the lock being garbage collected. We don't really need to release it as the OS will do so for us
// when our process shuts down, but we try in stop() anyway just to be nice.
private var nodeFileLock: FileLock? = null
init {
alreadyRunningNodeCheck()
storage = makeStorageService(dir)
smm = StateMachineManager(services, serverThread)
net = ArtemisMessagingService(dir, myNetAddr)
wallet = E2ETestWalletService(services)
keyManagement = E2ETestKeyManagementService()
// Insert a network map entry for the timestamper: this is all temp scaffolding and will go away. If we are
// given the details, the timestamping node is somewhere else. Otherwise, we do our own timestamping.
val tsid = if (timestamperAddress != null) {
@ -101,6 +115,7 @@ class Node(val dir: Path, val myNetAddr: HostAndPort, val configuration: NodeCon
fun stop() {
net.stop()
serverThread.shutdownNow()
nodeFileLock!!.release()
}
fun makeStorageService(dir: Path): StorageService {
@ -152,6 +167,30 @@ class Node(val dir: Path, val myNetAddr: HostAndPort, val configuration: NodeCon
}
}
private fun alreadyRunningNodeCheck() {
// Write out our process ID (which may or may not resemble a UNIX process id - to us it's just a string) to a
// file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already
// exists, we try to take the file lock first before replacing it and if that fails it means we're being started
// twice with the same directory: that's a user error and we should bail out.
val pidPath = dir.resolve("process-id")
val file = pidPath.toFile()
if (file.exists()) {
val f = RandomAccessFile(file, "rw")
val l = f.channel.tryLock()
if (l == null) {
println("It appears there is already a node running with the specified data directory $dir")
println("Shut that other node down and try again. It may have process ID ${file.readText()}")
System.exit(1)
}
nodeFileLock = l
}
val ourProcessID: String = ManagementFactory.getRuntimeMXBean().name.split("@")[0]
Files.write(pidPath, ourProcessID.toByteArray(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)
pidPath.toFile().deleteOnExit()
if (nodeFileLock == null)
nodeFileLock = RandomAccessFile(file, "rw").channel.lock()
}
companion object {
val PRIVATE_KEY_FILE_NAME = "identity-private-key"
val PUBLIC_IDENTITY_FILE_NAME = "identity-public"