orbit.client.execution.ExecutionSystem.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.client.execution
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withTimeout
import mu.KotlinLogging
import orbit.client.OrbitClientConfig
import orbit.client.addressable.Addressable
import orbit.client.addressable.AddressableClass
import orbit.client.addressable.AddressableConstructor
import orbit.client.addressable.AddressableDefinitionDirectory
import orbit.client.addressable.AddressableImplDefinition
import orbit.client.addressable.DeactivationReason
import orbit.client.net.ClientState
import orbit.client.net.Completion
import orbit.client.net.LocalNode
import orbit.shared.addressable.AddressableInvocation
import orbit.shared.addressable.AddressableReference
import orbit.shared.exception.RerouteMessageException
import orbit.util.di.ComponentContainer
import orbit.util.time.Clock
import java.util.concurrent.ConcurrentHashMap
internal class ExecutionSystem(
private val executionLeases: ExecutionLeases,
private val definitionDirectory: AddressableDefinitionDirectory,
private val componentContainer: ComponentContainer,
private val clock: Clock,
private val addressableConstructor: AddressableConstructor,
private val defaultDeactivator: AddressableDeactivator,
private val localNode: LocalNode,
config: OrbitClientConfig
) {
private val logger = KotlinLogging.logger { }
private val activeAddressables = ConcurrentHashMap()
private val deactivationTimeoutMs = config.deactivationTimeout.toMillis()
private val defaultTtl = config.addressableTTL.toMillis()
private val clientState: ClientState get() = localNode.status.clientState
suspend fun handleInvocation(invocation: AddressableInvocation, completion: Completion) {
try {
executionLeases.getOrRenewLease(invocation.reference)
var handle = activeAddressables[invocation.reference]
if (clientState == ClientState.STOPPING && (handle == null || !handle.active)) {
completion.completeExceptionally(RerouteMessageException("Client is stopping, message should be routed to a new node."))
return
}
if (handle == null) {
handle = activate(invocation.reference)
}
checkNotNull(handle) { "No active addressable found for ${invocation.reference}" }
val result = handle.invoke(invocation).await()
completion.complete(result)
} catch (t: Throwable) {
completion.completeExceptionally(t)
}
}
suspend fun tick() {
val (deactivate, active) = activeAddressables.values.partition { handle ->
handle.deactivateNextTick ||
(clock.currentTime - handle.lastActivity > defaultTtl) ||
(executionLeases.getLease(handle.reference) == null)
}
val expired = active.filter { clock.inPast(executionLeases.getLease(it.reference)!!.renewAt) }
if (deactivate.any() || expired.any()) {
logger.debug { "Execution system tick: ${expired} expired, ${deactivate.count()} deactivating." }
}
activeAddressables.forEach { (_, handle) ->
if (handle.deactivateNextTick) {
deactivate(handle, DeactivationReason.EXTERNALLY_TRIGGERED)
return@forEach
}
if (clock.currentTime - handle.lastActivity > defaultTtl) {
deactivate(handle, DeactivationReason.TTL_EXPIRED)
return@forEach
}
val lease = executionLeases.getLease(handle.reference)
if (lease != null) {
if (clock.inPast(lease.renewAt)) {
try {
executionLeases.renewLease(handle.reference)
} catch (t: Throwable) {
logger.error(t) { "Unexpected error renewing lease" }
deactivate(handle, DeactivationReason.LEASE_RENEWAL_FAILED)
return@forEach
}
}
} else {
logger.error { "No lease found for ${handle.reference}" }
deactivate(handle, DeactivationReason.LEASE_RENEWAL_FAILED)
return@forEach
}
}
if (deactivate.any() || expired.any()) {
logger.debug { "Execution system tick: end" }
}
}
suspend fun stop(deactivator: AddressableDeactivator?) {
while (activeAddressables.count() > 0) {
logger.info { "Draining ${activeAddressables.count()} addressables" }
(deactivator ?: defaultDeactivator)
.deactivate(activeAddressables.values.toList()) { a ->
deactivate(a, DeactivationReason.NODE_SHUTTING_DOWN)
}
}
}
private suspend fun activate(
reference: AddressableReference
): ExecutionHandle? =
definitionDirectory.getImplDefinition(reference.type).let {
val handle = getOrCreateAddressable(reference, it)
handle.activate().await()
handle
}
private suspend fun deactivate(deactivatable: Deactivatable, deactivationReason: DeactivationReason) {
try {
withTimeout(deactivationTimeoutMs) {
deactivatable.deactivate(deactivationReason).await()
}
} catch (t: TimeoutCancellationException) {
logger.error(
"A timeout occurred (>${deactivationTimeoutMs}ms) during deactivation of " +
"${deactivatable.reference}. This addressable is now considered deactivated, this may cause state " +
"corruption."
)
}
executionLeases.abandonLease(deactivatable.reference)
activeAddressables.remove(deactivatable.reference)
}
private fun getOrCreateAddressable(
reference: AddressableReference,
implDefinition: AddressableImplDefinition
): ExecutionHandle =
activeAddressables.getOrPut(reference) {
val newInstance = createInstance(implDefinition.implClass)
createHandle(reference, implDefinition, newInstance)
}
private fun createHandle(
reference: AddressableReference,
implDefinition: AddressableImplDefinition,
instance: Addressable
): ExecutionHandle =
ExecutionHandle(
componentContainer = componentContainer,
instance = instance,
reference = reference,
implDefinition = implDefinition
)
private fun createInstance(addressableClass: AddressableClass): Addressable =
addressableConstructor.constructAddressable(addressableClass)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy