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,
)