CORDA-3139: Cater for port already bound scenario during port allocation (#5361)

* CORDA-3139: Cater for port already bound scenario during port allocation

Also moved `SharedMemoryIncremental` into a separate file as it getting bigger
and improved readability of logic and added some logging.

* CORDA-3139: Fix the unit test

* CORDA-3139: Improve logging when failing

* CORDA-3139: Improve stability of the test
This commit is contained in:
Viktor Kolomeyko
2019-08-12 10:38:15 +01:00
committed by Matthew Nesbit
parent b8e278680d
commit 6db3ded032
5 changed files with 153 additions and 98 deletions

View File

@ -36,7 +36,8 @@ public class PortAllocationRunner {
* allocate ports and print out for later consumption by the spawning test
*/
PortAllocation pa = PortAllocation.getDefaultAllocator();
for (int i = 0; i < 10000; i++) {
int iterCount = Integer.parseInt(args[2]);
for (int i = 0; i < iterCount; i++) {
System.out.println(pa.nextPort());
}
}

View File

@ -1,25 +1,33 @@
package net.corda.testing.driver
import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
import net.corda.testing.node.PortAllocationRunner
import org.assertj.core.util.Files
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.core.IsNot.not
import org.hamcrest.number.OrderingComparison
import org.junit.Assert
import org.junit.Assume.assumeFalse
import org.junit.Test
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.util.concurrent.TimeUnit
import kotlin.streams.toList
class PortAllocationTest {
companion object {
val logger = contextLogger()
}
@Test
fun `should allocate a port whilst cycling back round if exceeding start of ephemeral range`() {
val startingPoint = PortAllocation.DEFAULT_START_PORT
val portAllocator = PortAllocation.defaultAllocator
var previous = portAllocator.nextPort()
(0 until 1000000).forEach { _ ->
(0 until 50_000).forEach { _ ->
val next = portAllocator.nextPort()
Assert.assertThat(next, `is`(not(previous)))
Assert.assertThat(next, `is`(OrderingComparison.lessThan(PortAllocation.FIRST_EPHEMERAL_PORT)))
@ -27,7 +35,7 @@ class PortAllocationTest {
if (next == startingPoint) {
Assert.assertThat(previous, `is`(PortAllocation.FIRST_EPHEMERAL_PORT - 1))
} else {
Assert.assertThat(next, `is`(previous + 1))
Assert.assertTrue(next >= previous + 1)
}
previous = next
}
@ -35,50 +43,60 @@ class PortAllocationTest {
@Test(timeout = 120_000)
fun `should support multiprocess port allocation`() {
if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
println("Starting multiprocess port allocation test")
val spinnerFile = Files.newTemporaryFile().also { it.deleteOnExit() }.absolutePath
val process1 = buildJvmProcess(spinnerFile, 1)
val process2 = buildJvmProcess(spinnerFile, 2)
assumeFalse(System.getProperty("os.name").toLowerCase().contains("windows"))
println("Started child processes")
logger.info("Starting multiprocess port allocation test")
val spinnerFile = Files.newTemporaryFile().also { it.deleteOnExit() }.absolutePath
val iterCount = 8_000 // Default port range 10000-30000 since we will have 2 processes we want to make sure there is enough leg room
// If we rollover, we may well receive the ports that were already given to a different process
val process1 = buildJvmProcess(spinnerFile, 1, iterCount)
val process2 = buildJvmProcess(spinnerFile, 2, iterCount)
val processes = listOf(process1, process2)
logger.info("Started child processes")
val spinnerBackingFile = RandomAccessFile(spinnerFile, "rw")
println("Mapped spinner file")
val spinnerBuffer = spinnerBackingFile.channel.map(FileChannel.MapMode.READ_WRITE, 0, 512)
println("Created spinner buffer")
val processes = listOf(process1, process2)
var timeWaited = 0L
val spinnerBackingFile = RandomAccessFile(spinnerFile, "rw")
logger.info("Mapped spinner file at: $spinnerFile")
val spinnerBuffer = spinnerBackingFile.channel.map(FileChannel.MapMode.READ_WRITE, 0, 512)
logger.info("Created spinner buffer")
while (spinnerBuffer.getShort(1) != 10.toShort() && spinnerBuffer.getShort(2) != 10.toShort() && timeWaited < 60_000) {
println("Waiting to childProcesses to report back. waited ${timeWaited}ms")
Thread.sleep(1000)
timeWaited += 1000
}
var timeWaited = 0L
//GO!
println("Instructing child processes to start allocating ports")
spinnerBuffer.putShort(0, 8)
println("Waiting for child processes to terminate")
processes.forEach { it.waitFor(1, TimeUnit.MINUTES) }
println("child processes terminated")
val process1Output = process1.inputStream.reader().readLines().toSet()
val process2Output = process2.inputStream.reader().readLines().toSet()
println("child process out captured")
Assert.assertThat(process1Output.size, `is`(10_000))
Assert.assertThat(process2Output.size, `is`(10_000))
//there should be no overlap between the outputs as each process should have been allocated a unique set of ports
Assert.assertThat(process1Output.intersect(process2Output), `is`(emptySet()))
while (spinnerBuffer.getShort(1) != 10.toShort() && spinnerBuffer.getShort(2) != 10.toShort() && timeWaited < 60_000) {
logger.info("Waiting to childProcesses to report back. waited ${timeWaited}ms")
Thread.sleep(1000)
timeWaited += 1000
}
//GO!
logger.info("Instructing child processes to start allocating ports")
spinnerBuffer.putShort(0, 8)
logger.info("Waiting for child processes to terminate")
val terminationStatuses = processes.parallelStream().map { if(it.waitFor(1, TimeUnit.MINUTES)) "OK" else "STILL RUNNING" }.toList()
logger.info("child processes terminated: $terminationStatuses")
fun List<String>.setOfPorts() : Set<Int> {
// May include warnings when ports are busy
return map { Try.on { Integer.parseInt(it)} }.filter { it.isSuccess }.map { it.getOrThrow() }.toSet()
}
val lines1 = process1.inputStream.reader().readLines()
val portsAllocated1 = lines1.setOfPorts()
val lines2 = process2.inputStream.reader().readLines()
val portsAllocated2 = lines2.setOfPorts()
logger.info("child process out captured")
Assert.assertThat(lines1.joinToString(), portsAllocated1.size, `is`(iterCount))
Assert.assertThat(lines2.joinToString(), portsAllocated2.size, `is`(iterCount))
//there should be no overlap between the outputs as each process should have been allocated a unique set of ports
val intersect = portsAllocated1.intersect(portsAllocated2)
Assert.assertThat(intersect.joinToString(), intersect, `is`(emptySet()))
}
private fun buildJvmProcess(spinnerFile: String, reportingIndex: Int): Process {
private fun buildJvmProcess(spinnerFile: String, reportingIndex: Int, iterCount: Int): Process {
val separator = System.getProperty("file.separator")
val classpath = System.getProperty("java.class.path")
val path = (System.getProperty("java.home")
@ -87,11 +105,9 @@ class PortAllocationTest {
classpath,
PortAllocationRunner::class.java.name,
spinnerFile,
reportingIndex.toString())
reportingIndex.toString(),
iterCount.toString())
return processBuilder.start()
}
}
}