More careful event dispatch (no longer global, but per endpoint)

This commit is contained in:
Robinson 2023-10-26 08:05:08 +02:00
parent 4b58a63dc1
commit 70825708a3
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
9 changed files with 293 additions and 313 deletions

View File

@ -422,10 +422,10 @@ open class Client<CONNECTION : Connection>(config: ClientConfiguration = ClientC
// on the client, we must GUARANTEE that the disconnect/close completes before NEW connect begins.
// we will know this if we are running inside an INTERNAL dispatch that is NOT the connect dispatcher!
val currentEvent = EventDispatcher.getCurrentEvent()
if (currentEvent != null && currentEvent != EventDispatcher.CONNECT) {
if (eventDispatch.isDispatch() && !eventDispatch.CONNECT.isDispatch()) {
// only re-dispatch if we are on the event dispatch AND it's not the CONNECT one
EventDispatcher.CONNECT.launch {
// if we are on an "outside" thread, then we don't care.
eventDispatch.CONNECT.launch {
connect(
remoteAddress = remoteAddress,
remoteAddressString = remoteAddressString,

View File

@ -283,12 +283,14 @@ open class Server<CONNECTION : Connection>(config: ServerConfiguration = ServerC
if (mustRestartDriverOnError) {
logger.error("Critical driver error detected, restarting server.")
EventDispatcher.launchSequentially(EventDispatcher.CONNECT) {
eventDispatch.CLOSE.launch {
waitForEndpointShutdown()
// also wait for everyone else to shutdown!!
aeronDriver.internal.endPointUsages.forEach {
it.waitForEndpointShutdown()
if (it !== this@Server) {
it.waitForEndpointShutdown()
}
}

View File

@ -20,7 +20,6 @@ import dorkbox.collections.ConcurrentIterator
import dorkbox.collections.LockFreeHashSet
import dorkbox.network.Configuration
import dorkbox.network.connection.EndPoint
import dorkbox.network.connection.EventDispatcher
import dorkbox.network.connection.ListenerManager
import dorkbox.network.connection.ListenerManager.Companion.cleanAllStackTrace
import dorkbox.network.connection.ListenerManager.Companion.cleanStackTrace
@ -196,14 +195,12 @@ internal class AeronDriverInternal(endPoint: EndPoint<*>?, config: Configuration
// this must be set before anything else happens
mustRestartDriverOnError = true
// we must close all the connections on a DIFFERENT thread!
EventDispatcher.CLOSE.launch {
endPointUsages.forEach {
it.close(closeEverything = false,
sendDisconnectMessage = false,
notifyDisconnect = false,
releaseWaitingThreads = false)
}
// close will make sure to run on a different thread
endPointUsages.forEach {
it.close(closeEverything = false,
sendDisconnectMessage = false,
notifyDisconnect = false,
releaseWaitingThreads = false)
}
}
}

View File

@ -20,7 +20,6 @@ import dorkbox.bytes.ByteArrayWrapper
import dorkbox.collections.ConcurrentIterator
import dorkbox.network.Configuration
import dorkbox.network.connection.EndPoint
import dorkbox.network.connection.EventDispatcher
import dorkbox.util.NamedThreadFactory
import kotlinx.atomicfu.atomic
import org.agrona.concurrent.IdleStrategy
@ -187,7 +186,7 @@ internal class EventPoller {
fun close(logger: Logger, endPoint: EndPoint<*>) {
// make sure that we close on the CLOSE dispatcher if we run on the poll dispatcher!
if (isDispatch()) {
EventDispatcher.CLOSE.launch {
endPoint.eventDispatch.CLOSE.launch {
close(logger, endPoint)
}
return

View File

@ -337,13 +337,20 @@ open class Connection(connectionParameters: ConnectionParams<*>) {
// the compareAndSet is used to make sure that if we call close() MANUALLY, (and later) when the auto-cleanup/disconnect is called -- it doesn't
// try to do it again.
// make sure that EVERYTHING before "close()" runs before we do
EventDispatcher.launchSequentially(EventDispatcher.CLOSE) {
closeImmediately(
sendDisconnectMessage = sendDisconnectMessage,
notifyDisconnect = notifyDisconnect,
closeEverything = closeEverything)
// make sure that EVERYTHING before "close()" runs before we do.
// If there are multiple clients/servers sharing the same NetworkPoller -- then they will wait on each other!
val close = endPoint.eventDispatch.CLOSE
if (!close.isDispatch()) {
close.launch {
close(sendDisconnectMessage, notifyDisconnect, closeEverything)
}
return
}
closeImmediately(
sendDisconnectMessage = sendDisconnectMessage,
notifyDisconnect = notifyDisconnect,
closeEverything = closeEverything)
}

View File

@ -100,6 +100,8 @@ abstract class EndPoint<CONNECTION : Connection> private constructor(val type: C
val logger: Logger = LoggerFactory.getLogger(loggerName)
internal val eventDispatch = EventDispatcher(loggerName)
private val handler = CoroutineExceptionHandler { _, exception ->
logger.error("Uncaught Coroutine Error: ${exception.stackTraceToString()}")
}
@ -110,7 +112,7 @@ abstract class EndPoint<CONNECTION : Connection> private constructor(val type: C
private val messageChannel = Channel<Paired<CONNECTION>>()
private val pairedPool: Pool<Paired<CONNECTION>>
internal val listenerManager = ListenerManager<CONNECTION>(logger)
internal val listenerManager = ListenerManager<CONNECTION>(logger, eventDispatch)
val connections = ConcurrentIterator<CONNECTION>()

View File

@ -23,110 +23,112 @@ import org.slf4j.LoggerFactory
import java.util.concurrent.*
/**
* Event logic throughout the network MUST be run on multiple threads! There are deadlock issues if it is only one.
* Event logic throughout the network MUST be run on multiple threads! There are deadlock issues if it is only one, or if the client + server
* share an event dispatcher (multiple network restarts were required to check this)
*
* WARNING: The logic in this class will ONLY work in this class, as it relies on this specific behavior. Do not use it elsewhere!
*/
enum class EventDispatcher {
// NOTE: CLOSE must be last!
HANDSHAKE, CONNECT, DISCONNECT, ERROR, CLOSE;
internal class EventDispatcher(val type: String) {
enum class EDType {
// CLOSE must be last!
CONNECT, ERROR, CLOSE
}
companion object {
private val DEBUG_EVENTS = false
private val traceId = atomic(0)
private val logger = LoggerFactory.getLogger(EventDispatcher::class.java.simpleName)
private val threadIds = entries.map { atomic(0L) }.toTypedArray()
private val executors = entries.map { event ->
// It CANNOT be the default dispatch because there will be thread starvation
// NOTE: THIS CANNOT CHANGE!! IT WILL BREAK EVERYTHING IF IT CHANGES!
Executors.newSingleThreadExecutor(
NamedThreadFactory("Event Dispatcher-${event.name}",
Configuration.networkThreadGroup, Thread.NORM_PRIORITY, true) { thread ->
// when a new thread is created, assign it to the array
threadIds[event.ordinal].lazySet(thread.id)
}
)
}.toTypedArray()
private val typedEntries: Array<EventDispatcher>
private val typedEntries: Array<EDType>
init {
executors.forEachIndexed { _, executor ->
executor.submit {
// this is to create a new thread only, so that the thread ID can be assigned
}
}
typedEntries = EDType.entries.toTypedArray()
}
}
typedEntries = entries.toTypedArray()
private val logger = LoggerFactory.getLogger("$type Dispatch")
private val threadIds = EDType.entries.map { atomic(0L) }.toTypedArray()
private val executors = EDType.entries.map { event ->
// It CANNOT be the default dispatch because there will be thread starvation
// NOTE: THIS CANNOT CHANGE!! IT WILL BREAK EVERYTHING IF IT CHANGES!
Executors.newSingleThreadExecutor(
NamedThreadFactory(
namePrefix = "$type-${event.name}",
group = Configuration.networkThreadGroup,
threadPriority = Thread.NORM_PRIORITY,
daemon = true
) { thread ->
// when a new thread is created, assign it to the array
threadIds[event.ordinal].lazySet(thread.id)
}
)
}.toTypedArray()
internal class ED(private val dispatcher: EventDispatcher, private val type: EDType) {
fun launch(function: () -> Unit) {
dispatcher.launch(type, function)
}
/**
* Checks if the current execution thread is running inside one of the event dispatchers listed.
*
* No values specified means we check ALL events
*/
fun isDispatch(): Boolean {
return isCurrentEvent(*typedEntries)
return dispatcher.isDispatch(type)
}
}
/**
* Checks if the current execution thread is running inside one of the event dispatchers listed.
*
* No values specified means we check ALL events
*/
fun isCurrentEvent(vararg events: EventDispatcher = typedEntries): Boolean {
val threadId = Thread.currentThread().id
val CONNECT: ED
val ERROR: ED
val CLOSE: ED
events.forEach { event ->
if (threadIds[event.ordinal].value == threadId) {
return true
}
init {
executors.forEachIndexed { _, executor ->
executor.submit {
// this is to create a new thread only, so that the thread ID can be assigned
}
return false
}
/**
* Checks if the current execution thread is NOT running inside one of the event dispatchers listed.
*
* No values specified means we check ALL events
*/
fun isNotCurrentEvent(vararg events: EventDispatcher = typedEntries): Boolean {
val currentDispatch = getCurrentEvent() ?: return false
CONNECT = ED(this, EDType.CONNECT)
ERROR = ED(this, EDType.ERROR)
CLOSE = ED(this, EDType.CLOSE)
}
return events.contains(currentDispatch)
}
/**
* @return which event dispatch thread we are running in, if any
*/
fun getCurrentEvent(): EventDispatcher? {
val threadId = Thread.currentThread().id
/**
* Checks if the current execution thread is running inside one of the event dispatchers.
*/
fun isDispatch(): Boolean {
val threadId = Thread.currentThread().id
typedEntries.forEach { event ->
if (threadIds[event.ordinal].value == threadId) {
return event
}
typedEntries.forEach { event ->
if (threadIds[event.ordinal].value == threadId) {
return true
}
return null
}
/**
* Each event type runs inside its own coroutine dispatcher.
*
* We want EACH event type to run in its own dispatcher... on its OWN thread, in order to prevent deadlocks
* This is because there are blocking dependencies: DISCONNECT -> CONNECT.
*
* If an event is RE-ENTRANT, then it will immediately execute!
*/
private fun launch(event: EventDispatcher, function: () -> Unit) {
val eventId = event.ordinal
return false
}
/**
* Checks if the current execution thread is running inside one of the event dispatchers.
*/
private fun isDispatch(type: EDType): Boolean {
val threadId = Thread.currentThread().id
return threadIds[type.ordinal].value == threadId
}
/**
* Each event type runs inside its own coroutine dispatcher.
*
* We want EACH event type to run in its own dispatcher... on its OWN thread, in order to prevent deadlocks
* This is because there are blocking dependencies: DISCONNECT -> CONNECT.
*
* If an event is RE-ENTRANT, then it will immediately execute!
*/
private fun launch(event: EDType, function: () -> Unit) {
val eventId = event.ordinal
try {
if (DEBUG_EVENTS) {
val id = traceId.getAndIncrement()
executors[eventId].submit {
@ -141,31 +143,8 @@ enum class EventDispatcher {
} else {
executors[eventId].submit(function)
}
} catch (e: Exception) {
logger.error("Error during event dispatch!", e)
}
fun launchSequentially(endEvent: EventDispatcher, function: () -> Unit) {
// If one of our callbacks requested a shutdown, we wait until all callbacks have run ... THEN shutdown
val event = getCurrentEvent()
val index = event?.ordinal ?: -1
// This will loop through until it runs on the CLOSE EventDispatcher
if (index < endEvent.ordinal) {
// If this runs inside EVENT.CONNECT/DISCONNECT/ETC, we must ***WAIT*** until all listeners have been called!
// this problem is solved by running AGAIN after we have finished running whatever event dispatcher we are currently on
// MORE SPECIFICALLY, we must run at the end of our current one, but repeatedly until CLOSE
EventDispatcher.launch(typedEntries[index+1]) {
launchSequentially(endEvent, function)
}
} else {
endEvent.launch(function)
}
}
}
fun launch(function: () -> Unit) {
launch(this, function)
}
}

View File

@ -28,7 +28,7 @@ import kotlin.concurrent.write
/**
* Manages all of the different connect/disconnect/etc listeners
*/
internal class ListenerManager<CONNECTION: Connection>(private val logger: Logger) {
internal class ListenerManager<CONNECTION: Connection>(private val logger: Logger, val eventDispatch: EventDispatcher) {
companion object {
/**
* Specifies the load-factor for the IdentityMap used to manage keeping track of the number of connections + listeners
@ -402,7 +402,7 @@ internal class ListenerManager<CONNECTION: Connection>(private val logger: Logge
fun notifyConnect(connection: CONNECTION) {
val list = onConnectList
if (list.isNotEmpty()) {
EventDispatcher.CONNECT.launch {
connection.endPoint.eventDispatch.CONNECT.launch {
list.forEach {
try {
it(connection)
@ -436,7 +436,7 @@ internal class ListenerManager<CONNECTION: Connection>(private val logger: Logge
fun directNotifyDisconnect(connection: CONNECTION) {
val list = onDisconnectList
if (list.isNotEmpty()) {
EventDispatcher.DISCONNECT.launch {
connection.endPoint.eventDispatch.CLOSE.launch {
list.forEach {
try {
it(connection)
@ -461,7 +461,7 @@ internal class ListenerManager<CONNECTION: Connection>(private val logger: Logge
fun notifyError(connection: CONNECTION, exception: Throwable) {
val list = onErrorList
if (list.isNotEmpty()) {
EventDispatcher.ERROR.launch {
connection.endPoint.eventDispatch.ERROR.launch {
list.forEach {
try {
it(connection, exception)
@ -485,7 +485,7 @@ internal class ListenerManager<CONNECTION: Connection>(private val logger: Logge
fun notifyError(exception: Throwable) {
val list = onErrorGlobalList
if (list.isNotEmpty()) {
EventDispatcher.ERROR.launch {
eventDispatch.ERROR.launch {
list.forEach {
try {
it(exception)

View File

@ -26,7 +26,6 @@ import dorkbox.network.aeron.AeronDriver
import dorkbox.network.aeron.AeronDriver.Companion.uriHandshake
import dorkbox.network.aeron.AeronPoller
import dorkbox.network.connection.Connection
import dorkbox.network.connection.EventDispatcher
import dorkbox.network.connection.IpInfo
import dorkbox.network.exceptions.ServerException
import dorkbox.network.exceptions.ServerHandshakeException
@ -121,72 +120,58 @@ internal object ServerHandshakePollers {
}
// we have read all the data, now dispatch it.
EventDispatcher.HANDSHAKE.launch {
// HandshakeMessage.HELLO
// HandshakeMessage.DONE
val messageState = message.state
val connectKey = message.connectKey
// HandshakeMessage.HELLO
// HandshakeMessage.DONE
val messageState = message.state
val connectKey = message.connectKey
if (messageState == HandshakeMessage.HELLO) {
// we create a NEW publication for the handshake, which connects directly to the client handshake subscription
if (messageState == HandshakeMessage.HELLO) {
// we create a NEW publication for the handshake, which connects directly to the client handshake subscription
val publicationUri = uriHandshake(CommonContext.IPC_MEDIA, isReliable)
val publicationUri = uriHandshake(CommonContext.IPC_MEDIA, isReliable)
// this will always connect to the CLIENT handshake subscription!
val publication = try {
driver.addExclusivePublication(publicationUri, message.streamId, logInfo, true)
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
// this will always connect to the CLIENT handshake subscription!
val publication = try {
driver.addExclusivePublication(publicationUri, message.streamId, logInfo, true)
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Cannot create IPC publication back to client remote process", e))
return@launch
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Cannot create IPC publication back to client remote process", e))
return
}
try {
// we actually have to wait for it to connect before we continue
driver.waitForConnection(publication, handshakeTimeoutNs, logInfo) { cause ->
ServerTimedoutException("$logInfo publication cannot connect with client in ${Sys.getTimePrettyFull(handshakeTimeoutNs)}", cause)
}
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
try {
// we actually have to wait for it to connect before we continue
driver.waitForConnection(publication, handshakeTimeoutNs, logInfo) { cause ->
ServerTimedoutException("$logInfo publication cannot connect with client in ${Sys.getTimePrettyFull(handshakeTimeoutNs)}", cause)
}
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Cannot create IPC publication back to client remote process", e))
return@launch
}
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Cannot create IPC publication back to client remote process", e))
return
}
try {
val success = handshake.processIpcHandshakeMessageServer(
server = server,
handshaker = handshaker,
aeronDriver = driver,
handshakePublication = publication,
publicKey = message.publicKey!!,
message = message,
logInfo = logInfo,
logger = logger
)
if (success) {
publications[connectKey] = publication
} else {
try {
// we might not be able to close this connection.
driver.close(publication, logInfo)
}
catch (e: Exception) {
server.listenerManager.notifyError(e)
}
}
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
try {
val success = handshake.processIpcHandshakeMessageServer(
server = server,
handshaker = handshaker,
aeronDriver = driver,
handshakePublication = publication,
publicKey = message.publicKey!!,
message = message,
logInfo = logInfo,
logger = logger
)
if (success) {
publications[connectKey] = publication
} else {
try {
// we might not be able to close this connection.
driver.close(publication, logInfo)
@ -194,35 +179,8 @@ internal object ServerHandshakePollers {
catch (e: Exception) {
server.listenerManager.notifyError(e)
}
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Error processing IPC handshake", e))
}
} else {
// HandshakeMessage.DONE
val publication = publications.remove(connectKey)
if (publication == null) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] No publication back to IPC"))
return@launch
}
try {
handshake.validateMessageTypeAndDoPending(
server = server,
handshaker = handshaker,
handshakePublication = publication,
message = message,
logInfo = logInfo,
logger = logger
)
} catch (e: Exception) {
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Error processing IPC handshake", e))
}
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
@ -234,6 +192,45 @@ internal object ServerHandshakePollers {
catch (e: Exception) {
server.listenerManager.notifyError(e)
}
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Error processing IPC handshake", e))
}
} else {
// HandshakeMessage.DONE
val publication = publications.remove(connectKey)
if (publication == null) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] No publication back to IPC"))
return
}
try {
handshake.validateMessageTypeAndDoPending(
server = server,
handshaker = handshaker,
handshakePublication = publication,
message = message,
logInfo = logInfo,
logger = logger
)
} catch (e: Exception) {
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Error processing IPC handshake", e))
}
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
try {
// we might not be able to close this connection.
driver.close(publication, logInfo)
}
catch (e: Exception) {
server.listenerManager.notifyError(e)
}
}
}
@ -365,86 +362,71 @@ internal object ServerHandshakePollers {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
return
}
EventDispatcher.HANDSHAKE.launch {
// HandshakeMessage.HELLO
// HandshakeMessage.DONE
val messageState = message.state
val connectKey = message.connectKey
// HandshakeMessage.HELLO
// HandshakeMessage.DONE
val messageState = message.state
val connectKey = message.connectKey
if (messageState == HandshakeMessage.HELLO) {
// we create a NEW publication for the handshake, which connects directly to the client handshake subscription
if (messageState == HandshakeMessage.HELLO) {
// we create a NEW publication for the handshake, which connects directly to the client handshake subscription
// we explicitly have the publisher "connect to itself", because we are using MDC to work around NAT.
// It will "auto-connect" to the correct client port (negotiated by the MDC client subscription negotiating on the
// control port of the server)
val publicationUri = uriHandshake(CommonContext.UDP_MEDIA, isReliable)
.controlEndpoint(ipInfo.getAeronPubAddress(isRemoteIpv4) + ":" + mdcPortPub)
// we explicitly have the publisher "connect to itself", because we are using MDC to work around NAT.
// It will "auto-connect" to the correct client port (negotiated by the MDC client subscription negotiating on the
// control port of the server)
val publicationUri = uriHandshake(CommonContext.UDP_MEDIA, isReliable)
.controlEndpoint(ipInfo.getAeronPubAddress(isRemoteIpv4) + ":" + mdcPortPub)
// this will always connect to the CLIENT handshake subscription!
val publication = try {
driver.addExclusivePublication(publicationUri, message.streamId, logInfo, false)
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
// this will always connect to the CLIENT handshake subscription!
val publication = try {
driver.addExclusivePublication(publicationUri, message.streamId, logInfo, false)
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Cannot create publication back to $clientAddressString", e))
return@launch
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Cannot create publication back to $clientAddressString", e))
return
}
try {
// we actually have to wait for it to connect before we continue
driver.waitForConnection(publication, handshakeTimeoutNs, logInfo) { cause ->
ServerTimedoutException("$logInfo publication cannot connect with client in ${Sys.getTimePrettyFull(handshakeTimeoutNs)}", cause)
}
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
try {
// we actually have to wait for it to connect before we continue
driver.waitForConnection(publication, handshakeTimeoutNs, logInfo) { cause ->
ServerTimedoutException("$logInfo publication cannot connect with client in ${Sys.getTimePrettyFull(handshakeTimeoutNs)}", cause)
}
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Cannot create publication back to $clientAddressString", e))
return
}
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Cannot create publication back to $clientAddressString", e))
return@launch
}
try {
val success = handshake.processUdpHandshakeMessageServer(
server = server,
handshaker = handshaker,
handshakePublication = publication,
publicKey = message.publicKey!!,
clientAddress = clientAddress,
clientAddressString = clientAddressString,
portPub = message.port,
portSub = serverPortSub,
mdcPortPub = mdcPortPub,
isReliable = isReliable,
message = message,
logInfo = logInfo,
logger = logger
)
try {
val success = handshake.processUdpHandshakeMessageServer(
server = server,
handshaker = handshaker,
handshakePublication = publication,
publicKey = message.publicKey!!,
clientAddress = clientAddress,
clientAddressString = clientAddressString,
portPub = message.port,
portSub = serverPortSub,
mdcPortPub = mdcPortPub,
isReliable = isReliable,
message = message,
logInfo = logInfo,
logger = logger
)
if (success) {
publications[connectKey] = publication
} else {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
try {
// we might not be able to close this connection.
driver.close(publication, logInfo)
}
catch (e: Exception) {
server.listenerManager.notifyError(e)
}
}
} catch (e: Exception) {
if (success) {
publications[connectKey] = publication
} else {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
@ -454,50 +436,62 @@ internal object ServerHandshakePollers {
driver.close(publication, logInfo)
}
catch (e: Exception) {
driver.close(publication, logInfo)
server.listenerManager.notifyError(e)
}
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Error processing IPC handshake", e))
}
} else {
// HandshakeMessage.DONE
val publication = publications.remove(connectKey)
if (publication == null) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] No publication back to $clientAddressString"))
return@launch
}
try {
handshake.validateMessageTypeAndDoPending(
server = server,
handshaker = handshaker,
handshakePublication = publication,
message = message,
logInfo = logInfo,
logger = logger
)
} catch (e: Exception) {
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Error processing IPC handshake", e))
}
} catch (e: Exception) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
try {
// we might not be able to close this connection.
driver.close(publication, logInfo)
}
catch (e: Exception) {
server.listenerManager.notifyError(e)
driver.close(publication, logInfo)
}
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Error processing IPC handshake", e))
}
} else {
// HandshakeMessage.DONE
val publication = publications.remove(connectKey)
if (publication == null) {
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] No publication back to $clientAddressString"))
return
}
try {
handshake.validateMessageTypeAndDoPending(
server = server,
handshaker = handshaker,
handshakePublication = publication,
message = message,
logInfo = logInfo,
logger = logger
)
} catch (e: Exception) {
server.listenerManager.notifyError(ServerHandshakeException("[$logInfo] Error processing IPC handshake", e))
}
try {
// we might not be able to close this connection.
driver.close(publication, logInfo)
}
catch (e: Exception) {
server.listenerManager.notifyError(e)
}
// we should immediately remove the logbuffer for this! Aeron will **EVENTUALLY** remove the logbuffer, but if errors
// and connections occur too quickly (within the cleanup/linger period), we can run out of memory!
driver.deleteLogFile(image)
}
}