All Downloads are FREE. Search and download functionalities are using the official Maven repository.

r.0.9.1.source-code.TransportLayer.kt Maven / Gradle / Ivy

The newest version!
package se.wollan.tolr

import kotlinx.serialization.Serializable
import se.wollan.time.HLCTimestamp
import se.wollan.time.HybridLogicalClockSynchronizer

/** Impl by consuming project as a remote procedure call, over e.g. HTTP. */
interface RPCClient {
    suspend fun call(remote: RemoteHostname, outData: String): String
}

internal interface RPCServer {
    suspend fun handle(inData: String): String
}

internal interface ServerServerAPI {
    suspend fun replicateLog(
        remote: RemoteHostname, // where to connect to
        batch: ReplicationBatch,
        target: RemoteHostname? // final destination of the replication data
    ): Pair
}

internal suspend fun ServerServerAPI.replicateLogInitial(
    remote: RemoteHostname,
    batch: ReplicationBatch,
) = replicateLog(remote, batch, null)

internal class ServerServerAPIImpl(
    private val rpcClient: RPCClient,
    private val clock: HybridLogicalClockSynchronizer,
) : ServerServerAPI {

    override suspend fun replicateLog(
        remote: RemoteHostname,
        batch: ReplicationBatch,
        target: RemoteHostname?
    ): Pair {
        // no need to encode msg type into data as we only have one (for now)
        val outTimestamp = clock.tick()
        val request = ReplicateLogRequest(target, outTimestamp, batch)
        val responseJson = rpcClient.call(remote, request.toJson())
        val response = responseJson.fromJson()
        clock.onReceive(response.timestamp)
        return response.batch to response.target
    }
}

internal class RPCServerImpl(
    private val logReplicator: LogReplicator,
    private val clock: HybridLogicalClockSynchronizer,
    private val serverServerAPI: ServerServerAPI,
    private val configurationProvider: ConfigurationProvider,
) : RPCServer {

    override suspend fun handle(inData: String): String {
        val request = inData.fromJson()
        clock.onReceive(request.timestamp)
        val (outBatch, target) = handleRequest(request)
        val outTimestamp = clock.tick()
        val response = ReplicateLogResponse(target, outTimestamp, outBatch)
        return response.toJson()
    }

    private suspend fun handleRequest(request: ReplicateLogRequest): Pair {
        val locallyExposedHostname = configurationProvider.getConfiguration().locallyExposedHostname
        if (request.target == null || request.target == locallyExposedHostname)
            return logReplicator.handleIncomingBatch(request.batch) to locallyExposedHostname

        // let's try to forward the request to target by changing remote
        return serverServerAPI.replicateLog(remote = request.target, request.batch, request.target)
    }
}

/**
 * @param target null on initial request means that target will be the first receiving node.
 * If non-null, please forward request to target host.
 * After initial request this field must be populated, and will be the same during the whole replication,
 * both in requests and responses.
 */
@Serializable
internal data class ReplicateLogRequest(
    val target: RemoteHostname?,
    val timestamp: HLCTimestamp,
    val batch: ReplicationBatch,
)

@Serializable
internal data class ReplicateLogResponse(
    val target: RemoteHostname,
    val timestamp: HLCTimestamp,
    val batch: ReplicationBatch,
)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy