orbit.server.mesh.AddressableManager.kt Maven / Gradle / Ivy
/*
Copyright (C) 2015 - 2019 Electronic Arts Inc. All rights reserved.
This file is part of the Orbit Project .
See license in LICENSE.
*/
package orbit.server.mesh
import io.micrometer.core.instrument.Metrics
import orbit.server.OrbitServerConfig
import orbit.server.service.Meters
import orbit.shared.addressable.AddressableLease
import orbit.shared.addressable.AddressableReference
import orbit.shared.addressable.NamespacedAddressableReference
import orbit.shared.exception.PlacementFailedException
import orbit.shared.mesh.Namespace
import orbit.shared.mesh.NodeId
import orbit.shared.mesh.NodeStatus
import orbit.util.instrumentation.recordSuspended
import orbit.util.misc.attempt
import orbit.util.time.Clock
import orbit.util.time.toTimestamp
class AddressableManager(
private val addressableDirectory: AddressableDirectory,
private val clusterManager: ClusterManager,
private val clock: Clock,
config: OrbitServerConfig
) {
private val leaseExpiration = config.addressableLeaseDuration
private val placementTimer = Metrics.timer(Meters.Names.PlacementTimer)
suspend fun locateOrPlace(
namespace: Namespace,
addressableReference: AddressableReference,
ineligibleNodes: List = emptyList()
): NodeId =
NamespacedAddressableReference(namespace, addressableReference).let { key ->
addressableDirectory.getOrPut(key) {
createNewEntry(key, ineligibleNodes)
}.let {
val invalidNode =
clusterManager.getNode(it.nodeId) == null || (ineligibleNodes.contains(it.nodeId) == true)
val expired = clock.inPast(it.expiresAt)
if (expired || invalidNode) {
val newEntry = createNewEntry(key, ineligibleNodes.plus(it.nodeId))
if (addressableDirectory.compareAndSet(key, it, newEntry)) {
newEntry.nodeId
} else {
locateOrPlace(namespace, addressableReference, ineligibleNodes.plus(it.nodeId))
}
} else {
it.nodeId
}
}
}
// TODO: We need to take the expiry time
suspend fun renewLease(addressableReference: AddressableReference, nodeId: NodeId): AddressableLease =
addressableDirectory.manipulate(
NamespacedAddressableReference(
nodeId.namespace,
addressableReference
)
) { initialValue ->
if (initialValue == null || initialValue.nodeId != nodeId || clock.inPast(initialValue.expiresAt)) {
throw PlacementFailedException("Could not renew lease for $addressableReference")
}
initialValue.copy(
expiresAt = clock.now().plus(leaseExpiration.expiresIn).toTimestamp(),
renewAt = clock.now().plus(leaseExpiration.renewIn).toTimestamp()
)
}!!
suspend fun abandonLease(addressableReference: AddressableReference, nodeId: NodeId): Boolean {
val key = NamespacedAddressableReference(nodeId.namespace, addressableReference)
val currentLease = addressableDirectory.get(key)
if (currentLease != null && currentLease.nodeId == nodeId && clock.inFuture(currentLease.expiresAt)) {
return addressableDirectory.compareAndSet(key, currentLease, null)
}
return false
}
private suspend fun createNewEntry(reference: NamespacedAddressableReference, invalidNodes: List) =
AddressableLease(
nodeId = place(reference, invalidNodes),
reference = reference.addressableReference,
expiresAt = clock.now().plus(leaseExpiration.expiresIn).toTimestamp(),
renewAt = clock.now().plus(leaseExpiration.renewIn).toTimestamp()
)
private suspend fun place(reference: NamespacedAddressableReference, invalidNodes: List): NodeId =
runCatching {
placementTimer.recordSuspended {
attempt(
maxAttempts = 5,
initialDelay = 1000
) {
val allNodes = clusterManager.getAllNodes()
val potentialNodes = allNodes
.filter { !invalidNodes.contains(it.id) }
.filter { it.id.namespace == reference.namespace }
.filter { it.nodeStatus == NodeStatus.ACTIVE }
.filter { it.capabilities.addressableTypes.contains(reference.addressableReference.type) }
potentialNodes.random().id
}
}
}.fold(
{ it },
{ throw PlacementFailedException("Could not find node capable of hosting $reference") })
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy