mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
174 lines
5.0 KiB
Rust
174 lines
5.0 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
use crate::config::{Config, DeliveryConfig};
|
|
use crate::delivery;
|
|
use crate::metrics::Metrics;
|
|
use crate::relay;
|
|
use crate::relay_consent::{self, RelayConsentStore};
|
|
use crate::rpc::RpcClient;
|
|
use crate::tokens::TokenCache;
|
|
use crate::vendor;
|
|
use axum::Router;
|
|
use fluxer_common::secret::SecretString;
|
|
use fluxer_svc::shutdown::{DEFAULT_DRAIN_TIMEOUT, drain_with_timeout, wait_for_shutdown};
|
|
use fluxer_svc::transport::NatsTransport;
|
|
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::time::Duration;
|
|
use tokio::net::TcpListener;
|
|
use tokio::sync::{oneshot, watch};
|
|
use tokio::task::JoinHandle;
|
|
use tracing::info;
|
|
|
|
const JOB_DRAIN_TIMEOUT: Duration = Duration::from_secs(25);
|
|
|
|
pub struct Sidecar {
|
|
metrics: Arc<Metrics>,
|
|
serving: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl Sidecar {
|
|
pub fn new(metrics: Arc<Metrics>) -> Self {
|
|
Self {
|
|
metrics,
|
|
serving: Arc::new(AtomicBool::new(false)),
|
|
}
|
|
}
|
|
|
|
pub fn router(&self) -> Router {
|
|
let metrics = self.metrics.clone();
|
|
fluxer_svc::server::ops_router(self.serving.clone(), Arc::new(move || metrics.render()))
|
|
}
|
|
|
|
pub fn set_serving(&self, serving: bool) {
|
|
self.serving.store(serving, Ordering::SeqCst);
|
|
}
|
|
}
|
|
|
|
pub struct Serving {
|
|
stop: oneshot::Sender<()>,
|
|
served: JoinHandle<std::io::Result<()>>,
|
|
}
|
|
|
|
impl Serving {
|
|
pub async fn stop(self) {
|
|
let _ = self.stop.send(());
|
|
drain_with_timeout(
|
|
async {
|
|
let _ = self.served.await;
|
|
},
|
|
DEFAULT_DRAIN_TIMEOUT,
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
pub async fn serve(addr: SocketAddr, router: Router) -> anyhow::Result<Serving> {
|
|
let listener = TcpListener::bind(addr).await?;
|
|
let (stop, stopped) = oneshot::channel::<()>();
|
|
let served = tokio::spawn(async move {
|
|
axum::serve(
|
|
listener,
|
|
router.into_make_service_with_connect_info::<SocketAddr>(),
|
|
)
|
|
.with_graceful_shutdown(async move {
|
|
let _ = stopped.await;
|
|
})
|
|
.await
|
|
});
|
|
Ok(Serving { stop, served })
|
|
}
|
|
|
|
pub struct AppState {
|
|
pub(crate) cfg: DeliveryConfig,
|
|
pub(crate) metrics: Arc<Metrics>,
|
|
pub(crate) sidecar: Arc<Sidecar>,
|
|
pub(crate) relay_consent: RelayConsentStore,
|
|
pub(crate) rpc: RpcClient,
|
|
pub(crate) http: reqwest::Client,
|
|
pub(crate) web_push_http: reqwest::Client,
|
|
pub(crate) apns_http: reqwest::Client,
|
|
pub(crate) tokens: TokenCache,
|
|
pub(crate) draining: watch::Sender<bool>,
|
|
}
|
|
|
|
impl AppState {
|
|
pub(crate) fn try_new(cfg: DeliveryConfig) -> anyhow::Result<Self> {
|
|
let metrics = Arc::new(Metrics::new());
|
|
let relay_consent = RelayConsentStore::new(cfg.relay_consent_accepted);
|
|
metrics.record_relay_consent_accepted(relay_consent.accepted());
|
|
let http = vendor::http_client()?;
|
|
Ok(Self {
|
|
rpc: RpcClient::new(&cfg.rpc, http.clone(), Arc::clone(&metrics)),
|
|
relay_consent,
|
|
sidecar: Arc::new(Sidecar::new(Arc::clone(&metrics))),
|
|
web_push_http: vendor::web_push_http_client(&cfg.private_hosts)?,
|
|
apns_http: vendor::apns_http_client()?,
|
|
tokens: TokenCache::new(),
|
|
draining: watch::Sender::new(false),
|
|
cfg,
|
|
metrics,
|
|
http,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub async fn run(cfg: Config) -> anyhow::Result<()> {
|
|
match cfg {
|
|
Config::Delivery(cfg) => run_delivery(*cfg).await,
|
|
Config::Relay(cfg) => relay::run(*cfg).await,
|
|
}
|
|
}
|
|
|
|
async fn run_delivery(cfg: DeliveryConfig) -> anyhow::Result<()> {
|
|
let state = Arc::new(AppState::try_new(cfg)?);
|
|
let addr = state.cfg.bind_addr;
|
|
let serving = serve(addr, state.sidecar.router()).await?;
|
|
info!(%addr, vapid_subject = state.cfg.vapid.email, "push sidecar listening");
|
|
|
|
let transport = NatsTransport::connect(
|
|
&state.cfg.nats.url,
|
|
state.cfg.nats.auth_token.as_ref().map(SecretString::expose),
|
|
)
|
|
.await?;
|
|
|
|
state.sidecar.set_serving(true);
|
|
|
|
let subscriber = tokio::spawn({
|
|
let transport = transport.clone();
|
|
let state = Arc::clone(&state);
|
|
async move {
|
|
relay_consent::run_subscriber(
|
|
transport,
|
|
&state.rpc,
|
|
&state.relay_consent,
|
|
&state.metrics,
|
|
relay_consent::RECONCILE_INTERVAL,
|
|
)
|
|
.await
|
|
}
|
|
});
|
|
let jobs = tokio::spawn(delivery::run_job_subscribers(transport, Arc::clone(&state)));
|
|
|
|
wait_for_shutdown().await;
|
|
state.sidecar.set_serving(false);
|
|
subscriber.abort();
|
|
drain_jobs(&state, jobs, JOB_DRAIN_TIMEOUT).await;
|
|
serving.stop().await;
|
|
Ok(())
|
|
}
|
|
|
|
async fn drain_jobs(state: &AppState, jobs: JoinHandle<()>, timeout: Duration) {
|
|
state.draining.send_replace(true);
|
|
let running = jobs.abort_handle();
|
|
drain_with_timeout(
|
|
async {
|
|
let _ = jobs.await;
|
|
},
|
|
timeout,
|
|
)
|
|
.await;
|
|
running.abort();
|
|
}
|