Fixed "fast" reconnect issues, where sometimes the media driver would be disconnected from the aeron driver

This commit is contained in:
Robinson 2021-04-27 13:57:47 +02:00
parent 9e0bc0da3e
commit f53e705432
14 changed files with 656 additions and 533 deletions

View File

@ -18,7 +18,7 @@ package dorkbox.network
import dorkbox.netUtil.IP import dorkbox.netUtil.IP
import dorkbox.netUtil.IPv4 import dorkbox.netUtil.IPv4
import dorkbox.netUtil.IPv6 import dorkbox.netUtil.IPv6
import dorkbox.network.aeron.AeronConfig import dorkbox.network.aeron.AeronDriver
import dorkbox.network.aeron.IpcMediaDriverConnection import dorkbox.network.aeron.IpcMediaDriverConnection
import dorkbox.network.aeron.UdpMediaDriverClientConnection import dorkbox.network.aeron.UdpMediaDriverClientConnection
import dorkbox.network.connection.* import dorkbox.network.connection.*
@ -170,8 +170,8 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
connectionTimeoutMS: Long = 30_000L, reliable: Boolean = true) { connectionTimeoutMS: Long = 30_000L, reliable: Boolean = true) {
// Default IPC ports are flipped because they are in the perspective of the SERVER // Default IPC ports are flipped because they are in the perspective of the SERVER
connect(remoteAddress = remoteAddress, connect(remoteAddress = remoteAddress,
ipcPublicationId = AeronConfig.IPC_HANDSHAKE_STREAM_ID_SUB, ipcPublicationId = AeronDriver.IPC_HANDSHAKE_STREAM_ID_SUB,
ipcSubscriptionId = AeronConfig.IPC_HANDSHAKE_STREAM_ID_PUB, ipcSubscriptionId = AeronDriver.IPC_HANDSHAKE_STREAM_ID_PUB,
connectionTimeoutMS = connectionTimeoutMS, connectionTimeoutMS = connectionTimeoutMS,
reliable = reliable) reliable = reliable)
} }
@ -188,8 +188,8 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
* @throws ClientRejectedException if the client connection is rejected * @throws ClientRejectedException if the client connection is rejected
*/ */
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
suspend fun connect(ipcPublicationId: Int = AeronConfig.IPC_HANDSHAKE_STREAM_ID_SUB, suspend fun connect(ipcPublicationId: Int = AeronDriver.IPC_HANDSHAKE_STREAM_ID_SUB,
ipcSubscriptionId: Int = AeronConfig.IPC_HANDSHAKE_STREAM_ID_PUB, ipcSubscriptionId: Int = AeronDriver.IPC_HANDSHAKE_STREAM_ID_PUB,
connectionTimeoutMS: Long = 30_000L) { connectionTimeoutMS: Long = 30_000L) {
// Default IPC ports are flipped because they are in the perspective of the SERVER // Default IPC ports are flipped because they are in the perspective of the SERVER
@ -229,8 +229,8 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
private suspend fun connect(remoteAddress: InetAddress? = null, private suspend fun connect(remoteAddress: InetAddress? = null,
// Default IPC ports are flipped because they are in the perspective of the SERVER // Default IPC ports are flipped because they are in the perspective of the SERVER
ipcPublicationId: Int = AeronConfig.IPC_HANDSHAKE_STREAM_ID_SUB, ipcPublicationId: Int = AeronDriver.IPC_HANDSHAKE_STREAM_ID_SUB,
ipcSubscriptionId: Int = AeronConfig.IPC_HANDSHAKE_STREAM_ID_PUB, ipcSubscriptionId: Int = AeronDriver.IPC_HANDSHAKE_STREAM_ID_PUB,
connectionTimeoutMS: Long = 30_000L, reliable: Boolean = true) { connectionTimeoutMS: Long = 30_000L, reliable: Boolean = true) {
require(connectionTimeoutMS >= 0) { "connectionTimeoutMS '$connectionTimeoutMS' is invalid. It must be >0" } require(connectionTimeoutMS >= 0) { "connectionTimeoutMS '$connectionTimeoutMS' is invalid. It must be >0" }
@ -250,7 +250,7 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
connection0 = null connection0 = null
// we are done with initial configuration, now initialize aeron and the general state of this endpoint // we are done with initial configuration, now initialize aeron and the general state of this endpoint
val aeron = initEndpointState() initEndpointState()
// only try to connect via IPv4 if we have a network interface that supports it! // only try to connect via IPv4 if we have a network interface that supports it!
if (remoteAddress is Inet4Address && !IPv4.isAvailable) { if (remoteAddress is Inet4Address && !IPv4.isAvailable) {
@ -271,7 +271,7 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
var isUsingIPC = false var isUsingIPC = false
val canUseIPC = config.enableIpc && remoteAddress == null val canUseIPC = config.enableIpc && remoteAddress == null
val autoChangeToIpc = canUseIPC && config.enableIpcForLoopback && val autoChangeToIpc = canUseIPC && config.enableIpcForLoopback &&
remoteAddress != null && remoteAddress.isLoopbackAddress && isRunning(mediaDriverContext) remoteAddress != null && remoteAddress.isLoopbackAddress && aeronDriver.isRunning()
if (autoChangeToIpc) { if (autoChangeToIpc) {
logger.info {"IPC for loopback enabled and aeron is already running. Auto-changing network connection from ${IP.toString(remoteAddress!!)} -> IPC" } logger.info {"IPC for loopback enabled and aeron is already running. Auto-changing network connection from ${IP.toString(remoteAddress!!)} -> IPC" }
} }
@ -282,11 +282,11 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
// MAYBE the server doesn't have IPC enabled? If no, we need to connect via UDP instead // MAYBE the server doesn't have IPC enabled? If no, we need to connect via UDP instead
val ipcConnection = IpcMediaDriverConnection(streamIdSubscription = ipcSubscriptionId, val ipcConnection = IpcMediaDriverConnection(streamIdSubscription = ipcSubscriptionId,
streamId = ipcPublicationId, streamId = ipcPublicationId,
sessionId = AeronConfig.RESERVED_SESSION_ID_INVALID) sessionId = AeronDriver.RESERVED_SESSION_ID_INVALID)
// throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports // throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports
try { try {
ipcConnection.buildClient(aeron, logger) ipcConnection.buildClient(aeronDriver, logger)
isUsingIPC = true isUsingIPC = true
} catch (e: Exception) { } catch (e: Exception) {
// if we specified that we want to use IPC, then we have to throw the timeout exception, because there is no IPC // if we specified that we want to use IPC, then we have to throw the timeout exception, because there is no IPC
@ -305,13 +305,13 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
address = this.remoteAddress0!!, address = this.remoteAddress0!!,
publicationPort = config.subscriptionPort, publicationPort = config.subscriptionPort,
subscriptionPort = config.publicationPort, subscriptionPort = config.publicationPort,
streamId = AeronConfig.UDP_HANDSHAKE_STREAM_ID, streamId = AeronDriver.UDP_HANDSHAKE_STREAM_ID,
sessionId = AeronConfig.RESERVED_SESSION_ID_INVALID, sessionId = AeronDriver.RESERVED_SESSION_ID_INVALID,
connectionTimeoutMS = connectionTimeoutMS, connectionTimeoutMS = connectionTimeoutMS,
isReliable = reliable) isReliable = reliable)
// throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports // throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports
udpConnection.buildClient(aeron, logger) udpConnection.buildClient(aeronDriver, logger)
udpConnection udpConnection
} }
} }
@ -320,13 +320,13 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
address = this.remoteAddress0!!, address = this.remoteAddress0!!,
publicationPort = config.subscriptionPort, publicationPort = config.subscriptionPort,
subscriptionPort = config.publicationPort, subscriptionPort = config.publicationPort,
streamId = AeronConfig.UDP_HANDSHAKE_STREAM_ID, streamId = AeronDriver.UDP_HANDSHAKE_STREAM_ID,
sessionId = AeronConfig.RESERVED_SESSION_ID_INVALID, sessionId = AeronDriver.RESERVED_SESSION_ID_INVALID,
connectionTimeoutMS = connectionTimeoutMS, connectionTimeoutMS = connectionTimeoutMS,
isReliable = reliable) isReliable = reliable)
// throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports // throws a ConnectTimedOutException if the client cannot connect for any reason to the server handshake ports
test.buildClient(aeron, logger) test.buildClient(aeronDriver, logger)
test test
} }
@ -381,7 +381,7 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
} }
// we have to construct how the connection will communicate! // we have to construct how the connection will communicate!
clientConnection.buildClient(aeron, logger) clientConnection.buildClient(aeronDriver, logger)
// only the client connects to the server, so here we have to connect. The server (when creating the new "connection" object) // only the client connects to the server, so here we have to connect. The server (when creating the new "connection" object)
// does not need to do anything // does not need to do anything
@ -424,6 +424,7 @@ open class Client<CONNECTION : Connection>(config: Configuration = Configuration
if (!permitConnection) { if (!permitConnection) {
handshakeConnection.close() handshakeConnection.close()
val exception = ClientRejectedException("Connection to ${IP.toString(remoteAddress)} was not permitted!") val exception = ClientRejectedException("Connection to ${IP.toString(remoteAddress)} was not permitted!")
ListenerManager.cleanStackTrace(exception)
listenerManager.notifyError(exception) listenerManager.notifyError(exception)
throw exception throw exception
} }

View File

@ -17,7 +17,7 @@ package dorkbox.network
import dorkbox.netUtil.IPv4 import dorkbox.netUtil.IPv4
import dorkbox.netUtil.IPv6 import dorkbox.netUtil.IPv6
import dorkbox.network.aeron.AeronConfig import dorkbox.network.aeron.AeronDriver
import dorkbox.network.aeron.CoroutineBackoffIdleStrategy import dorkbox.network.aeron.CoroutineBackoffIdleStrategy
import dorkbox.network.aeron.CoroutineIdleStrategy import dorkbox.network.aeron.CoroutineIdleStrategy
import dorkbox.network.aeron.CoroutineSleepingMillisIdleStrategy import dorkbox.network.aeron.CoroutineSleepingMillisIdleStrategy
@ -66,7 +66,7 @@ class ServerConfiguration : dorkbox.network.Configuration() {
/** /**
* The IPC Publication ID is used to define what ID the server will send data on. The client IPC subscription ID must match this value. * The IPC Publication ID is used to define what ID the server will send data on. The client IPC subscription ID must match this value.
*/ */
var ipcPublicationId = AeronConfig.IPC_HANDSHAKE_STREAM_ID_PUB var ipcPublicationId = AeronDriver.IPC_HANDSHAKE_STREAM_ID_PUB
set(value) { set(value) {
require(context == null) { errorMessage } require(context == null) { errorMessage }
field = value field = value
@ -75,7 +75,7 @@ class ServerConfiguration : dorkbox.network.Configuration() {
/** /**
* The IPC Subscription ID is used to define what ID the server will receive data on. The client IPC publication ID must match this value. * The IPC Subscription ID is used to define what ID the server will receive data on. The client IPC publication ID must match this value.
*/ */
var ipcSubscriptionId = AeronConfig.IPC_HANDSHAKE_STREAM_ID_SUB var ipcSubscriptionId = AeronDriver.IPC_HANDSHAKE_STREAM_ID_SUB
set(value) { set(value) {
require(context == null) { errorMessage } require(context == null) { errorMessage }
field = value field = value

View File

@ -18,7 +18,7 @@ package dorkbox.network
import dorkbox.netUtil.IP import dorkbox.netUtil.IP
import dorkbox.netUtil.IPv4 import dorkbox.netUtil.IPv4
import dorkbox.netUtil.IPv6 import dorkbox.netUtil.IPv6
import dorkbox.network.aeron.AeronConfig import dorkbox.network.aeron.AeronDriver
import dorkbox.network.aeron.AeronPoller import dorkbox.network.aeron.AeronPoller
import dorkbox.network.aeron.IpcMediaDriverConnection import dorkbox.network.aeron.IpcMediaDriverConnection
import dorkbox.network.aeron.UdpMediaDriverServerConnection import dorkbox.network.aeron.UdpMediaDriverServerConnection
@ -35,7 +35,6 @@ import dorkbox.network.rmi.RemoteObject
import dorkbox.network.rmi.RemoteObjectStorage import dorkbox.network.rmi.RemoteObjectStorage
import dorkbox.network.rmi.RmiManagerConnections import dorkbox.network.rmi.RmiManagerConnections
import dorkbox.network.rmi.TimeoutException import dorkbox.network.rmi.TimeoutException
import io.aeron.Aeron
import io.aeron.FragmentAssembler import io.aeron.FragmentAssembler
import io.aeron.Image import io.aeron.Image
import io.aeron.logbuffer.Header import io.aeron.logbuffer.Header
@ -71,12 +70,12 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
*/ */
fun isRunning(configuration: ServerConfiguration): Boolean { fun isRunning(configuration: ServerConfiguration): Boolean {
if (configuration.context == null) { if (configuration.context == null) {
AeronConfig.createContext(configuration) AeronDriver.createContext(configuration)
} }
require(configuration.context != null) { "Configuration context cannot be properly created. Unable to continue!" } require(configuration.context != null) { "Configuration context cannot be properly created. Unable to continue!" }
return AeronConfig.isRunning(configuration.context!!) return AeronDriver.isRunning(configuration.context!!)
} }
init { init {
@ -160,12 +159,13 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
return super.getRmiConnectionSupport() return super.getRmiConnectionSupport()
} }
private suspend fun getIpcPoller(aeron: Aeron, config: ServerConfiguration): AeronPoller { private suspend fun getIpcPoller(aeronDriver: AeronDriver, config: ServerConfiguration): AeronPoller {
val poller = if (config.enableIpc) { val poller = if (config.enableIpc) {
val driver = IpcMediaDriverConnection(streamIdSubscription = config.ipcSubscriptionId, val driver = IpcMediaDriverConnection(streamIdSubscription = config.ipcSubscriptionId,
streamId = config.ipcPublicationId, streamId = config.ipcPublicationId,
sessionId = AeronConfig.RESERVED_SESSION_ID_INVALID) sessionId = AeronDriver.RESERVED_SESSION_ID_INVALID)
driver.buildServer(aeron, logger) driver.buildServer(aeronDriver, logger)
val publication = driver.publication val publication = driver.publication
val subscription = driver.subscription val subscription = driver.subscription
@ -193,7 +193,7 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
publication, publication,
sessionId, sessionId,
message, message,
aeron) aeronDriver)
} }
override fun poll(): Int { return subscription.poll(handler, 1) } override fun poll(): Int { return subscription.poll(handler, 1) }
@ -213,17 +213,18 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
} }
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
private suspend fun getIpv4Poller(aeron: Aeron, config: ServerConfiguration): AeronPoller { private suspend fun getIpv4Poller(aeronDriver: AeronDriver, config: ServerConfiguration): AeronPoller {
val poller = if (canUseIPv4) { val poller = if (canUseIPv4) {
val driver = UdpMediaDriverServerConnection( val driver = UdpMediaDriverServerConnection(
listenAddress = listenIPv4Address!!, listenAddress = listenIPv4Address!!,
publicationPort = config.publicationPort, publicationPort = config.publicationPort,
subscriptionPort = config.subscriptionPort, subscriptionPort = config.subscriptionPort,
streamId = AeronConfig.UDP_HANDSHAKE_STREAM_ID, streamId = AeronDriver.UDP_HANDSHAKE_STREAM_ID,
sessionId = AeronConfig.RESERVED_SESSION_ID_INVALID, sessionId = AeronDriver.RESERVED_SESSION_ID_INVALID,
connectionTimeoutMS = TimeUnit.SECONDS.toMillis(config.connectionCloseTimeoutInSeconds.toLong())) connectionTimeoutMS = TimeUnit.SECONDS.toMillis(config.connectionCloseTimeoutInSeconds.toLong()))
driver.buildServer(aeron, logger) driver.buildServer(aeronDriver, logger)
val publication = driver.publication val publication = driver.publication
val subscription = driver.subscription val subscription = driver.subscription
@ -274,7 +275,7 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
clientAddressString, clientAddressString,
clientAddress, clientAddress,
message, message,
aeron, aeronDriver,
false) false)
} }
@ -295,17 +296,18 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
} }
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
private suspend fun getIpv6Poller(aeron: Aeron, config: ServerConfiguration): AeronPoller { private suspend fun getIpv6Poller(aeronDriver: AeronDriver, config: ServerConfiguration): AeronPoller {
val poller = if (canUseIPv6) { val poller = if (canUseIPv6) {
val driver = UdpMediaDriverServerConnection( val driver = UdpMediaDriverServerConnection(
listenAddress = listenIPv6Address!!, listenAddress = listenIPv6Address!!,
publicationPort = config.publicationPort, publicationPort = config.publicationPort,
subscriptionPort = config.subscriptionPort, subscriptionPort = config.subscriptionPort,
streamId = AeronConfig.UDP_HANDSHAKE_STREAM_ID, streamId = AeronDriver.UDP_HANDSHAKE_STREAM_ID,
sessionId = AeronConfig.RESERVED_SESSION_ID_INVALID, sessionId = AeronDriver.RESERVED_SESSION_ID_INVALID,
connectionTimeoutMS = TimeUnit.SECONDS.toMillis(config.connectionCloseTimeoutInSeconds.toLong())) connectionTimeoutMS = TimeUnit.SECONDS.toMillis(config.connectionCloseTimeoutInSeconds.toLong()))
driver.buildServer(aeron, logger) driver.buildServer(aeronDriver, logger)
val publication = driver.publication val publication = driver.publication
val subscription = driver.subscription val subscription = driver.subscription
@ -356,7 +358,7 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
clientAddressString, clientAddressString,
clientAddress, clientAddress,
message, message,
aeron, aeronDriver,
false) false)
} }
@ -377,16 +379,17 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
} }
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
private suspend fun getIpv6WildcardPoller(aeron: Aeron, config: ServerConfiguration): AeronPoller { private suspend fun getIpv6WildcardPoller(aeronDriver: AeronDriver, config: ServerConfiguration): AeronPoller {
val driver = UdpMediaDriverServerConnection( val driver = UdpMediaDriverServerConnection(
listenAddress = listenIPv6Address!!, listenAddress = listenIPv6Address!!,
publicationPort = config.publicationPort, publicationPort = config.publicationPort,
subscriptionPort = config.subscriptionPort, subscriptionPort = config.subscriptionPort,
streamId = AeronConfig.UDP_HANDSHAKE_STREAM_ID, streamId = AeronDriver.UDP_HANDSHAKE_STREAM_ID,
sessionId = AeronConfig.RESERVED_SESSION_ID_INVALID, sessionId = AeronDriver.RESERVED_SESSION_ID_INVALID,
connectionTimeoutMS = TimeUnit.SECONDS.toMillis(config.connectionCloseTimeoutInSeconds.toLong())) connectionTimeoutMS = TimeUnit.SECONDS.toMillis(config.connectionCloseTimeoutInSeconds.toLong()))
driver.buildServer(aeron, logger) driver.buildServer(aeronDriver, logger)
val publication = driver.publication val publication = driver.publication
val subscription = driver.subscription val subscription = driver.subscription
@ -438,7 +441,7 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
clientAddressString, clientAddressString,
clientAddress, clientAddress,
message, message,
aeron, aeronDriver,
true) true)
} }
@ -461,17 +464,16 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
return return
} }
val aeron = initEndpointState() initEndpointState()
config as ServerConfiguration config as ServerConfiguration
// we are done with initial configuration, now initialize aeron and the general state of this endpoint // we are done with initial configuration, now initialize aeron and the general state of this endpoint
bindAlreadyCalled = true bindAlreadyCalled = true
val waiter = SuspendWaiter() val waiter = SuspendWaiter()
actionDispatch.launch { actionDispatch.launch {
val ipcPoller: AeronPoller = getIpcPoller(aeron, config) val ipcPoller: AeronPoller = getIpcPoller(aeronDriver, config)
// if we are binding to WILDCARD, then we have to do something special if BOTH IPv4 and IPv6 are enabled! // if we are binding to WILDCARD, then we have to do something special if BOTH IPv4 and IPv6 are enabled!
val isWildcard = listenIPv4Address == IPv4.WILDCARD || listenIPv6Address != IPv6.WILDCARD val isWildcard = listenIPv4Address == IPv4.WILDCARD || listenIPv6Address != IPv6.WILDCARD
@ -486,15 +488,15 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
override fun close() {} override fun close() {}
override fun serverInfo(): String { return "IPv4 Disabled" } override fun serverInfo(): String { return "IPv4 Disabled" }
} }
ipv6Poller = getIpv6WildcardPoller(aeron, config) ipv6Poller = getIpv6WildcardPoller(aeronDriver, config)
} else { } else {
// only 1 will be a real poller // only 1 will be a real poller
ipv4Poller = getIpv4Poller(aeron, config) ipv4Poller = getIpv4Poller(aeronDriver, config)
ipv6Poller = getIpv6Poller(aeron, config) ipv6Poller = getIpv6Poller(aeronDriver, config)
} }
} else { } else {
ipv4Poller = getIpv4Poller(aeron, config) ipv4Poller = getIpv4Poller(aeronDriver, config)
ipv6Poller = getIpv6Poller(aeron, config) ipv6Poller = getIpv6Poller(aeronDriver, config)
} }
waiter.doNotify() waiter.doNotify()

View File

@ -1,314 +0,0 @@
package dorkbox.network.aeron
import dorkbox.network.Configuration
import dorkbox.util.NamedThreadFactory
import io.aeron.driver.MediaDriver
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import mu.KLogger
import mu.KotlinLogging
import java.io.File
/**
*
*/
object AeronConfig {
/**
* Identifier for invalid sessions. This must be < RESERVED_SESSION_ID_LOW
*/
internal const val RESERVED_SESSION_ID_INVALID = 0
/**
* The inclusive lower bound of the reserved sessions range. THIS SHOULD NEVER BE <= 0!
*/
internal const val RESERVED_SESSION_ID_LOW = 1
/**
* The inclusive upper bound of the reserved sessions range.
*/
internal const val RESERVED_SESSION_ID_HIGH = Integer.MAX_VALUE
const val UDP_HANDSHAKE_STREAM_ID: Int = 0x1337cafe
const val IPC_HANDSHAKE_STREAM_ID_PUB: Int = 0x1337c0de
const val IPC_HANDSHAKE_STREAM_ID_SUB: Int = 0x1337c0d3
// on close, the publication CAN linger (in case a client goes away, and then comes back)
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
private const val AERON_PUBLICATION_LINGER_TIMEOUT = 5_000L // in MS
private fun create(config: Configuration, logger: KLogger): MediaDriver.Context {
/*
* Linux
* Linux normally requires some settings of sysctl values. One is net.core.rmem_max to allow larger SO_RCVBUF and
* net.core.wmem_max to allow larger SO_SNDBUF values to be set.
*
* Windows
* Windows tends to use SO_SNDBUF values that are too small. It is recommended to use values more like 1MB or so.
*
* Mac/Darwin
*
* Mac tends to use SO_SNDBUF values that are too small. It is recommended to use larger values, like 16KB.
*/
if (config.receiveBufferSize == 0) {
config.receiveBufferSize = io.aeron.driver.Configuration.SOCKET_RCVBUF_LENGTH_DEFAULT
// when {
// OS.isLinux() ->
// OS.isWindows() ->
// OS.isMacOsX() ->
// }
// val rmem_max = dorkbox.network.other.NetUtil.sysctlGetInt("net.core.rmem_max")
// val wmem_max = dorkbox.network.other.NetUtil.sysctlGetInt("net.core.wmem_max")
}
if (config.sendBufferSize == 0) {
config.receiveBufferSize = io.aeron.driver.Configuration.SOCKET_SNDBUF_LENGTH_DEFAULT
// when {
// OS.isLinux() ->
// OS.isWindows() ->
// OS.isMacOsX() ->
// }
// val rmem_max = dorkbox.network.other.NetUtil.sysctlGetInt("net.core.rmem_max")
// val wmem_max = dorkbox.network.other.NetUtil.sysctlGetInt("net.core.wmem_max")
}
/*
* Note: Since Mac OS does not have a built-in support for /dev/shm it is advised to create a RAM disk for the Aeron directory (aeron.dir).
*
* You can create a RAM disk with the following command:
*
* $ diskutil erasevolume HFS+ "DISK_NAME" `hdiutil attach -nomount ram://$((2048 * SIZE_IN_MB))`
*
* where:
*
* DISK_NAME should be replaced with a name of your choice.
* SIZE_IN_MB is the size in megabytes for the disk (e.g. 4096 for a 4GB disk).
*
* For example, the following command creates a RAM disk named DevShm which is 2GB in size:
*
* $ diskutil erasevolume HFS+ "DevShm" `hdiutil attach -nomount ram://$((2048 * 2048))`
*
* After this command is executed the new disk will be mounted under /Volumes/DevShm.
*/
if (config.aeronDirectory == null) {
val baseFileLocation = config.suggestAeronLogLocation(logger)
// val aeronLogDirectory = File(baseFileLocation, "aeron-" + type.simpleName)
val aeronLogDirectory = File(baseFileLocation, "aeron")
config.aeronDirectory = aeronLogDirectory
}
// LOW-LATENCY SETTINGS
// .termBufferSparseFile(false)
// .useWindowsHighResTimer(true)
// .threadingMode(ThreadingMode.DEDICATED)
// .conductorIdleStrategy(BusySpinIdleStrategy.INSTANCE)
// .receiverIdleStrategy(NoOpIdleStrategy.INSTANCE)
// .senderIdleStrategy(NoOpIdleStrategy.INSTANCE);
// setProperty(DISABLE_BOUNDS_CHECKS_PROP_NAME, "true");
// setProperty("aeron.mtu.length", "16384");
// setProperty("aeron.socket.so_sndbuf", "2097152");
// setProperty("aeron.socket.so_rcvbuf", "2097152");
// setProperty("aeron.rcv.initial.window.length", "2097152");
// driver context must happen in the initializer, because we have a Server.isRunning() method that uses the mediaDriverContext (without bind)
val context = MediaDriver.Context()
.publicationReservedSessionIdLow(RESERVED_SESSION_ID_LOW)
.publicationReservedSessionIdHigh(RESERVED_SESSION_ID_HIGH)
.threadingMode(config.threadingMode)
.mtuLength(config.networkMtuSize)
.socketSndbufLength(config.sendBufferSize)
.socketRcvbufLength(config.receiveBufferSize)
context.aeronDirectoryName(config.aeronDirectory!!.absolutePath)
if (context.ipcTermBufferLength() != io.aeron.driver.Configuration.ipcTermBufferLength()) {
// default 64 megs each is HUGE
context.ipcTermBufferLength(8 * 1024 * 1024)
}
if (context.publicationTermBufferLength() != io.aeron.driver.Configuration.termBufferLength()) {
// default 16 megs each is HUGE (we run out of space in production w/ lots of clients)
context.publicationTermBufferLength(2 * 1024 * 1024)
}
// we DO NOT want to abort the JVM if there are errors.
context.errorHandler { error ->
logger.error("Error in Aeron", error)
}
val aeronDir = File(context.aeronDirectoryName()).absoluteFile
context.aeronDirectoryName(aeronDir.path)
return context
}
/**
* Creates the Aeron Media Driver context
*
* @throws IllegalStateException if the configuration has already been used to create a context
* @throws IllegalArgumentException if the aeron media driver directory cannot be setup
*/
fun createContext(config: Configuration, logger: KLogger = KotlinLogging.logger("AeronConfig")) {
if (config.context != null) {
logger.warn { "Unable to recreate a context for a configuration that has already been created" }
return
}
var context = create(config, logger)
// this happens EXACTLY once. Must be BEFORE the "isRunning" check!
context.concludeAeronDirectory()
// will setup the aeron directory or throw IllegalArgumentException if it cannot be configured
val aeronDir = context.aeronDirectory()
var isRunning = isRunning(context)
// this is incompatible with IPC, and will not be set if IPC is enabled
if (config.aeronDirectoryForceUnique && isRunning) {
val savedParent = aeronDir.parentFile
var retry = 0
val retryMax = 100
while (config.aeronDirectoryForceUnique && isRunning) {
if (retry++ > retryMax) {
throw IllegalArgumentException("Unable to force unique aeron Directory. Tried $retryMax times and all tries were in use.")
}
val randomNum = (1..retryMax).shuffled().first()
val newDir = savedParent.resolve("${aeronDir.name}_$randomNum")
context = create(config, logger)
context.aeronDirectoryName(newDir.path)
// this happens EXACTLY once. Must be BEFORE the "isRunning" check!
context.concludeAeronDirectory()
isRunning = isRunning(context)
}
if (!isRunning) {
// NOTE: We must be *super* careful trying to delete directories, because if we have multiple AERON/MEDIA DRIVERS connected to the
// same directory, deleting the directory will cause any other aeron connection to fail! (which makes sense).
// since we are forcing a unique directory, we should ALSO delete it when we are done!
context.dirDeleteOnShutdown()
}
}
logger.info { "Aeron directory: '${context.aeronDirectory()}'" }
// once we do this, we cannot change any of the config values!
config.context = context
}
/**
* Checks to see if an endpoint (using the specified configuration) is running.
*
* @return true if the media driver is active and running
*/
fun isRunning(context: MediaDriver.Context): Boolean {
// if the media driver is running, it will be a quick connection. Usually 100ms or so
return context.isDriverActive(context.driverTimeoutMs()) { }
}
/**
* If the driver is not already running, this will start the driver
*
* @throws Exception if there is a problem starting the media driver
*/
fun startDriver(config: Configuration,
type: Class<*> = AeronConfig::class.java,
logger: KLogger = KotlinLogging.logger("AeronConfig")): MediaDriver? {
config.validate()
if (config.context == null) {
createContext(config, logger)
}
require(config.context != null) { "Configuration context cannot be properly created. Unable to continue!" }
val context = config.context!!
if (!isRunning(context)) {
logger.debug("Starting Aeron Media driver in '${context.aeronDirectory()}'")
var threadFactory: NamedThreadFactory? = null
// try to start. If we start/stop too quickly, it's a problem
var count = 10
while (count-- > 0) {
try {
if (threadFactory == null) {
threadFactory = NamedThreadFactory("Thread", ThreadGroup("${type.simpleName}-AeronDriver"), true)
context
.conductorThreadFactory(threadFactory)
.receiverThreadFactory(threadFactory)
.senderThreadFactory(threadFactory)
.sharedNetworkThreadFactory(threadFactory)
.sharedThreadFactory(threadFactory)
}
return MediaDriver.launch(context)
} catch (e: Exception) {
logger.warn(e) { "Unable to start the Aeron Media driver. Retrying $count more times..." }
runBlocking {
delay(context.driverTimeoutMs())
}
}
}
} else {
logger.debug("Not starting Aeron Media driver. It was already running in '${context.aeronDirectory()}'")
}
return null
}
/**
* A safer way to try to close the media driver
*
* NOTE: We must be *super* careful trying to delete directories, because if we have multiple AERON/MEDIA DRIVERS connected to the
* same directory, deleting the directory will cause any other aeron connection to fail! (which makes sense).
*/
internal suspend fun stopDriver(mediaDriver: MediaDriver?, logger: KLogger = KotlinLogging.logger("AeronConfig")) {
if (mediaDriver == null) {
logger.debug { "No driver started for this instance. Not Stopping." }
return
}
val context = mediaDriver.context()
logger.debug("Stopping driver at '${context.aeronDirectory()}'...")
if (!isRunning(context)) {
// not running
logger.debug { "Driver is not running at '${context.aeronDirectory()}' for this context. Not Stopping." }
return
}
try {
mediaDriver.close()
// on close, the publication CAN linger (in case a client goes away, and then comes back)
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
delay(AERON_PUBLICATION_LINGER_TIMEOUT)
// wait for the media driver to actually stop
var count = 10
while (count-- >= 0 && isRunning(context)) {
logger.warn { "Aeron Media driver at '${context.aeronDirectory()}' is still running. Waiting for it to stop. Trying $count more times." }
delay(context.driverTimeoutMs())
}
} catch (e: Exception) {
logger.error("Error closing the media driver at '${context.aeronDirectory()}'", e)
}
(context.sharedThreadFactory() as NamedThreadFactory).group.destroy()
logger.debug { "Closed the media driver at '${context.aeronDirectory()}'" }
}
}

View File

@ -0,0 +1,489 @@
package dorkbox.network.aeron
import dorkbox.network.Configuration
import dorkbox.network.connection.ListenerManager
import dorkbox.util.NamedThreadFactory
import io.aeron.Aeron
import io.aeron.ChannelUriStringBuilder
import io.aeron.Publication
import io.aeron.Subscription
import io.aeron.driver.MediaDriver
import io.aeron.exceptions.DriverTimeoutException
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import mu.KLogger
import mu.KotlinLogging
import org.agrona.concurrent.BackoffIdleStrategy
import java.io.File
/**
* Class for managing the Aeron+Media drivers
*/
class AeronDriver(val config: Configuration,
val type: Class<*> = AeronDriver::class.java,
val logger: KLogger = KotlinLogging.logger("AeronConfig")) {
companion object {
/**
* Identifier for invalid sessions. This must be < RESERVED_SESSION_ID_LOW
*/
internal const val RESERVED_SESSION_ID_INVALID = 0
/**
* The inclusive lower bound of the reserved sessions range. THIS SHOULD NEVER BE <= 0!
*/
internal const val RESERVED_SESSION_ID_LOW = 1
/**
* The inclusive upper bound of the reserved sessions range.
*/
internal const val RESERVED_SESSION_ID_HIGH = Integer.MAX_VALUE
const val UDP_HANDSHAKE_STREAM_ID: Int = 0x1337cafe
const val IPC_HANDSHAKE_STREAM_ID_PUB: Int = 0x1337c0de
const val IPC_HANDSHAKE_STREAM_ID_SUB: Int = 0x1337c0d3
// on close, the publication CAN linger (in case a client goes away, and then comes back)
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
private const val AERON_PUBLICATION_LINGER_TIMEOUT = 5_000L // in MS
private fun create(config: Configuration, logger: KLogger): MediaDriver.Context {
/*
* Linux
* Linux normally requires some settings of sysctl values. One is net.core.rmem_max to allow larger SO_RCVBUF and
* net.core.wmem_max to allow larger SO_SNDBUF values to be set.
*
* Windows
* Windows tends to use SO_SNDBUF values that are too small. It is recommended to use values more like 1MB or so.
*
* Mac/Darwin
*
* Mac tends to use SO_SNDBUF values that are too small. It is recommended to use larger values, like 16KB.
*/
if (config.receiveBufferSize == 0) {
config.receiveBufferSize = io.aeron.driver.Configuration.SOCKET_RCVBUF_LENGTH_DEFAULT
// when {
// OS.isLinux() ->
// OS.isWindows() ->
// OS.isMacOsX() ->
// }
// val rmem_max = dorkbox.network.other.NetUtil.sysctlGetInt("net.core.rmem_max")
// val wmem_max = dorkbox.network.other.NetUtil.sysctlGetInt("net.core.wmem_max")
}
if (config.sendBufferSize == 0) {
config.receiveBufferSize = io.aeron.driver.Configuration.SOCKET_SNDBUF_LENGTH_DEFAULT
// when {
// OS.isLinux() ->
// OS.isWindows() ->
// OS.isMacOsX() ->
// }
// val rmem_max = dorkbox.network.other.NetUtil.sysctlGetInt("net.core.rmem_max")
// val wmem_max = dorkbox.network.other.NetUtil.sysctlGetInt("net.core.wmem_max")
}
/*
* Note: Since Mac OS does not have a built-in support for /dev/shm it is advised to create a RAM disk for the Aeron directory (aeron.dir).
*
* You can create a RAM disk with the following command:
*
* $ diskutil erasevolume HFS+ "DISK_NAME" `hdiutil attach -nomount ram://$((2048 * SIZE_IN_MB))`
*
* where:
*
* DISK_NAME should be replaced with a name of your choice.
* SIZE_IN_MB is the size in megabytes for the disk (e.g. 4096 for a 4GB disk).
*
* For example, the following command creates a RAM disk named DevShm which is 2GB in size:
*
* $ diskutil erasevolume HFS+ "DevShm" `hdiutil attach -nomount ram://$((2048 * 2048))`
*
* After this command is executed the new disk will be mounted under /Volumes/DevShm.
*/
if (config.aeronDirectory == null) {
val baseFileLocation = config.suggestAeronLogLocation(logger)
// val aeronLogDirectory = File(baseFileLocation, "aeron-" + type.simpleName)
val aeronLogDirectory = File(baseFileLocation, "aeron")
config.aeronDirectory = aeronLogDirectory
}
// LOW-LATENCY SETTINGS
// .termBufferSparseFile(false)
// .useWindowsHighResTimer(true)
// .threadingMode(ThreadingMode.DEDICATED)
// .conductorIdleStrategy(BusySpinIdleStrategy.INSTANCE)
// .receiverIdleStrategy(NoOpIdleStrategy.INSTANCE)
// .senderIdleStrategy(NoOpIdleStrategy.INSTANCE);
// setProperty(DISABLE_BOUNDS_CHECKS_PROP_NAME, "true");
// setProperty("aeron.mtu.length", "16384");
// setProperty("aeron.socket.so_sndbuf", "2097152");
// setProperty("aeron.socket.so_rcvbuf", "2097152");
// setProperty("aeron.rcv.initial.window.length", "2097152");
// driver context must happen in the initializer, because we have a Server.isRunning() method that uses the mediaDriverContext (without bind)
val context = MediaDriver.Context()
.publicationReservedSessionIdLow(RESERVED_SESSION_ID_LOW)
.publicationReservedSessionIdHigh(RESERVED_SESSION_ID_HIGH)
.threadingMode(config.threadingMode)
.mtuLength(config.networkMtuSize)
.socketSndbufLength(config.sendBufferSize)
.socketRcvbufLength(config.receiveBufferSize)
context.aeronDirectoryName(config.aeronDirectory!!.absolutePath)
if (context.ipcTermBufferLength() != io.aeron.driver.Configuration.ipcTermBufferLength()) {
// default 64 megs each is HUGE
context.ipcTermBufferLength(8 * 1024 * 1024)
}
if (context.publicationTermBufferLength() != io.aeron.driver.Configuration.termBufferLength()) {
// default 16 megs each is HUGE (we run out of space in production w/ lots of clients)
context.publicationTermBufferLength(2 * 1024 * 1024)
}
// we DO NOT want to abort the JVM if there are errors.
context.errorHandler { error ->
logger.error("Error in Aeron", error)
}
val aeronDir = File(context.aeronDirectoryName()).absoluteFile
context.aeronDirectoryName(aeronDir.path)
return context
}
/**
* Creates the Aeron Media Driver context
*
* @throws IllegalStateException if the configuration has already been used to create a context
* @throws IllegalArgumentException if the aeron media driver directory cannot be setup
*/
fun createContext(config: Configuration, logger: KLogger = KotlinLogging.logger("AeronConfig")) {
if (config.context != null) {
logger.warn { "Unable to recreate a context for a configuration that has already been created" }
return
}
var context = create(config, logger)
// this happens EXACTLY once. Must be BEFORE the "isRunning" check!
context.concludeAeronDirectory()
// will setup the aeron directory or throw IllegalArgumentException if it cannot be configured
val aeronDir = context.aeronDirectory()
var isRunning = isRunning(context)
// this is incompatible with IPC, and will not be set if IPC is enabled
if (config.aeronDirectoryForceUnique && isRunning) {
val savedParent = aeronDir.parentFile
var retry = 0
val retryMax = 100
while (config.aeronDirectoryForceUnique && isRunning) {
if (retry++ > retryMax) {
throw IllegalArgumentException("Unable to force unique aeron Directory. Tried $retryMax times and all tries were in use.")
}
val randomNum = (1..retryMax).shuffled().first()
val newDir = savedParent.resolve("${aeronDir.name}_$randomNum")
context = create(config, logger)
context.aeronDirectoryName(newDir.path)
// this happens EXACTLY once. Must be BEFORE the "isRunning" check!
context.concludeAeronDirectory()
isRunning = isRunning(context)
}
if (!isRunning) {
// NOTE: We must be *super* careful trying to delete directories, because if we have multiple AERON/MEDIA DRIVERS connected to the
// same directory, deleting the directory will cause any other aeron connection to fail! (which makes sense).
// since we are forcing a unique directory, we should ALSO delete it when we are done!
context.dirDeleteOnShutdown()
}
}
logger.info { "Aeron directory: '${context.aeronDirectory()}'" }
// once we do this, we cannot change any of the config values!
config.context = context
}
/**
* Checks to see if an endpoint (using the specified configuration) is running.
*
* @return true if the media driver is active and running
*/
fun isRunning(context: MediaDriver.Context, timeout: Long = context.driverTimeoutMs()): Boolean {
// if the media driver is running, it will be a quick connection. Usually 100ms or so
return context.isDriverActive(context.driverTimeoutMs()) { }
}
/**
* validates and creates the configuration. This can only happen once!
*/
fun validateConfig(config: Configuration, logger: KLogger = KotlinLogging.logger("AeronConfig")) {
config.validate()
if (config.context == null) {
createContext(config, logger)
}
require(config.context != null) { "Configuration context cannot be properly created. Unable to continue!" }
}
}
private var aeron: Aeron? = null
@Volatile
private var mediaDriver: MediaDriver? = null
// the context is validated before the AeronDriver object is created
private val threadFactory = NamedThreadFactory("Thread", ThreadGroup("${type.simpleName}-AeronDriver"), true)
private val mediaDriverContext = config.context!!
init {
mediaDriverContext
.conductorThreadFactory(threadFactory)
.receiverThreadFactory(threadFactory)
.senderThreadFactory(threadFactory)
.sharedNetworkThreadFactory(threadFactory)
.sharedThreadFactory(threadFactory)
}
private fun setupAeron(): Aeron.Context {
val aeronDriverContext = Aeron.Context()
aeronDriverContext
.aeronDirectoryName(mediaDriverContext.aeronDirectory().path)
.concludeAeronDirectory()
aeronDriverContext
.threadFactory(threadFactory)
.idleStrategy(BackoffIdleStrategy())
// we DO NOT want to abort the JVM if there are errors.
// this replaces the default handler with one that doesn't abort the JVM
aeronDriverContext.errorHandler { error ->
ListenerManager.cleanStackTrace(error)
logger.error("Error in Aeron", error)
}
return aeronDriverContext
}
private fun startDriver(): Boolean {
if (mediaDriver == null) {
// only start if we didn't already start... There will be several checks.
if (!isRunning(mediaDriverContext)) {
logger.debug("Starting Aeron Media driver in '${mediaDriverContext.aeronDirectory()}'")
// try to start. If we start/stop too quickly, it's a problem
var count = 10
while (count-- > 0) {
try {
mediaDriver = MediaDriver.launch(mediaDriverContext)
return true
} catch (e: Exception) {
logger.warn(e) { "Unable to start the Aeron Media driver. Retrying $count more times..." }
runBlocking {
delay(mediaDriverContext.driverTimeoutMs())
}
}
}
} else {
logger.debug("Not starting Aeron Media driver. It was already running in '${mediaDriverContext.aeronDirectory()}'")
}
}
return false
}
private fun startAeron(didStartDriver: Boolean) {
// the media driver MIGHT already be started in a different process! We still ALWAYS want to connect to
// aeron (which connects to the other media driver process), especially if we haven't already connected to
// it (or if there was an error connecting because a different media driver was shutting down)
if (didStartDriver || aeron == null) {
aeron?.close()
// this might succeed if we can connect to the media driver
aeron = Aeron.connect(setupAeron())
}
}
/**
* If the driver is not already running, this will start the driver
*
* @throws Exception if there is a problem starting the media driver
*/
fun start() {
val didStartDriver = startDriver()
startAeron(didStartDriver)
}
/**
* @return the aeron media driver log file for a specific publication. This should be removed when a publication is closed (but is not always!)
*/
fun getMediaDriverPublicationFile(publicationRegId: Long): File {
return mediaDriverContext.aeronDirectory().resolve("publications").resolve("${publicationRegId}.logbuffer")
}
suspend fun addPublicationWithRetry(publicationUri: ChannelUriStringBuilder, streamId: Int): Publication {
val uri = publicationUri.build()
// If we start/stop too quickly, we might have the address already in use! Retry a few times.
var count = 10
var exception: Exception? = null
while (count-- > 0) {
try {
// if aeron is null, an exception is thrown
return aeron!!.addPublication(uri, streamId)
} catch (e: Exception) {
// NOTE: this error will be logged in the `aeronDriverContext` logger
exception = e
logger.warn { "Unable to add a publication to Aeron. Retrying $count more times..." }
if (e is DriverTimeoutException) {
delay(mediaDriverContext.driverTimeoutMs())
}
// reasons we cannot add a pub/sub to aeron
// 1) the driver was closed
// 2) aeron was unable to connect to the driver
// 3) the address already in use
// configuring pub/sub to aeron is LINEAR -- and it happens in 2 places.
// 1) starting up the client/server
// 2) creating a new client-server connection pair (the media driver won't be "dead" at this poitn)
// try to start/restart aeron
try {
start()
} catch (e2: Exception) {
e2.printStackTrace()
}
}
}
throw exception!!
}
suspend fun addSubscriptionWithRetry(subscriptionUri: ChannelUriStringBuilder, streamId: Int): Subscription {
val uri = subscriptionUri.build()
// If we start/stop too quickly, we might have the address already in use! Retry a few times.
var count = 10
var exception: Exception? = null
while (count-- > 0) {
try {
val aeron = aeron
if (aeron != null) {
return aeron.addSubscription(uri, streamId)
}
} catch (e: Exception) {
// NOTE: this error will be logged in the `aeronDriverContext` logger
exception = e
logger.warn { "Unable to add a publication to Aeron. Retrying $count more times..." }
if (e is DriverTimeoutException) {
delay(mediaDriverContext.driverTimeoutMs())
}
// reasons we cannot add a pub/sub to aeron
// 1) the driver was closed
// 2) aeron was unable to connect to the driver
// 3) the address already in use
// configuring pub/sub to aeron is LINEAR -- and it happens in 2 places.
// 1) starting up the client/server
// 2) creating a new client-server connection pair (the media driver won't be "dead" at this poitn)
// try to start/restart aeron
try {
start()
} catch (e2: Exception) {
e2.printStackTrace()
}
}
}
throw exception!!
}
/**
* Checks to see if an endpoint (using the specified configuration) is running.
*
* @return true if the media driver is active and running
*/
fun isRunning(timeout: Long = mediaDriverContext.driverTimeoutMs()): Boolean {
// if the media driver is running, it will be a quick connection. Usually 100ms or so
return mediaDriverContext.isDriverActive(timeout) { }
}
/**
* A safer way to try to close the media driver
*
* NOTE: We must be *super* careful trying to delete directories, because if we have multiple AERON/MEDIA DRIVERS connected to the
* same directory, deleting the directory will cause any other aeron connection to fail! (which makes sense).
*/
suspend fun close() {
try {
aeron?.close()
} catch (e: Exception) {
logger.error("Error stopping aeron.", e)
}
val mediaDriver = mediaDriver
if (mediaDriver == null) {
logger.debug { "No driver started for this instance. Not Stopping." }
return
}
logger.debug("Stopping driver at '${mediaDriverContext.aeronDirectory()}'...")
if (!isRunning(mediaDriverContext)) {
// not running
logger.debug { "Driver is not running at '${mediaDriverContext.aeronDirectory()}' for this context. Not Stopping." }
return
}
try {
mediaDriver.close()
// on close, the publication CAN linger (in case a client goes away, and then comes back)
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
delay(AERON_PUBLICATION_LINGER_TIMEOUT)
// wait for the media driver to actually stop
var count = 10
while (count-- >= 0 && isRunning(mediaDriverContext)) {
logger.warn { "Aeron Media driver at '${mediaDriverContext.aeronDirectory()}' is still running. Waiting for it to stop. Trying $count more times." }
delay(mediaDriverContext.driverTimeoutMs())
}
} catch (e: Exception) {
logger.error("Error closing the media driver at '${mediaDriverContext.aeronDirectory()}'", e)
}
// Destroys this thread group and all of its subgroups.
// This thread group must be empty, indicating that all threads that had been in this thread group have since stopped.
threadFactory.group.destroy()
logger.debug { "Closed the media driver at '${mediaDriverContext.aeronDirectory()}'" }
}
}

View File

@ -17,7 +17,6 @@
package dorkbox.network.aeron package dorkbox.network.aeron
import dorkbox.network.exceptions.ClientTimedOutException import dorkbox.network.exceptions.ClientTimedOutException
import io.aeron.Aeron
import io.aeron.ChannelUriStringBuilder import io.aeron.ChannelUriStringBuilder
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import mu.KLogger import mu.KLogger
@ -26,7 +25,7 @@ import mu.KLogger
* For a client, the streamId specified here MUST be manually flipped because they are in the perspective of the SERVER * For a client, the streamId specified here MUST be manually flipped because they are in the perspective of the SERVER
* NOTE: IPC connection will ALWAYS have a timeout of 1 second to connect. This is IPC, it should connect fast * NOTE: IPC connection will ALWAYS have a timeout of 1 second to connect. This is IPC, it should connect fast
*/ */
internal class IpcMediaDriverConnection(streamId: Int, internal open class IpcMediaDriverConnection(streamId: Int,
val streamIdSubscription: Int, val streamIdSubscription: Int,
sessionId: Int, sessionId: Int,
) : ) :
@ -36,7 +35,7 @@ internal class IpcMediaDriverConnection(streamId: Int,
private fun uri(): ChannelUriStringBuilder { private fun uri(): ChannelUriStringBuilder {
val builder = ChannelUriStringBuilder().media("ipc") val builder = ChannelUriStringBuilder().media("ipc")
if (sessionId != AeronConfig.RESERVED_SESSION_ID_INVALID) { if (sessionId != AeronDriver.RESERVED_SESSION_ID_INVALID) {
builder.sessionId(sessionId) builder.sessionId(sessionId)
} }
@ -48,7 +47,7 @@ internal class IpcMediaDriverConnection(streamId: Int,
* *
* @throws ClientTimedOutException if we cannot connect to the server in the designated time * @throws ClientTimedOutException if we cannot connect to the server in the designated time
*/ */
override suspend fun buildClient(aeron: Aeron, logger: KLogger) { override suspend fun buildClient(aeronDriver: AeronDriver, logger: KLogger) {
// Create a publication at the given address and port, using the given stream ID. // Create a publication at the given address and port, using the given stream ID.
// Note: The Aeron.addPublication method will block until the Media Driver acknowledges the request or a timeout occurs. // Note: The Aeron.addPublication method will block until the Media Driver acknowledges the request or a timeout occurs.
val publicationUri = uri() val publicationUri = uri()
@ -66,8 +65,8 @@ internal class IpcMediaDriverConnection(streamId: Int,
// publication of any state to other threads and not be long running or re-entrant with the client. // publication of any state to other threads and not be long running or re-entrant with the client.
// If we start/stop too quickly, we might have the aeron connectivity issues! Retry a few times. // If we start/stop too quickly, we might have the aeron connectivity issues! Retry a few times.
val publication = addPublicationWithRetry(aeron, publicationUri.build(), streamId, logger) val publication = aeronDriver.addPublicationWithRetry(publicationUri, streamId)
val subscription = addSubscriptionWithRetry(aeron, subscriptionUri.build(), streamIdSubscription, logger) val subscription = aeronDriver.addSubscriptionWithRetry(subscriptionUri, streamIdSubscription)
var success = false var success = false
@ -117,7 +116,7 @@ internal class IpcMediaDriverConnection(streamId: Int,
* *
* serverAddress is ignored for IPC * serverAddress is ignored for IPC
*/ */
override suspend fun buildServer(aeron: Aeron, logger: KLogger) { override suspend fun buildServer(aeronDriver: AeronDriver, logger: KLogger, pairConnection: Boolean) {
// Create a publication with a control port (for dynamic MDC) at the given address and port, using the given stream ID. // Create a publication with a control port (for dynamic MDC) at the given address and port, using the given stream ID.
// Note: The Aeron.addPublication method will block until the Media Driver acknowledges the request or a timeout occurs. // Note: The Aeron.addPublication method will block until the Media Driver acknowledges the request or a timeout occurs.
val publicationUri = uri() val publicationUri = uri()
@ -138,12 +137,12 @@ internal class IpcMediaDriverConnection(streamId: Int,
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param) // AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
// If we start/stop too quickly, we might have the aeron connectivity issues! Retry a few times. // If we start/stop too quickly, we might have the aeron connectivity issues! Retry a few times.
publication = addPublicationWithRetry(aeron, publicationUri.build(), streamId, logger) publication = aeronDriver.addPublicationWithRetry(publicationUri, streamId)
subscription = addSubscriptionWithRetry(aeron, subscriptionUri.build(), streamIdSubscription, logger) subscription = aeronDriver.addSubscriptionWithRetry(subscriptionUri, streamIdSubscription)
} }
override fun clientInfo() : String { override fun clientInfo() : String {
return if (sessionId != AeronConfig.RESERVED_SESSION_ID_INVALID) { return if (sessionId != AeronDriver.RESERVED_SESSION_ID_INVALID) {
"[$sessionId] IPC connection established to [$streamIdSubscription|$streamId]" "[$sessionId] IPC connection established to [$streamIdSubscription|$streamId]"
} else { } else {
"Connecting handshake to IPC [$streamIdSubscription|$streamId]" "Connecting handshake to IPC [$streamIdSubscription|$streamId]"
@ -151,7 +150,7 @@ internal class IpcMediaDriverConnection(streamId: Int,
} }
override fun serverInfo() : String { override fun serverInfo() : String {
return if (sessionId != AeronConfig.RESERVED_SESSION_ID_INVALID) { return if (sessionId != AeronDriver.RESERVED_SESSION_ID_INVALID) {
"[$sessionId] IPC listening on [$streamIdSubscription|$streamId] " "[$sessionId] IPC listening on [$streamIdSubscription|$streamId] "
} else { } else {
"Listening handshake on IPC [$streamIdSubscription|$streamId]" "Listening handshake on IPC [$streamIdSubscription|$streamId]"

View File

@ -18,57 +18,22 @@
package dorkbox.network.aeron package dorkbox.network.aeron
import dorkbox.network.exceptions.ClientTimedOutException import dorkbox.network.exceptions.ClientTimedOutException
import io.aeron.Aeron
import io.aeron.Publication import io.aeron.Publication
import io.aeron.Subscription import io.aeron.Subscription
import kotlinx.coroutines.delay
import mu.KLogger import mu.KLogger
abstract class MediaDriverConnection(val publicationPort: Int, val subscriptionPort: Int, abstract class MediaDriverConnection(
val streamId: Int, val sessionId: Int, val publicationPort: Int, val subscriptionPort: Int,
val connectionTimeoutMS: Long, val isReliable: Boolean) : AutoCloseable { val streamId: Int, val sessionId: Int,
val connectionTimeoutMS: Long, val isReliable: Boolean) : AutoCloseable {
lateinit var subscription: Subscription lateinit var subscription: Subscription
lateinit var publication: Publication lateinit var publication: Publication
suspend fun addSubscriptionWithRetry(aeron: Aeron, uri: String, streamId: Int, logger: KLogger): Subscription {
// If we start/stop too quickly, we might have the address already in use! Retry a few times.
var count = 10
var exception: Exception? = null
while (count-- > 0) {
try {
return aeron.addSubscription(uri, streamId)
} catch (e: Exception) {
exception = e
logger.warn { "Unable to add a publication to Aeron. Retrying $count more times..." }
delay(5000)
}
}
throw exception!!
}
suspend fun addPublicationWithRetry(aeron: Aeron, uri: String, streamId: Int, logger: KLogger): Publication {
// If we start/stop too quickly, we might have the address already in use! Retry a few times.
var count = 10
var exception: Exception? = null
while (count-- > 0) {
try {
return aeron.addPublication(uri, streamId)
} catch (e: Exception) {
exception = e
logger.warn { "Unable to add a publication to Aeron. Retrying $count more times..." }
delay(5_000)
}
}
throw exception!!
}
@Throws(ClientTimedOutException::class) @Throws(ClientTimedOutException::class)
abstract suspend fun buildClient(aeron: Aeron, logger: KLogger) abstract suspend fun buildClient(aeronDriver: AeronDriver, logger: KLogger)
abstract suspend fun buildServer(aeron: Aeron, logger: KLogger) abstract suspend fun buildServer(aeronDriver: AeronDriver, logger: KLogger, pairConnection: Boolean = false)
abstract fun clientInfo() : String abstract fun clientInfo() : String
abstract fun serverInfo() : String abstract fun serverInfo() : String

View File

@ -19,7 +19,6 @@ package dorkbox.network.aeron
import dorkbox.netUtil.IP import dorkbox.netUtil.IP
import dorkbox.network.exceptions.ClientException import dorkbox.network.exceptions.ClientException
import dorkbox.network.exceptions.ClientTimedOutException import dorkbox.network.exceptions.ClientTimedOutException
import io.aeron.Aeron
import io.aeron.ChannelUriStringBuilder import io.aeron.ChannelUriStringBuilder
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import mu.KLogger import mu.KLogger
@ -62,7 +61,7 @@ internal class UdpMediaDriverClientConnection(val address: InetAddress,
private fun uri(): ChannelUriStringBuilder { private fun uri(): ChannelUriStringBuilder {
val builder = ChannelUriStringBuilder().reliable(isReliable).media("udp") val builder = ChannelUriStringBuilder().reliable(isReliable).media("udp")
if (sessionId != AeronConfig.RESERVED_SESSION_ID_INVALID) { if (sessionId != AeronDriver.RESERVED_SESSION_ID_INVALID) {
builder.sessionId(sessionId) builder.sessionId(sessionId)
} }
@ -70,7 +69,7 @@ internal class UdpMediaDriverClientConnection(val address: InetAddress,
} }
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
override suspend fun buildClient(aeron: Aeron, logger: KLogger) { override suspend fun buildClient(aeronDriver: AeronDriver, logger: KLogger) {
val aeronAddressString = aeronConnectionString(address) val aeronAddressString = aeronConnectionString(address)
// Create a publication at the given address and port, using the given stream ID. // Create a publication at the given address and port, using the given stream ID.
@ -99,8 +98,8 @@ internal class UdpMediaDriverClientConnection(val address: InetAddress,
// publication of any state to other threads and not be long running or re-entrant with the client. // publication of any state to other threads and not be long running or re-entrant with the client.
// on close, the publication CAN linger (in case a client goes away, and then comes back) // on close, the publication CAN linger (in case a client goes away, and then comes back)
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param) // AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
val publication = addPublicationWithRetry(aeron, publicationUri.build(), streamId, logger) val publication = aeronDriver.addPublicationWithRetry(publicationUri, streamId)
val subscription = addSubscriptionWithRetry(aeron, subscriptionUri.build(), streamId, logger) val subscription = aeronDriver.addSubscriptionWithRetry(subscriptionUri, streamId)
var success = false var success = false
@ -150,14 +149,14 @@ internal class UdpMediaDriverClientConnection(val address: InetAddress,
override fun clientInfo(): String { override fun clientInfo(): String {
address address
return if (sessionId != AeronConfig.RESERVED_SESSION_ID_INVALID) { return if (sessionId != AeronDriver.RESERVED_SESSION_ID_INVALID) {
"Connecting to ${IP.toString(address)} [$subscriptionPort|$publicationPort] [$streamId|$sessionId] (reliable:$isReliable)" "Connecting to ${IP.toString(address)} [$subscriptionPort|$publicationPort] [$streamId|$sessionId] (reliable:$isReliable)"
} else { } else {
"Connecting handshake to ${IP.toString(address)} [$subscriptionPort|$publicationPort] [$streamId|*] (reliable:$isReliable)" "Connecting handshake to ${IP.toString(address)} [$subscriptionPort|$publicationPort] [$streamId|*] (reliable:$isReliable)"
} }
} }
override suspend fun buildServer(aeron: Aeron, logger: KLogger) { override suspend fun buildServer(aeronDriver: AeronDriver, logger: KLogger, pairConnection: Boolean) {
throw ClientException("Server info not implemented in Client MDC") throw ClientException("Server info not implemented in Client MDC")
} }
override fun serverInfo(): String { override fun serverInfo(): String {

View File

@ -20,7 +20,6 @@ import dorkbox.netUtil.IP
import dorkbox.netUtil.IPv4 import dorkbox.netUtil.IPv4
import dorkbox.netUtil.IPv6 import dorkbox.netUtil.IPv6
import dorkbox.network.exceptions.ServerException import dorkbox.network.exceptions.ServerException
import io.aeron.Aeron
import io.aeron.ChannelUriStringBuilder import io.aeron.ChannelUriStringBuilder
import mu.KLogger import mu.KLogger
import java.net.Inet4Address import java.net.Inet4Address
@ -41,7 +40,7 @@ internal open class UdpMediaDriverServerConnection(val listenAddress: InetAddres
var success: Boolean = false var success: Boolean = false
private fun aeronConnectionString(ipAddress: InetAddress): String { protected fun aeronConnectionString(ipAddress: InetAddress): String {
return if (ipAddress is Inet4Address) { return if (ipAddress is Inet4Address) {
ipAddress.hostAddress ipAddress.hostAddress
} else { } else {
@ -55,9 +54,9 @@ internal open class UdpMediaDriverServerConnection(val listenAddress: InetAddres
} }
} }
private fun uri(): ChannelUriStringBuilder { protected fun uri(): ChannelUriStringBuilder {
val builder = ChannelUriStringBuilder().reliable(isReliable).media("udp") val builder = ChannelUriStringBuilder().reliable(isReliable).media("udp")
if (sessionId != AeronConfig.RESERVED_SESSION_ID_INVALID) { if (sessionId != AeronDriver.RESERVED_SESSION_ID_INVALID) {
builder.sessionId(sessionId) builder.sessionId(sessionId)
} }
@ -65,11 +64,11 @@ internal open class UdpMediaDriverServerConnection(val listenAddress: InetAddres
} }
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
override suspend fun buildClient(aeron: Aeron, logger: KLogger) { override suspend fun buildClient(aeronDriver: AeronDriver, logger: KLogger) {
throw ServerException("Client info not implemented in Server MDC") throw ServerException("Client info not implemented in Server MDC")
} }
override suspend fun buildServer(aeron: Aeron, logger: KLogger) { override suspend fun buildServer(aeronDriver: AeronDriver, logger: KLogger, pairConnection: Boolean) {
val connectionString = aeronConnectionString(listenAddress) val connectionString = aeronConnectionString(listenAddress)
// Create a publication with a control port (for dynamic MDC) at the given address and port, using the given stream ID. // Create a publication with a control port (for dynamic MDC) at the given address and port, using the given stream ID.
@ -100,8 +99,8 @@ internal open class UdpMediaDriverServerConnection(val listenAddress: InetAddres
// AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param) // AERON_PUBLICATION_LINGER_TIMEOUT, 5s by default (this can also be set as a URI param)
// If we start/stop too quickly, we might have the address already in use! Retry a few times. // If we start/stop too quickly, we might have the address already in use! Retry a few times.
publication = addPublicationWithRetry(aeron, publicationUri.build(), streamId, logger) publication = aeronDriver.addPublicationWithRetry(publicationUri, streamId)
subscription = addSubscriptionWithRetry(aeron, subscriptionUri.build(), streamId, logger) subscription = aeronDriver.addSubscriptionWithRetry(subscriptionUri, streamId)
} }
override fun clientInfo(): String { override fun clientInfo(): String {
@ -119,7 +118,7 @@ internal open class UdpMediaDriverServerConnection(val listenAddress: InetAddres
IP.toString(listenAddress) IP.toString(listenAddress)
} }
return if (sessionId != AeronConfig.RESERVED_SESSION_ID_INVALID) { return if (sessionId != AeronDriver.RESERVED_SESSION_ID_INVALID) {
"Listening on $address [$subscriptionPort|$publicationPort] [$streamId|$sessionId] (reliable:$isReliable)" "Listening on $address [$subscriptionPort|$publicationPort] [$streamId|$sessionId] (reliable:$isReliable)"
} else { } else {
"Listening handshake on $address [$subscriptionPort|$publicationPort] [$streamId|*] (reliable:$isReliable)" "Listening handshake on $address [$subscriptionPort|$publicationPort] [$streamId|*] (reliable:$isReliable)"

View File

@ -416,7 +416,7 @@ open class Connection(connectionParameters: ConnectionParams<*>) {
} }
// on close, we want to make sure this file is DELETED! // on close, we want to make sure this file is DELETED!
val logFile = endPoint.getMediaDriverPublicationFile(publication.registrationId()) val logFile = endPoint.aeronDriver.getMediaDriverPublicationFile(publication.registrationId())
publication.close() publication.close()
closeTimeoutTime = System.currentTimeMillis() + timeOut closeTimeoutTime = System.currentTimeMillis() + timeOut

View File

@ -19,7 +19,7 @@ import dorkbox.network.Client
import dorkbox.network.Configuration import dorkbox.network.Configuration
import dorkbox.network.Server import dorkbox.network.Server
import dorkbox.network.ServerConfiguration import dorkbox.network.ServerConfiguration
import dorkbox.network.aeron.AeronConfig import dorkbox.network.aeron.AeronDriver
import dorkbox.network.aeron.CoroutineIdleStrategy import dorkbox.network.aeron.CoroutineIdleStrategy
import dorkbox.network.coroutines.SuspendWaiter import dorkbox.network.coroutines.SuspendWaiter
import dorkbox.network.exceptions.MessageNotRegisteredException import dorkbox.network.exceptions.MessageNotRegisteredException
@ -33,9 +33,7 @@ import dorkbox.network.rmi.messages.RmiMessage
import dorkbox.network.serialization.KryoExtra import dorkbox.network.serialization.KryoExtra
import dorkbox.network.serialization.Serialization import dorkbox.network.serialization.Serialization
import dorkbox.network.storage.SettingsStore import dorkbox.network.storage.SettingsStore
import dorkbox.util.NamedThreadFactory
import dorkbox.util.exceptions.SecurityException import dorkbox.util.exceptions.SecurityException
import io.aeron.Aeron
import io.aeron.Publication import io.aeron.Publication
import io.aeron.driver.MediaDriver import io.aeron.driver.MediaDriver
import io.aeron.logbuffer.Header import io.aeron.logbuffer.Header
@ -47,8 +45,6 @@ import kotlinx.coroutines.runBlocking
import mu.KLogger import mu.KLogger
import mu.KotlinLogging import mu.KotlinLogging
import org.agrona.DirectBuffer import org.agrona.DirectBuffer
import org.agrona.concurrent.BackoffIdleStrategy
import java.io.File
// If TCP and UDP both fill the pipe, THERE WILL BE FRAGMENTATION and dropped UDP packets! // If TCP and UDP both fill the pipe, THERE WILL BE FRAGMENTATION and dropped UDP packets!
@ -77,9 +73,7 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
internal val listenerManager = ListenerManager<CONNECTION>() internal val listenerManager = ListenerManager<CONNECTION>()
internal val connections = ConnectionManager<CONNECTION>() internal val connections = ConnectionManager<CONNECTION>()
internal val aeron: Aeron internal val aeronDriver: AeronDriver
private var mediaDriver: MediaDriver? = null
internal val mediaDriverContext: MediaDriver.Context
/** /**
* Returns the serialization wrapper if there is an object type that needs to be added outside of the basic types. * Returns the serialization wrapper if there is an object type that needs to be added outside of the basic types.
@ -134,42 +128,13 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
// Only starts the media driver if we are NOT already running! // Only starts the media driver if we are NOT already running!
try { try {
mediaDriver = AeronConfig.startDriver(config, type, logger) AeronDriver.validateConfig(config, logger)
aeronDriver = AeronDriver(config, type, logger)
aeronDriver.start()
} catch (e: Exception) { } catch (e: Exception) {
listenerManager.notifyError(e) listenerManager.notifyError(e)
throw e throw e
} }
require(config.context != null) { "Configuration context cannot be properly created. Unable to continue!" }
mediaDriverContext = config.context!!
val aeronContext = Aeron.Context()
aeronContext
.aeronDirectoryName(mediaDriverContext.aeronDirectory().path)
.concludeAeronDirectory()
val threadFactory = NamedThreadFactory("Thread", ThreadGroup("${type.simpleName}-AeronClient"),true)
aeronContext
.threadFactory(threadFactory)
.idleStrategy(BackoffIdleStrategy())
// we DO NOT want to abort the JVM if there are errors.
aeronContext.errorHandler { error ->
logger.error("Error in Aeron", error)
}
try {
aeron = Aeron.connect(aeronContext)
} catch (e: Exception) {
try {
mediaDriver?.close()
} catch (secondaryException: Exception) {
e.addSuppressed(secondaryException)
}
listenerManager.notifyError(e)
throw e
}
} }
/** /**
@ -180,17 +145,9 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
} }
internal fun initEndpointState(): Aeron { internal fun initEndpointState() {
shutdown.getAndSet(false) shutdown.getAndSet(false)
shutdownWaiter = SuspendWaiter() shutdownWaiter = SuspendWaiter()
return aeron
}
/**
* @return the aeron media driver log file for a specific publication. This should be removed when a publication is closed (but is not always!)
*/
internal fun getMediaDriverPublicationFile(publicationRegId: Long): File {
return mediaDriverContext.aeronDirectory().resolve("publications").resolve("${publicationRegId}.logbuffer")
} }
abstract fun newException(message: String, cause: Throwable? = null): Throwable abstract fun newException(message: String, cause: Throwable? = null): Throwable
@ -637,8 +594,7 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
* @return true if the media driver is active and running * @return true if the media driver is active and running
*/ */
fun isRunning(): Boolean { fun isRunning(): Boolean {
// if the media driver is running, it will be a quick connection. Usually 100ms or so return aeronDriver.isRunning()
return mediaDriverContext.isDriverActive(1_000) { }
} }
/** /**
@ -655,14 +611,9 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
if (shutdown.compareAndSet(expect = false, update = true)) { if (shutdown.compareAndSet(expect = false, update = true)) {
logger.info { "Shutting down..." } logger.info { "Shutting down..." }
try {
aeron.close()
} catch (e: Exception) {
logger.error("Error stopping aeron.", e)
}
runBlocking { runBlocking {
AeronConfig.stopDriver(mediaDriver, logger) aeronDriver.close()
connections.forEach { connections.forEach {
it.close() it.close()
} }
@ -677,8 +628,6 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
close0() close0()
(aeron.context().threadFactory() as NamedThreadFactory).group.destroy()
// if we are waiting for shutdown, cancel the waiting thread (since we have shutdown now) // if we are waiting for shutdown, cancel the waiting thread (since we have shutdown now)
shutdownWaiter.cancel() shutdownWaiter.cancel()
} }

View File

@ -21,7 +21,7 @@ import com.github.benmanes.caffeine.cache.RemovalCause
import com.github.benmanes.caffeine.cache.RemovalListener import com.github.benmanes.caffeine.cache.RemovalListener
import dorkbox.network.Server import dorkbox.network.Server
import dorkbox.network.ServerConfiguration import dorkbox.network.ServerConfiguration
import dorkbox.network.aeron.AeronConfig import dorkbox.network.aeron.AeronDriver
import dorkbox.network.aeron.IpcMediaDriverConnection import dorkbox.network.aeron.IpcMediaDriverConnection
import dorkbox.network.aeron.UdpMediaDriverPairedConnection import dorkbox.network.aeron.UdpMediaDriverPairedConnection
import dorkbox.network.connection.Connection import dorkbox.network.connection.Connection
@ -29,7 +29,6 @@ import dorkbox.network.connection.ConnectionParams
import dorkbox.network.connection.ListenerManager import dorkbox.network.connection.ListenerManager
import dorkbox.network.connection.PublicKeyValidationState import dorkbox.network.connection.PublicKeyValidationState
import dorkbox.network.exceptions.* import dorkbox.network.exceptions.*
import io.aeron.Aeron
import io.aeron.Publication import io.aeron.Publication
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -72,7 +71,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
private val connectionsPerIpCounts = ConnectionCounts() private val connectionsPerIpCounts = ConnectionCounts()
// guarantee that session/stream ID's will ALWAYS be unique! (there can NEVER be a collision!) // guarantee that session/stream ID's will ALWAYS be unique! (there can NEVER be a collision!)
private val sessionIdAllocator = RandomIdAllocator(AeronConfig.RESERVED_SESSION_ID_LOW, AeronConfig.RESERVED_SESSION_ID_HIGH) private val sessionIdAllocator = RandomIdAllocator(AeronDriver.RESERVED_SESSION_ID_LOW, AeronDriver.RESERVED_SESSION_ID_HIGH)
private val streamIdAllocator = RandomIdAllocator(1, Integer.MAX_VALUE) private val streamIdAllocator = RandomIdAllocator(1, Integer.MAX_VALUE)
@ -201,7 +200,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
handshakePublication: Publication, handshakePublication: Publication,
sessionId: Int, sessionId: Int,
message: HandshakeMessage, message: HandshakeMessage,
aeron: Aeron) { aeronDriver: AeronDriver) {
val connectionString = "IPC" val connectionString = "IPC"
@ -278,7 +277,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
// we have to construct how the connection will communicate! // we have to construct how the connection will communicate!
runBlocking { runBlocking {
clientConnection.buildServer(aeron, logger) clientConnection.buildServer(aeronDriver, logger, true)
} }
logger.info { logger.info {
@ -345,7 +344,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
clientAddressString: String, clientAddressString: String,
clientAddress: InetAddress, clientAddress: InetAddress,
message: HandshakeMessage, message: HandshakeMessage,
aeron: Aeron, aeronDriver: AeronDriver,
isIpv6Wildcard: Boolean) { isIpv6Wildcard: Boolean) {
if (!validateMessageTypeAndDoPending(server, server.actionDispatch, handshakePublication, message, sessionId, clientAddressString)) { if (!validateMessageTypeAndDoPending(server, server.actionDispatch, handshakePublication, message, sessionId, clientAddressString)) {
@ -444,7 +443,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
// we have to construct how the connection will communicate! // we have to construct how the connection will communicate!
runBlocking { runBlocking {
clientConnection.buildServer(aeron, logger) clientConnection.buildServer(aeronDriver, logger, true)
} }
logger.info { logger.info {

View File

@ -46,7 +46,12 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
@ -92,7 +97,12 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
@ -137,7 +147,12 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
@ -182,7 +197,12 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
@ -220,7 +240,12 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
@ -253,20 +278,18 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
waitForThreads() waitForThreads()
} }
@Test @Test
fun acceptAllCustomServer() { fun acceptAllCustomServer() {
val serverConnectSuccess = atomic(false) val serverConnectSuccess = atomic(false)
@ -303,7 +326,12 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
@ -350,7 +378,12 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
@ -390,11 +423,16 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK, Long.MAX_VALUE) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
waitForThreads(Long.MAX_VALUE) waitForThreads()
} }
@Test(expected = ClientException::class) @Test(expected = ClientException::class)
@ -425,19 +463,15 @@ class ConnectionFilterTest : BaseTest() {
} }
runBlocking { runBlocking {
client.connect(LOOPBACK) try {
client.connect(LOOPBACK)
} catch (e: Exception) {
stopEndPoints()
throw e
}
} }
} }
waitForThreads() waitForThreads()
} }
} }

View File

@ -2,7 +2,7 @@ package dorkboxTest.network
import dorkbox.network.Client import dorkbox.network.Client
import dorkbox.network.Server import dorkbox.network.Server
import dorkbox.network.aeron.AeronConfig import dorkbox.network.aeron.AeronDriver
import dorkbox.network.connection.Connection import dorkbox.network.connection.Connection
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -72,8 +72,9 @@ class DisconnectReconnectTest : BaseTest() {
@Test @Test
fun manualMediaDriverAndReconnectClient() { fun manualMediaDriverAndReconnectClient() {
val serverConfiguration = serverConfig() val serverConfiguration = serverConfig()
val mediaDriver = runBlocking { val aeronDriver = runBlocking {
AeronConfig.startDriver(serverConfiguration) AeronDriver.validateConfig(serverConfiguration)
AeronDriver(serverConfiguration)
} }
run { run {
@ -124,7 +125,7 @@ class DisconnectReconnectTest : BaseTest() {
waitForThreads() waitForThreads()
runBlocking { runBlocking {
AeronConfig.stopDriver(mediaDriver) aeronDriver.close()
} }
System.err.println("Connection count (after reconnecting) is: " + reconnectCount.value) System.err.println("Connection count (after reconnecting) is: " + reconnectCount.value)