Implement UNIX sockets
Initial implementation done in https://gitlab.com/famedly/conduit/-/merge_requests/507, *substantially* reworked, corrected, improved by infamous <ehuff007@gmail.com>, and few parts done by me. Co-authored-by: infamous <ehuff007@gmail.com> Signed-off-by: girlbossceo <june@girlboss.ceo>
This commit is contained in:
parent
3bfdae795d
commit
42efc9deaf
8 changed files with 186 additions and 40 deletions
|
@ -2,11 +2,13 @@ use std::{
|
|||
collections::BTreeMap,
|
||||
fmt,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use figment::Figment;
|
||||
use ruma::{OwnedServerName, RoomVersionId};
|
||||
use serde::{de::IgnoredAny, Deserialize};
|
||||
use tracing::warn;
|
||||
use tracing::{error, warn};
|
||||
|
||||
mod proxy;
|
||||
|
||||
|
@ -19,7 +21,9 @@ pub struct Config {
|
|||
#[serde(default = "default_port")]
|
||||
pub port: u16,
|
||||
pub tls: Option<TlsConfig>,
|
||||
|
||||
pub unix_socket_path: Option<PathBuf>,
|
||||
#[serde(default = "default_unix_socket_perms")]
|
||||
pub unix_socket_perms: u32,
|
||||
pub server_name: OwnedServerName,
|
||||
#[serde(default = "default_database_backend")]
|
||||
pub database_backend: String,
|
||||
|
@ -102,7 +106,7 @@ impl Config {
|
|||
.keys()
|
||||
.filter(|key| DEPRECATED_KEYS.iter().any(|s| s == key))
|
||||
{
|
||||
warn!("Config parameter {} is deprecated", key);
|
||||
warn!("Config parameter \"{}\" is deprecated.", key);
|
||||
was_deprecated = true;
|
||||
}
|
||||
|
||||
|
@ -110,6 +114,19 @@ impl Config {
|
|||
warn!("Read conduit documentation and check your configuration if any new configuration parameters should be adjusted");
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks the presence of the `address` and `unix_socket_path` keys in the raw_config, exiting the process if both keys were detected.
|
||||
pub fn error_dual_listening(&self, raw_config: Figment) -> Result<(), ()> {
|
||||
let check_address = raw_config.find_value("address");
|
||||
let check_unix_socket = raw_config.find_value("unix_socket_path");
|
||||
|
||||
if check_address.is_ok() && check_unix_socket.is_ok() {
|
||||
error!("TOML keys \"address\" and \"unix_socket_path\" were both defined. Please specify only one option.");
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Config {
|
||||
|
@ -223,6 +240,10 @@ fn default_port() -> u16 {
|
|||
8000
|
||||
}
|
||||
|
||||
fn default_unix_socket_perms() -> u32 {
|
||||
660
|
||||
}
|
||||
|
||||
fn default_database_backend() -> String {
|
||||
"sqlite".to_owned()
|
||||
}
|
||||
|
|
|
@ -1071,33 +1071,46 @@ impl KeyValueDatabase {
|
|||
let timer_interval =
|
||||
Duration::from_secs(services().globals.config.cleanup_second_interval as u64);
|
||||
|
||||
fn perform_cleanup() {
|
||||
let start = Instant::now();
|
||||
if let Err(e) = services().globals.cleanup() {
|
||||
error!(target: "database-cleanup", "Ran into an error during cleanup: {}", e);
|
||||
} else {
|
||||
debug!(target: "database-cleanup", "Finished cleanup in {:#?}.", start.elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut i = interval(timer_interval);
|
||||
#[cfg(unix)]
|
||||
let mut s = signal(SignalKind::hangup()).unwrap();
|
||||
let mut hangup = signal(SignalKind::hangup()).unwrap();
|
||||
let mut ctrl_c = signal(SignalKind::interrupt()).unwrap();
|
||||
let mut terminate = signal(SignalKind::terminate()).unwrap();
|
||||
|
||||
loop {
|
||||
#[cfg(unix)]
|
||||
tokio::select! {
|
||||
_ = i.tick() => {
|
||||
debug!("cleanup: Timer ticked");
|
||||
debug!(target: "database-cleanup", "Timer ticked");
|
||||
}
|
||||
_ = s.recv() => {
|
||||
debug!("cleanup: Received SIGHUP");
|
||||
_ = hangup.recv() => {
|
||||
debug!(target: "database-cleanup","Received SIGHUP");
|
||||
}
|
||||
_ = ctrl_c.recv() => {
|
||||
debug!(target: "database-cleanup", "Received Ctrl+C, performing last cleanup");
|
||||
perform_cleanup();
|
||||
}
|
||||
_ = terminate.recv() => {
|
||||
debug!(target: "database-cleanup","Received SIGTERM, performing last cleanup");
|
||||
perform_cleanup();
|
||||
}
|
||||
};
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
i.tick().await;
|
||||
debug!("cleanup: Timer ticked")
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
if let Err(e) = services().globals.cleanup() {
|
||||
error!("cleanup: Errored: {}", e);
|
||||
} else {
|
||||
debug!("cleanup: Finished in {:?}", start.elapsed());
|
||||
debug!(target: "database-cleanup", "Timer ticked")
|
||||
}
|
||||
perform_cleanup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
89
src/main.rs
89
src/main.rs
|
@ -8,7 +8,10 @@
|
|||
#![allow(clippy::suspicious_else_formatting)]
|
||||
#![deny(clippy::dbg_macro)]
|
||||
|
||||
use std::{future::Future, io, net::SocketAddr, sync::atomic, time::Duration};
|
||||
use std::{
|
||||
fs::Permissions, future::Future, io, net::SocketAddr, os::unix::fs::PermissionsExt,
|
||||
sync::atomic, time::Duration,
|
||||
};
|
||||
|
||||
use axum::{
|
||||
extract::{DefaultBodyLimit, FromRequestParts, MatchedPath},
|
||||
|
@ -26,6 +29,8 @@ use http::{
|
|||
header::{self, HeaderName},
|
||||
Method, StatusCode, Uri,
|
||||
};
|
||||
use hyper::Server;
|
||||
use hyperlocal::SocketIncoming;
|
||||
use ruma::api::{
|
||||
client::{
|
||||
error::{Error as RumaError, ErrorBody, ErrorKind},
|
||||
|
@ -33,7 +38,7 @@ use ruma::api::{
|
|||
},
|
||||
IncomingRequest,
|
||||
};
|
||||
use tokio::signal;
|
||||
use tokio::{net::UnixListener, signal, sync::oneshot};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::{
|
||||
cors::{self, CorsLayer},
|
||||
|
@ -43,6 +48,8 @@ use tower_http::{
|
|||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
|
||||
use tokio::sync::oneshot::Sender;
|
||||
|
||||
pub use conduit::*; // Re-export everything from the library crate
|
||||
|
||||
#[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))]
|
||||
|
@ -69,12 +76,10 @@ async fn main() {
|
|||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("It looks like your config is invalid. The following error occurred: {e}");
|
||||
std::process::exit(1);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
config.warn_deprecated();
|
||||
|
||||
let log = format!("{},ruma_state_res=error,_=off,sled=off", config.log);
|
||||
|
||||
if config.allow_jaeger {
|
||||
|
@ -135,11 +140,15 @@ async fn main() {
|
|||
#[cfg(unix)]
|
||||
maximize_fd_limit().expect("should be able to increase the soft limit to the hard limit");
|
||||
|
||||
config.warn_deprecated();
|
||||
if let Err(_) = config.error_dual_listening(raw_config) {
|
||||
return;
|
||||
};
|
||||
|
||||
info!("Loading database");
|
||||
if let Err(error) = KeyValueDatabase::load_or_create(config).await {
|
||||
error!(?error, "The database couldn't be loaded or created");
|
||||
|
||||
std::process::exit(1);
|
||||
return;
|
||||
};
|
||||
let config = &services().globals.config;
|
||||
|
||||
|
@ -200,26 +209,57 @@ async fn run_server() -> io::Result<()> {
|
|||
|
||||
let app = routes().layer(middlewares).into_make_service();
|
||||
let handle = ServerHandle::new();
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
|
||||
tokio::spawn(shutdown_signal(handle.clone()));
|
||||
tokio::spawn(shutdown_signal(handle.clone(), tx));
|
||||
|
||||
match &config.tls {
|
||||
Some(tls) => {
|
||||
let conf = RustlsConfig::from_pem_file(&tls.certs, &tls.key).await?;
|
||||
let server = bind_rustls(addr, conf).handle(handle).serve(app);
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||
|
||||
server.await?
|
||||
if let Some(path) = &config.unix_socket_path {
|
||||
if path.exists() {
|
||||
warn!(
|
||||
"UNIX socket path {:#?} already exists (unclean shutdown?), attempting to remove it.",
|
||||
path.display()
|
||||
);
|
||||
tokio::fs::remove_file(&path).await?;
|
||||
}
|
||||
None => {
|
||||
let server = bind(addr).handle(handle).serve(app);
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||
tokio::fs::create_dir_all(path.parent().unwrap()).await?;
|
||||
|
||||
server.await?
|
||||
let socket_perms = config.unix_socket_perms.to_string();
|
||||
let octal_perms = u32::from_str_radix(&socket_perms, 8).unwrap();
|
||||
|
||||
let listener = UnixListener::bind(path.clone()).unwrap();
|
||||
tokio::fs::set_permissions(path, Permissions::from_mode(octal_perms))
|
||||
.await
|
||||
.unwrap();
|
||||
let socket = SocketIncoming::from_listener(listener);
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||
let server = Server::builder(socket).serve(app);
|
||||
let graceful = server.with_graceful_shutdown(async {
|
||||
rx.await.ok();
|
||||
});
|
||||
|
||||
if let Err(e) = graceful.await {
|
||||
error!("Server error: {:?}", e);
|
||||
}
|
||||
} else {
|
||||
match &config.tls {
|
||||
Some(tls) => {
|
||||
let conf = RustlsConfig::from_pem_file(&tls.certs, &tls.key).await?;
|
||||
let server = bind_rustls(addr, conf).handle(handle).serve(app);
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||
server.await?
|
||||
}
|
||||
None => {
|
||||
let server = bind(addr).handle(handle).serve(app);
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||
server.await?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,7 +479,7 @@ fn routes() -> Router {
|
|||
.fallback(not_found)
|
||||
}
|
||||
|
||||
async fn shutdown_signal(handle: ServerHandle) {
|
||||
async fn shutdown_signal(handle: ServerHandle, tx: Sender<()>) -> Result<()> {
|
||||
let ctrl_c = async {
|
||||
signal::ctrl_c()
|
||||
.await
|
||||
|
@ -471,6 +511,9 @@ async fn shutdown_signal(handle: ServerHandle) {
|
|||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Stopping]);
|
||||
tx.send(()).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn not_found(uri: Uri) -> impl IntoResponse {
|
||||
|
|
|
@ -379,9 +379,26 @@ impl Service {
|
|||
&self.config.well_known_client
|
||||
}
|
||||
|
||||
pub fn unix_socket_path(&self) -> &Option<PathBuf> {
|
||||
&self.config.unix_socket_path
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
self.shutdown.store(true, atomic::Ordering::Relaxed);
|
||||
// On shutdown
|
||||
|
||||
if self.unix_socket_path().is_some() {
|
||||
match &self.unix_socket_path() {
|
||||
Some(path) => {
|
||||
std::fs::remove_file(path.to_owned()).unwrap();
|
||||
}
|
||||
None => error!(
|
||||
"Unable to remove socket file at {:?} during shutdown.",
|
||||
&self.unix_socket_path()
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
info!(target: "shutdown-sync", "Received shutdown notification, notifying sync helpers...");
|
||||
services().globals.rotate.fire();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue