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

orbit.server.OrbitServer.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

import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Metrics
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import mu.KotlinLogging
import orbit.server.auth.AuthSystem
import orbit.server.concurrent.RuntimePools
import orbit.server.concurrent.RuntimeScopes
import orbit.server.mesh.AddressableDirectory
import orbit.server.mesh.AddressableManager
import orbit.server.mesh.ClusterManager
import orbit.server.mesh.LocalNodeInfo
import orbit.server.mesh.NodeDirectory
import orbit.server.net.ConnectionManager
import orbit.server.net.RemoteMeshNodeManager
import orbit.server.pipeline.Pipeline
import orbit.server.pipeline.PipelineSteps
import orbit.server.pipeline.step.AuthStep
import orbit.server.pipeline.step.BlankStep
import orbit.server.pipeline.step.EchoStep
import orbit.server.pipeline.step.IdentityStep
import orbit.server.pipeline.step.PlacementStep
import orbit.server.pipeline.step.RoutingStep
import orbit.server.pipeline.step.TransportStep
import orbit.server.pipeline.step.VerifyStep
import orbit.server.router.Router
import orbit.server.service.AddressableManagementService
import orbit.server.service.ConnectionService
import orbit.server.service.GrpcEndpoint
import orbit.server.service.HealthCheck
import orbit.server.service.HealthCheckList
import orbit.server.service.HealthService
import orbit.server.service.Meters
import orbit.server.service.NodeManagementService
import orbit.server.service.ServerAuthInterceptor
import orbit.shared.mesh.NodeStatus
import orbit.util.concurrent.ShutdownLatch
import orbit.util.di.ComponentContainer
import orbit.util.instrumentation.recordSuspended
import orbit.util.time.ConstantTicker
import orbit.util.time.stopwatch
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.CoroutineContext

class OrbitServer(private val config: OrbitServerConfig) : HealthCheck {

    val nodeStatus: NodeStatus get() = localNodeInfo.info.nodeStatus

    private val logger = KotlinLogging.logger {}

    private var shutdownLatch = AtomicReference()

    private val runtimePools = RuntimePools(
        cpuPool = config.cpuPool,
        ioPool = config.ioPool
    )

    private val runtimeScopes = RuntimeScopes(
        runtimePools = runtimePools,
        exceptionHandler = this::onUnhandledException
    )

    private val container = ComponentContainer()
    private val clock = config.clock

    private val grpcEndpoint by container.inject()
    private val localNodeInfo by container.inject()
    private val clusterManager by container.inject()
    private val nodeDirectory by container.inject()
    private val addressableDirectory by container.inject()

    private val pipeline by container.inject()
    private val remoteMeshNodeManager by container.inject()

    private val slowTick = Metrics.counter(Meters.Names.SlowTicks)
    private val tickTimer = Metrics.timer(Meters.Names.TickTimer)

    private val ticker = ConstantTicker(
        scope = runtimeScopes.cpuScope,
        targetTickRate = config.tickRate.toMillis(),
        clock = clock,
        logger = logger,
        exceptionHandler = this::onUnhandledException,
        autoStart = false,
        onTick = this::tick,
        onSlowTick = { slowTick.increment() }
    )

    init {
        container.configure {
            instance(this@OrbitServer)
            instance(config)
            instance(runtimePools)
            instance(runtimeScopes)
            instance(clock)
            instance(config.serverInfo)

            // Service
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()

            // Net
            singleton()

            // Pipeline
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()
            singleton()

            // Mesh
            singleton()
            singleton()
            singleton()
            singleton()
            externallyConfigured(config.nodeDirectory)
            externallyConfigured(config.addressableDirectory)
            externallyConfigured(config.meterRegistry)

            // Auth
            singleton()

            // Router
            singleton()

            // Hook to allow overriding container definitions
            config.containerOverrides(this)
        }

        Metrics.globalRegistry.add(container.resolve(MeterRegistry::class.java))

        Metrics.gauge(Meters.Names.NodeCount, nodeDirectory) { d -> runBlocking { d.entries().count().toDouble() } }
        Metrics.gauge(Meters.Names.AddressableCount, addressableDirectory) { runBlocking { it.count().toDouble() } }
    }

    fun start() = runtimeScopes.cpuScope.launch {
        logger.info("Starting Orbit server...")
        logger.info("Lease expirations: Addressable: ${config.addressableLeaseDuration.leaseDuration}s, Node: ${config.nodeLeaseDuration.leaseDuration}s")
        val (elapsed, _) = stopwatch(clock) {
            // Start the pipeline
            pipeline.start()

            // Setup  the local node information
            localNodeInfo.start()

            // Start tick
            ticker.start()

            // Start gRPC endpoint
            // We shouldn't do this until we're ready to serve traffic
            grpcEndpoint.start()

            // Flip status to active
            localNodeInfo.updateInfo {
                it.copy(nodeStatus = NodeStatus.ACTIVE)
            }

            // Acquire the latch
            if (config.acquireShutdownLatch) {
                ShutdownLatch().also {
                    shutdownLatch.set(it)
                    it.acquire()
                }
            }
        }

        logger.info("Orbit server started successfully in {}ms.", elapsed)
    }

    fun stop() = runtimeScopes.cpuScope.launch {
        logger.info("Stopping Orbit server...")
        val (elapsed, _) = stopwatch(clock) {
            // Flip status to draining
            localNodeInfo.updateInfo {
                it.copy(nodeStatus = NodeStatus.DRAINING)
            }

            // Stop gRPC
            val grpcEndpoint by container.inject()
            grpcEndpoint.stop()

            // Stop the tick
            ticker.stop()

            // Stop pipeline
            pipeline.stop()

            // Flip status to draining
            localNodeInfo.updateInfo {
                it.copy(nodeStatus = NodeStatus.STOPPED)
            }

            // Remove from node directory
            nodeDirectory.remove(localNodeInfo.info.id)

            // Release the latch
            shutdownLatch.get()?.release().also {
                shutdownLatch.set(null)
            }
        }

        logger.info("Orbit server stopped successfully in {}ms.", elapsed)
        Metrics.globalRegistry.remove(container.resolve(MeterRegistry::class.java))
    }

    internal suspend fun tick() {
        tickTimer.recordSuspended {
            localNodeInfo.tick()
            clusterManager.tick()
            nodeDirectory.tick()
            addressableDirectory.tick()
            remoteMeshNodeManager.tick()
        }
    }

    @Suppress("UNUSED_PARAMETER")
    private fun onUnhandledException(coroutineContext: CoroutineContext, throwable: Throwable) =
        onUnhandledException(throwable)

    private fun onUnhandledException(throwable: Throwable) {
        logger.error(throwable) { "Unhandled exception in Orbit." }
    }

    override suspend fun isHealthy(): Boolean = this.nodeStatus == NodeStatus.ACTIVE
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy