use pingora_core::server::{RunArgs, Server, configuration::ServerConf};
use tracing::info;
use super::RuntimeOptions;
pub struct PingoraServerRuntime {
server: Server,
}
impl std::fmt::Debug for PingoraServerRuntime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PingoraServerRuntime")
.field("threads", &self.server.configuration.threads)
.finish_non_exhaustive()
}
}
impl PingoraServerRuntime {
#[must_use]
pub fn new(config: &crate::config::Config) -> Self {
let opts = RuntimeOptions::from(&config.runtime);
let server = build_http_server(config.shutdown_timeout_secs, &opts);
Self { server }
}
pub fn server_mut(&mut self) -> &mut Server {
&mut self.server
}
pub fn run(self) -> ! {
self.server.run_forever()
}
pub fn run_with_args(self, args: RunArgs) {
self.server.run(args);
}
}
pub fn build_http_server(shutdown_timeout_secs: u64, runtime: &RuntimeOptions) -> Server {
let threads = resolve_thread_count(runtime.threads);
let conf = build_server_conf(shutdown_timeout_secs, threads, runtime);
let mut server = Server::new_with_opt_and_conf(None, conf);
server.bootstrap();
info!(
shutdown_timeout_secs, threads,
work_stealing = runtime.work_stealing,
upstream_ca_file = ?runtime.upstream_ca_file,
upstream_keepalive_pool_size = ?runtime.upstream_keepalive_pool_size,
"server configured"
);
server
}
fn build_server_conf(shutdown_timeout_secs: u64, threads: usize, runtime: &RuntimeOptions) -> ServerConf {
let mut conf = ServerConf {
grace_period_seconds: Some(shutdown_timeout_secs),
graceful_shutdown_timeout_seconds: Some(shutdown_timeout_secs),
threads,
work_stealing: runtime.work_stealing,
..ServerConf::default()
};
if let Some(pool_size) = runtime.upstream_keepalive_pool_size {
conf.upstream_keepalive_pool_size = pool_size;
}
apply_upstream_ca(&mut conf, runtime);
warn_unsupported_global_queue_interval(runtime);
conf
}
fn apply_upstream_ca(conf: &mut ServerConf, runtime: &RuntimeOptions) {
if let Some(ca_file) = &runtime.upstream_ca_file {
info!(ca_file, "setting global upstream CA file (replaces system trust store)");
conf.ca_file = Some(ca_file.clone());
}
}
fn warn_unsupported_global_queue_interval(runtime: &RuntimeOptions) {
if let Some(interval) = runtime.global_queue_interval {
tracing::warn!(
interval,
"global_queue_interval is set but has no effect: the async runtime is \
managed by Pingora, which does not expose this setting; the value is ignored"
);
}
}
fn resolve_thread_count(configured: usize) -> usize {
if configured == 0 {
std::thread::available_parallelism().map_or(1, std::num::NonZero::get)
} else {
configured
}
}
#[cfg(test)]
#[expect(clippy::allow_attributes, reason = "blanket test suppressions")]
#[allow(
clippy::expect_used,
clippy::unwrap_used,
reason = "tests use expect/unwrap for brevity"
)]
mod tests {
use super::*;
#[test]
fn build_http_server_returns_bootstrapped_server() {
let server = build_http_server(30, &RuntimeOptions::default());
assert_eq!(
server.configuration.grace_period_seconds,
Some(30),
"grace period should match shutdown timeout"
);
}
#[test]
fn build_http_server_with_explicit_threads() {
let runtime = RuntimeOptions {
threads: 4,
work_stealing: false,
..RuntimeOptions::default()
};
let server = build_http_server(10, &runtime);
assert_eq!(
server.configuration.threads, 4,
"thread count should match configured value"
);
assert!(!server.configuration.work_stealing, "work stealing should be disabled");
}
#[test]
fn stock_config_does_not_warn_about_global_queue_interval() {
let opts = RuntimeOptions::from(&crate::config::RuntimeConfig::default());
let (_conf, logs) = capture_warnings(|| build_server_conf(30, 1, &opts));
assert!(
!logs.contains("global_queue_interval"),
"a stock config must not warn about the no-op global_queue_interval knob: {logs}"
);
}
#[test]
fn explicit_global_queue_interval_warns_it_has_no_effect() {
let opts = RuntimeOptions {
global_queue_interval: Some(128),
..RuntimeOptions::default()
};
let (_conf, logs) = capture_warnings(|| build_server_conf(30, 1, &opts));
assert!(
logs.contains("global_queue_interval") && logs.contains("no effect"),
"explicitly setting global_queue_interval should warn it is ignored: {logs}"
);
}
fn capture_warnings<T, F: FnOnce() -> T>(func: F) -> (T, String) {
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct Buffer(Arc<Mutex<Vec<u8>>>);
impl std::io::Write for Buffer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().expect("buffer lock").extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let buffer = Buffer(Arc::new(Mutex::new(Vec::new())));
let writer = buffer.clone();
let subscriber = tracing_subscriber::fmt()
.with_writer(move || writer.clone())
.with_ansi(false)
.with_max_level(tracing::Level::WARN)
.finish();
let out = tracing::subscriber::with_default(subscriber, func);
let bytes = buffer.0.lock().expect("buffer lock").clone();
(out, String::from_utf8_lossy(&bytes).into_owned())
}
}