lavina/src/main.rs

154 lines
5.0 KiB
Rust
Raw Normal View History

2023-09-30 23:50:04 +00:00
mod http;
2023-01-19 17:18:41 +00:00
2023-01-19 14:25:52 +00:00
use std::future::Future;
2023-09-22 15:24:36 +00:00
use std::path::Path;
2023-01-19 14:25:52 +00:00
2023-09-22 15:24:36 +00:00
use clap::Parser;
2023-01-19 14:25:52 +00:00
use figment::providers::Format;
use figment::{providers::Toml, Figment};
use opentelemetry::global::set_text_map_propagator;
use opentelemetry::KeyValue;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::propagation::TraceContextPropagator;
use opentelemetry_sdk::trace::{BatchConfig, RandomIdGenerator, Sampler};
use opentelemetry_sdk::{runtime, Resource};
use opentelemetry_semantic_conventions::resource::SERVICE_NAME;
use opentelemetry_semantic_conventions::SCHEMA_URL;
use prometheus::Registry as MetricsRegistry;
2023-01-19 14:25:52 +00:00
use serde::Deserialize;
use tracing_opentelemetry::OpenTelemetryLayer;
use tracing_subscriber::fmt::Subscriber;
use tracing_subscriber::prelude::*;
2023-01-19 14:25:52 +00:00
use lavina_core::prelude::*;
2023-09-30 23:12:11 +00:00
use lavina_core::repo::Storage;
use lavina_core::LavinaCore;
2023-01-19 14:25:52 +00:00
#[derive(Deserialize, Debug)]
struct ServerConfig {
2023-09-30 23:50:04 +00:00
telemetry: http::ServerConfig,
irc: projection_irc::ServerConfig,
xmpp: projection_xmpp::ServerConfig,
2023-09-30 23:12:11 +00:00
storage: lavina_core::repo::StorageConfig,
cluster: lavina_core::clustering::ClusterConfig,
tracing: Option<TracingConfig>,
}
#[derive(Deserialize, Debug)]
struct TracingConfig {
endpoint: String,
service_name: String,
2023-01-19 14:25:52 +00:00
}
2023-09-22 15:24:36 +00:00
#[derive(Parser)]
struct CliArgs {
#[arg(long)]
2023-09-30 23:50:04 +00:00
config: Box<Path>,
2023-09-22 15:24:36 +00:00
}
2023-01-19 14:25:52 +00:00
fn load_config() -> Result<ServerConfig> {
2023-09-22 15:24:36 +00:00
let args = CliArgs::parse();
let raw_config = Figment::from(Toml::file(args.config));
2023-01-19 14:25:52 +00:00
let config: ServerConfig = raw_config.extract()?;
Ok(config)
}
#[tokio::main]
async fn main() -> Result<()> {
let sleep = ctrl_c()?;
let config = load_config()?;
set_up_logging(&config.tracing)?;
2023-01-27 20:43:20 +00:00
tracing::info!("Booting up");
tracing::info!("Loaded config: {config:?}");
2023-02-10 17:09:29 +00:00
let ServerConfig {
telemetry: telemetry_config,
irc: irc_config,
xmpp: xmpp_config,
2023-07-07 13:09:24 +00:00
storage: storage_config,
cluster: cluster_config,
tracing: _,
2023-02-10 17:09:29 +00:00
} = config;
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
let mut metrics = MetricsRegistry::new();
2023-07-07 13:09:24 +00:00
let storage = Storage::open(storage_config).await?;
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
let core = LavinaCore::new(&mut metrics, cluster_config, storage).await?;
let telemetry_terminator = http::launch(telemetry_config, metrics.clone(), core.clone()).await?;
let irc = projection_irc::launch(irc_config, core.clone(), metrics.clone()).await?;
let xmpp = projection_xmpp::launch(xmpp_config, core.clone(), metrics.clone()).await?;
2023-01-19 14:25:52 +00:00
tracing::info!("Started");
sleep.await;
2023-01-19 14:25:52 +00:00
tracing::info!("Begin shutdown");
xmpp.terminate().await?;
2023-02-07 15:21:00 +00:00
irc.terminate().await?;
telemetry_terminator.terminate().await?;
core: separate the model from the logic implementation (#66) This separates the core in two layers – the model objects and the `LavinaCore` service. Service is responsible for implementing the application logic and exposing it as core's public API to projections, while the model objects will be independent of each other and responsible only for managing and owning in-memory data. The model objects include: 1. `Storage` – the open connection to the SQLite DB. 2. `PlayerRegistry` – creates, stores refs to, and stops player actors. 3. `RoomRegistry` – manages active rooms. 4. `DialogRegistry` – manages active dialogs. 5. `Broadcasting` – manages subscriptions of players to rooms on remote cluster nodes. 6. `LavinaClient` – manages HTTP connections to remote cluster nodes. 7. `ClusterMetadata` – read-only configuration of the cluster metadata, i.e. allocation of entities to nodes. As a result: 1. Model objects will be fully independent of each other, e.g. it's no longer necessary to provide a `Storage` to all registries, or to provide `PlayerRegistry` and `DialogRegistry` to each other. 2. Model objects will no longer be `Arc`-wrapped; instead the whole `Services` object will be `Arc`ed and provided to projections. 3. The public API of `lavina-core` will be properly delimited by the APIs of `LavinaCore`, `PlayerConnection` and so on. 4. `LavinaCore` and `PlayerConnection` will also contain APIs of all features, unlike it was previously with `RoomRegistry` and `DialogRegistry`. This is unfortunate, but it could be improved in future. Reviewed-on: https://git.vilunov.me/lavina/lavina/pulls/66
2024-05-13 14:32:45 +00:00
let storage = core.shutdown().await;
storage.close().await;
2023-01-19 14:25:52 +00:00
tracing::info!("Shutdown complete");
Ok(())
}
2023-07-30 16:59:33 +00:00
#[cfg(windows)]
fn ctrl_c() -> Result<impl Future<Output = ()>> {
use tokio::signal::windows::*;
let chan = ctrl_c()?;
async fn recv(mut chan: CtrlC) {
let _ = chan.recv().await;
}
Ok(recv(chan))
}
#[cfg(unix)]
2023-01-19 14:25:52 +00:00
fn ctrl_c() -> Result<impl Future<Output = ()>> {
use tokio::signal::unix::*;
let chan = signal(SignalKind::interrupt())?;
async fn recv(mut chan: Signal) {
let _ = chan.recv().await;
}
Ok(recv(chan))
}
fn set_up_logging(tracing_config: &Option<TracingConfig>) -> Result<()> {
let subscriber = tracing_subscriber::registry().with(tracing_subscriber::fmt::layer());
let targets = {
use std::{env, str::FromStr};
use tracing_subscriber::filter::Targets;
match env::var("RUST_LOG") {
Ok(var) => Targets::from_str(&var)
.map_err(|e| {
eprintln!("Ignoring `RUST_LOG={:?}`: {}", var, e);
})
.unwrap_or_default(),
Err(env::VarError::NotPresent) => Targets::new().with_default(Subscriber::DEFAULT_MAX_LEVEL),
Err(e) => {
eprintln!("Ignoring `RUST_LOG`: {}", e);
Targets::new().with_default(Subscriber::DEFAULT_MAX_LEVEL)
}
}
};
if let Some(config) = tracing_config {
let trace_config = opentelemetry_sdk::trace::Config::default()
.with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(1.0))))
.with_id_generator(RandomIdGenerator::default())
.with_resource(Resource::from_schema_url(
[KeyValue::new(SERVICE_NAME, config.service_name.to_string())],
SCHEMA_URL,
));
let trace_exporter = opentelemetry_otlp::new_exporter().tonic().with_endpoint(&config.endpoint);
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_trace_config(trace_config)
.with_batch_config(BatchConfig::default())
.with_exporter(trace_exporter)
.install_batch(runtime::Tokio)?;
let subscriber = subscriber.with(OpenTelemetryLayer::new(tracer));
set_text_map_propagator(TraceContextPropagator::new());
targets.with_subscriber(subscriber).try_init()?;
} else {
targets.with_subscriber(subscriber).try_init()?;
}
2023-01-19 14:25:52 +00:00
Ok(())
}