Fixed issues with connection handshake. ALL kryo usage for a handshake is now ONLY on the primary coroutine

This commit is contained in:
nathan 2020-09-09 12:24:04 +02:00
parent 06a35ed027
commit ce5eb8cb77
4 changed files with 51 additions and 62 deletions

View File

@ -128,6 +128,9 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
*/
val serialization: Serialization
private val handshakeKryo: KryoExtra
private val sendIdleStrategy: CoroutineIdleStrategy
/**
@ -279,6 +282,7 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
// serialization stuff
serialization = config.serialization
sendIdleStrategy = config.sendIdleStrategy
handshakeKryo = serialization.initHandshakeKryo()
// we have to be able to specify WHAT property store we want to use, since it can change!
settingsStore = config.settingsStore
@ -461,27 +465,28 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
}
}
@Suppress("DuplicatedCode")
// note: CANNOT be called in action dispatch. ALWAYS ON SAME THREAD
internal suspend fun writeHandshakeMessage(publication: Publication, message: HandshakeMessage) {
// The sessionId is globally unique, and is assigned by the server.
logger.trace {
"[${publication.sessionId()}] send HS: $message"
}
// we are not thread-safe!
val kryo = serialization.takeHandshakeKryo()
try {
kryo.write(message)
// we are not thread-safe!
handshakeKryo.write(message)
val buffer = kryo.writerBuffer
val buffer = handshakeKryo.writerBuffer
val objectSize = buffer.position()
val internalBuffer = buffer.internalBuffer
var result: Long
while (true) {
result = publication.offer(internalBuffer, 0, objectSize)
// success!
if (result > 0) {
if (result >= 0) {
// success!
return
}
@ -514,7 +519,6 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
listenerManager.notifyError(exception)
} finally {
sendIdleStrategy.reset()
serialization.returnHandshakeKryo(kryo)
}
}
@ -526,9 +530,11 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
*
* @return the message
*/
// note: CANNOT be called in action dispatch. ALWAYS ON SAME THREAD
internal fun readHandshakeMessage(buffer: DirectBuffer, offset: Int, length: Int, header: Header): Any? {
try {
val message = serialization.readHandshakeMessage(buffer, offset, length)
val message = handshakeKryo.read(buffer, offset, length)
logger.trace {
"[${header.sessionId()}] received HS: $message"
}
@ -633,6 +639,7 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
}
// NOTE: this **MUST** stay on the same co-routine that calls "send". This cannot be re-dispatched onto a different coroutine!
@Suppress("DuplicatedCode")
internal suspend fun send(message: Any, publication: Publication, connection: Connection) {
// The sessionId is globally unique, and is assigned by the server.
logger.trace {
@ -651,8 +658,8 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
var result: Long
while (true) {
result = publication.offer(internalBuffer, 0, objectSize)
// success!
if (result > 0) {
if (result >= 0) {
// success!
return
}
@ -746,15 +753,15 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
connections.forEach {
it.close()
}
// the storage is closed via this as well.
settingsStore.close()
// Connections are closed first, because we want to make sure that no RMI messages can be received
// when we close the RMI support objects (in which case, weird - but harmless - errors show up)
rmiGlobalSupport.close()
}
// the storage is closed via this as well.
settingsStore.close()
// Connections are closed first, because we want to make sure that no RMI messages can be received
// when we close the RMI support objects (in which case, weird - but harmless - errors show up)
rmiGlobalSupport.close()
close0()
// if we are waiting for shutdown, cancel the waiting thread (since we have shutdown now)

View File

@ -110,10 +110,10 @@ internal class ClientHandshake<CONNECTION: Connection>(private val logger: KLogg
}
}
// called from the connect thread
suspend fun handshakeHello(handshakeConnection: MediaDriverConnection, connectionTimeoutMS: Long) : ClientConnectionInfo {
val registrationMessage = HandshakeMessage.helloFromClient(oneTimePad, config.settingsStore.getPublicKey()!!)
// Send the one-time pad to the server.
endPoint.writeHandshakeMessage(handshakeConnection.publication, registrationMessage)
sessionId = handshakeConnection.publication.sessionId()
@ -156,6 +156,7 @@ internal class ClientHandshake<CONNECTION: Connection>(private val logger: KLogg
return connectionHelloInfo!!
}
// called from the connect thread
suspend fun handshakeDone(handshakeConnection: MediaDriverConnection, connectionTimeoutMS: Long): Boolean {
val registrationMessage = HandshakeMessage.doneFromClient()

View File

@ -45,7 +45,7 @@ import kotlin.concurrent.write
/**
* @throws IllegalArgumentException If the port range is not valid
* 'notifyConnect' must be THE ONLY THING in this class to use the action dispatch!
*/
@Suppress("DuplicatedCode")
internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLogger,
@ -88,7 +88,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
if (message !is HandshakeMessage) {
listenerManager.notifyError(ClientRejectedException("[$sessionId] Connection from $connectionString not allowed! Invalid connection request"))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Invalid connection request"))
}
return false
@ -110,9 +110,12 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
// this enables the connection to start polling for messages
server.connections.add(pendingConnection)
server.actionDispatch.launch {
// now tell the client we are done
// now tell the client we are done
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.doneToClient(sessionId))
}
server.actionDispatch.launch {
// this must be THE ONLY THING in this class to use the action dispatch!
listenerManager.notifyConnect(pendingConnection)
}
}
@ -143,7 +146,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
if (server.connections.connectionCount() >= config.maxClientCount) {
listenerManager.notifyError(ClientRejectedException("Connection from $clientAddressString not allowed! Server is full. Max allowed is ${config.maxClientCount}"))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Server is full"))
}
return false
@ -157,16 +160,15 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
connectionsPerIpCounts.decrement(clientAddress, currentCountForIp)
listenerManager.notifyError(ClientRejectedException("Too many connections for IP address $clientAddressString. Max allowed is ${config.maxConnectionsPerIpAddress}"))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Too many connections for IP address"))
}
return false
}
connectionsPerIpCounts.increment(clientAddress, currentCountForIp)
} catch (e: Exception) {
listenerManager.notifyError(ClientRejectedException("could not validate client message", e))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Invalid connection"))
}
return false
@ -205,7 +207,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
connectionSessionId = sessionIdAllocator.allocate()
} catch (e: AllocationException) {
listenerManager.notifyError(ClientRejectedException("Connection from $connectionString not allowed! Unable to allocate a session ID for the client connection!"))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Connection error!"))
}
return
@ -220,7 +222,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
sessionIdAllocator.free(connectionSessionId)
listenerManager.notifyError(ClientRejectedException("Connection from $connectionString not allowed! Unable to allocate a stream ID for the client connection!"))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Connection error!"))
}
return
@ -235,7 +237,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
sessionIdAllocator.free(connectionStreamPubId)
listenerManager.notifyError(ClientRejectedException("Connection from $connectionString not allowed! Unable to allocate a stream ID for the client connection!"))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Connection error!"))
}
return
@ -271,10 +273,9 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
ListenerManager.cleanStackTrace(exception)
listenerManager.notifyError(connection, exception)
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Connection was not permitted!"))
}
return
}
@ -314,7 +315,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
}
// this tells the client all of the info to connect.
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, successMessage)
}
} catch (e: Exception) {
@ -374,7 +375,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
connectionsPerIpCounts.decrementSlow(clientAddress)
listenerManager.notifyError(ClientRejectedException("Connection from $clientAddressString not allowed! Unable to allocate a session ID for the client connection!"))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Connection error!"))
}
return
@ -390,7 +391,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
sessionIdAllocator.free(connectionSessionId)
listenerManager.notifyError(ClientRejectedException("Connection from $clientAddressString not allowed! Unable to allocate a stream ID for the client connection!"))
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Connection error!"))
}
return
@ -447,10 +448,9 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
ListenerManager.cleanStackTrace(exception)
listenerManager.notifyError(connection, exception)
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, HandshakeMessage.error("Connection was not permitted!"))
}
return
}
@ -483,7 +483,7 @@ internal class ServerHandshake<CONNECTION : Connection>(private val logger: KLog
}
// this tells the client all of the info to connect.
server.actionDispatch.launch {
runBlocking {
server.writeHandshakeMessage(handshakePublication, successMessage)
}
} catch (e: Exception) {

View File

@ -44,6 +44,7 @@ import dorkbox.os.OS
import dorkbox.util.serialization.SerializationDefaults
import dorkbox.util.serialization.SerializationManager
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import mu.KLogger
import mu.KotlinLogging
@ -93,8 +94,6 @@ open class Serialization(private val references: Boolean = true, private val fac
private var initialized = atomic(false)
private val initializedKryoCount = atomic(0)
private val kryoPool = MultithreadConcurrentQueue<KryoExtra>(1024) // reasonable size of available kryo's
private val kryoHandshakePool = MultithreadConcurrentQueue<KryoExtra>(1024) // reasonable size of available kryo's
// used by operations performed during kryo initialization, which are by default package access (since it's an anon-inner class)
// All registration MUST happen in-order of when the register(*) method was called, otherwise there are problems.
@ -119,14 +118,15 @@ open class Serialization(private val references: Boolean = true, private val fac
val rmiHolder = RmiHolder()
// reflectASM doesn't work on android
private val useAsm = !OS.isAndroid()
// These are GLOBAL, single threaded only kryo instances.
// The readKryo WILL RE-CONFIGURED during the client handshake! (it is all the same thread, so object visibility is not a problem)
// NOTE: These following can ONLY be called on a single thread!
private var readKryo = initGlobalKryo()
private var readHandshakeKryo = initHandshakeKryo()
private val kryoPool = MultithreadConcurrentQueue<KryoExtra>(1024) // reasonable size of available kryo's?
/**
* Registers the class using the lowest, next available integer ID and the [default serializer][Kryo.getDefaultSerializer].
@ -268,7 +268,7 @@ open class Serialization(private val references: Boolean = true, private val fac
/**
* Kryo specifically for handshakes
*/
private fun initHandshakeKryo(): KryoExtra {
internal fun initHandshakeKryo(): KryoExtra {
val kryo = KryoExtra()
kryo.instantiatorStrategy = instantiatorStrategy
@ -613,22 +613,6 @@ open class Serialization(private val references: Boolean = true, private val fac
return initializeClassRegistrations(kryo)
}
/**
* @return takes a kryo instance from the pool, or creates one if the pool was empty
*/
fun takeHandshakeKryo(): KryoExtra {
// ALWAYS get as many as needed. Recycle them to prevent too many getting created
return kryoHandshakePool.poll() ?: initHandshakeKryo()
}
/**
* Returns a kryo instance to the pool for re-use later on
*/
fun returnHandshakeKryo(kryo: KryoExtra) {
// return as much as we can. don't suspend if the pool is full, we just throw it away.
kryoHandshakePool.offer(kryo)
}
/**
* @return The number of kryo instances created. This does not reflect the size of the pool, just the number of
* existing kryo instances.
@ -819,9 +803,6 @@ open class Serialization(private val references: Boolean = true, private val fac
}
// NOTE: These following functions are ONLY called on a single thread!
fun readHandshakeMessage(buffer: DirectBuffer, offset: Int, length: Int): Any? {
return readHandshakeKryo.read(buffer, offset, length)
}
fun readMessage(buffer: DirectBuffer, offset: Int, length: Int, connection: Connection): Any? {
return readKryo.read(buffer, offset, length, connection)
}