0.34.6_repo_cleanup

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 09:31:15 +02:00
1 parent ce1a90397c
commit 133d2f0f91
273 files changed
+5969 -6926

No files matched your search

-8
View File
@@ -1,16 +1,8 @@
impl App {
/// Re-read configured public certificate/key files and publish a new
/// immutable TLS/routing snapshot without changing desired configuration
/// or its SQLite revision. Existing connections continue on their current
/// TLS session; new H1/H2/H3 handshakes use the refreshed certificates.
pub async fn reload_certificates(self: &Arc<Self>) -> Result<u64, ApiError> {
self.reload_certificate_snapshot(None).await
}
/// Reload an externally managed/manual certificate by id. The immutable TLS
/// resolver is rebuilt as one validated snapshot, so the requested pair is
/// re-read atomically with the other configured pairs while desired
/// configuration and its revision remain unchanged.
pub async fn reload_certificate(
self: &Arc<Self>,
certificate_id: &str,
-3
View File
@@ -214,9 +214,6 @@ impl App {
Arc::downgrade(&app),
app.shutdown.clone(),
));
// High request rates create short-lived allocations in Hyper/Tokio and
// cache fill paths. glibc may retain already-free arenas as RSS. Trim
// them periodically without evicting live cache objects or connections.
let trim_shutdown = app.shutdown.clone();
let trim_app = Arc::downgrade(&app);
app.tunnels.spawn(async move {
-2
View File
@@ -1,6 +1,4 @@
impl App {
/// Rebuild only the effective routing snapshot from the latest Docker observations.
/// Desired configuration and its SQLite revision remain unchanged.
pub async fn reconcile_docker(self: &Arc<Self>) -> Result<(), ApiError> {
let _lock = self.update_lock.lock().await;
let previous = self.config.load_full();
-5
View File
@@ -1,5 +1,3 @@
//! Explicit JSON configuration backing file. SQLite remains the revision/audit journal.
//! File replacement is atomic on the containing filesystem and refuses symlink targets.
use crate::config::{Config, MAX_CONFIG_BYTES};
use anyhow::{bail, ensure, Context, Result};
use sha2::{Digest, Sha256};
@@ -39,8 +37,6 @@ pub fn read(path: &Path) -> Result<(Config, Fingerprint)> {
Ok((config, Sha256::digest(&contents).into()))
}
// Only compare fingerprints of bytes, never raw secrets in logs. Caller serializes
// saves under App::update_lock; conflicts with out-of-band file edits fail closed.
pub fn save(path: &Path, expected: Fingerprint, config: &Config) -> Result<Fingerprint> {
let (_, actual) = read(path)?;
if actual != expected {
@@ -73,7 +69,6 @@ pub fn save(path: &Path, expected: Fingerprint, config: &Config) -> Result<Finge
let mut file = options.open(&temp)?;
file.write_all(&bytes)?;
file.sync_all()?;
// An external writer may have changed the source while the temp was prepared.
if read(path)?.1 != expected {
bail!("configuration file changed during save");
}
-3
View File
@@ -87,8 +87,6 @@ mod cache_revision_tests {
} else {
protocol_only.vhosts[0].http3_enabled = !base.vhosts[0].http3_enabled;
}
// The effective configuration really changes, but the cache key
// does not. This must hold for both toggling directions.
assert_ne!(
(protocol_only.vhosts[0].http2_enabled, protocol_only.vhosts[0].http3_enabled),
(base.vhosts[0].http2_enabled, base.vhosts[0].http3_enabled),
@@ -144,7 +142,6 @@ mod cache_revision_tests {
let affected_id = base.vhosts[0].id.clone();
assert!(matches!(cache_invalidation(&base, &vhost), CacheInvalidation::Scopes(ids) if ids == vec![affected_id]));
// A content change must not invalidate another independent vhost.
let mut multiple = base.clone();
let mut another = multiple.vhosts[0].clone();
another.id = "other-scope".into();
-14
View File
@@ -83,14 +83,10 @@ enum CacheInvalidation {
Scopes(Vec<String>),
}
/// Global compatibility identity for HOT/COLD cache entries. Vhost-specific
/// representation changes use individual scope generations and COLD fingerprints.
/// Connection-pool/LB knobs do not enter either identity.
fn cache_revision(config: &config::Config) -> u64 {
let mut cache = config.cache.clone();
cache.memory_preset = None;
cache.cold_preset = None;
// These are request admission or response diagnostics, never stored objects.
cache.bypass_query = false;
cache.bypass_prefixes.clear();
cache.http_invalidation_allowed_cidrs.clear();
@@ -104,11 +100,8 @@ fn cache_revision(config: &config::Config) -> u64 {
transform.preset = None;
transform.memory_budget_bytes = 0;
transform.max_inflight = 0;
// Web services and FPM profiles can change the bytes served by a location.
// Until their dependency graph is split into scopes, invalidate globally.
let mut web_services = config.web_services.clone();
for service in &mut web_services {
// Kernel write strategy never changes the HTTP representation.
service.sendfile = crate::web_server::SendfileMode::Off;
}
cache_digest(&(
@@ -127,8 +120,6 @@ fn cache_digest<T: serde::Serialize>(value: &T) -> u64 {
u64::from_be_bytes(digest[..8].try_into().expect("sha256 prefix"))
}
/// Only effective origin/content policies contribute to this scope identity.
/// A vhost keepalive or load-balancer control save must retain both cache tiers.
fn vhost_cache_revision(vhost: &crate::vhost::Vhost) -> u64 {
let mut view = vhost.clone();
view.websockets = false;
@@ -159,9 +150,6 @@ fn vhost_cache_revision(vhost: &crate::vhost::Vhost) -> u64 {
view.proxy_timeouts = Default::default();
view.frontend.listener_ids.clear();
view.cache.expose_only_hits = None;
// The complete LB policy (keepalive, recycling, weights, retries, health,
// affinity and queues) governs connection selection, not cache identity.
// Sticky authentication checks and bypass still run before the cache lookup.
view.load_balancing = Default::default();
for member in &mut view.backends {
member.weight = 1;
@@ -196,8 +184,6 @@ fn cache_invalidation(previous: &config::Config, next: &config::Config) -> Cache
.filter(|(id, revision)| new.get(*id) != Some(*revision))
.map(|(id, _)| id.clone())
.collect();
// New scopes do not have previous cache objects, but include the set for
// completeness if an ID is ever reused within a single config transaction.
changed.extend(new.keys().filter(|id| !old.contains_key(*id)).cloned());
changed.sort();
changed.dedup();
-18
View File
@@ -7,8 +7,6 @@ impl App {
*self.config_file.lock().await = Some((path, hash));
}
/// Reads an operator-edited JSON file and publishes it through the same
/// validation, history and hot-apply path as a panel save.
pub async fn reload_from_file(
self: &Arc<Self>,
expected_revision: u64,
@@ -71,11 +69,9 @@ impl App {
));
}
/// Drain detached writers after the listeners and their handlers have stopped.
pub async fn drain_updates(&self) {
let _all_slots = self.update_slots.clone().acquire_many_owned(8).await;
}
/// The detached task completes persistence + publication even if the HTTP caller disconnects.
pub async fn update(self: &Arc<Self>, update: Update) -> Result<config::Envelope, ApiError> {
self.update_as(update, "local".into()).await
}
@@ -161,9 +157,6 @@ impl App {
"configuration changed; reload before saving".into(),
));
}
// Explicit --config is the source of truth. A panel save writes the
// JSON file before recording its revision in SQLite, while a manual
// reload imports the operator-edited file without overwriting it.
let file_updated_hash =
if let Some((path, last_hash)) = this.config_file.lock().await.as_ref().cloned() {
let config = prepared.envelope.config.clone();
@@ -212,8 +205,6 @@ impl App {
}
};
if !matches!(&persisted, Ok(true)) {
// The database commit failed. Best-effort file rollback only
// for saves (never for an operator-authored manual reload).
if reload_hash.is_none() {
if let Some((path, _, new_hash)) = &file_updated_hash {
let path = path.clone();
@@ -226,7 +217,6 @@ impl App {
.await
{
Ok(Ok(restored_hash)) => {
// The old document may be reformatted, so use its new fingerprint.
*this.config_file.lock().await = Some((path, restored_hash));
}
result => tracing::error!(
@@ -254,12 +244,7 @@ impl App {
if let Some((path, _, new_hash)) = file_updated_hash.as_ref() {
*this.config_file.lock().await = Some((path.clone(), *new_hash));
}
// No await between successful commit and publication. Cache identity is
// intentionally decoupled from the configuration revision, so unrelated
// control-plane saves do not invalidate hot/cold objects.
let envelope = prepared.envelope.clone();
// After commit, reject late cache tickets from the old routing
// snapshot before any scope purge. Existing unaffected objects stay.
this.cache.set_control_revision(envelope.revision);
match invalidation {
CacheInvalidation::All(cache_revision) => {
@@ -272,9 +257,6 @@ impl App {
}
CacheInvalidation::None => {}
}
// A COLD entry is usable after restart only if its scope still has
// the same origin/content policy. Publish the fingerprints after
// the cache generation barriers but before the routing snapshot.
this.cache
.replace_scope_revisions(vhost_cache_revisions(&envelope.config));
this.cache.configure(envelope.config.cache.clone());