20260803 死repo传输层黑名单+daemon健康检查摘除(#141)
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::parallel::dead_repo_blacklist::DeadRepoBlacklist;
|
||||
use crate::parallel::types::{
|
||||
InFlightRepoEntry, RepoDedupKey, RepoIdentity, RepoKey, RepoRequester, RepoRuntimeState,
|
||||
RepoSyncResultEnvelope, RepoSyncResultKind, RepoSyncResultRef, RepoSyncTask, RepoTaskState,
|
||||
RepoTransportMode, RepoTransportResultEnvelope, RepoTransportResultKind, RepoTransportTask,
|
||||
RepoTransportErrorClass, RepoTransportMode, RepoTransportResultEnvelope,
|
||||
RepoTransportResultKind, RepoTransportTask,
|
||||
};
|
||||
use crate::policy::SyncPreference;
|
||||
use crate::report::Warning;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RepoRequestAction {
|
||||
@@ -74,6 +77,9 @@ pub struct TransportStateTables {
|
||||
rsync_failure_probe_inflight: HashMap<String, String>,
|
||||
rsync_failure_scope_reachable: HashSet<String>,
|
||||
runtime_records: HashMap<RepoIdentity, RepoRuntimeRecord>,
|
||||
/// Frozen run-start snapshot of the dead-repo blacklist (#141). Entries
|
||||
/// admitted during this run only take effect from the next run.
|
||||
dead_repo_blacklist: Option<DeadRepoBlacklist>,
|
||||
}
|
||||
|
||||
impl TransportStateTables {
|
||||
@@ -81,6 +87,44 @@ impl TransportStateTables {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn set_dead_repo_blacklist(&mut self, blacklist: DeadRepoBlacklist) {
|
||||
self.dead_repo_blacklist = Some(blacklist);
|
||||
}
|
||||
|
||||
fn dead_repo_terminal_envelope(
|
||||
identity: &RepoIdentity,
|
||||
requesters: &[RepoRequester],
|
||||
rsync_scope_uri: &str,
|
||||
rsync_failure_scope_uri: Option<String>,
|
||||
) -> RepoTransportResultEnvelope {
|
||||
let first_requester = requesters
|
||||
.first()
|
||||
.expect("blacklist terminal record must keep at least one requester");
|
||||
RepoTransportResultEnvelope {
|
||||
dedup_key: RepoDedupKey::RsyncScope {
|
||||
rsync_scope_uri: rsync_scope_uri.to_string(),
|
||||
},
|
||||
rsync_failure_scope_uri,
|
||||
repo_identity: identity.clone(),
|
||||
mode: RepoTransportMode::Rsync,
|
||||
tal_id: first_requester.tal_id.clone(),
|
||||
rir_id: first_requester.rir_id.clone(),
|
||||
timing_ms: 0,
|
||||
result: RepoTransportResultKind::Failed {
|
||||
detail: format!(
|
||||
"dead repo blacklist: rsync transport persistently unreachable: {}",
|
||||
identity.rsync_base_uri
|
||||
),
|
||||
warnings: vec![Warning::new(format!(
|
||||
"dead_repo_blacklist_skip_all: repository {} skipped after repeated transport failures",
|
||||
identity.rsync_base_uri
|
||||
))
|
||||
.with_context(identity.rsync_base_uri.clone())],
|
||||
error_class: RepoTransportErrorClass::Unknown,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runtime_record(&self, identity: &RepoIdentity) -> Option<&RepoRuntimeRecord> {
|
||||
self.runtime_records.get(identity)
|
||||
}
|
||||
@@ -205,6 +249,82 @@ impl TransportStateTables {
|
||||
};
|
||||
}
|
||||
|
||||
// Dead-repo blacklist fast paths (#141), consulted only for fresh
|
||||
// identities; existing records already encode their terminal state.
|
||||
if let Some(blacklist) = self.dead_repo_blacklist.as_ref() {
|
||||
let rrdp_wanted = sync_preference == SyncPreference::RrdpThenRsync
|
||||
&& identity.notification_uri.is_some();
|
||||
let rrdp_dead = rrdp_wanted
|
||||
&& identity
|
||||
.notification_uri
|
||||
.as_deref()
|
||||
.map(|uri| blacklist.is_blacklisted(RepoTransportMode::Rrdp, uri))
|
||||
.unwrap_or(false);
|
||||
let rsync_dead = blacklist.is_blacklisted(
|
||||
RepoTransportMode::Rsync,
|
||||
identity.rsync_base_uri.as_str(),
|
||||
);
|
||||
if rsync_dead && (!rrdp_wanted || rrdp_dead) {
|
||||
crate::progress_log::emit(
|
||||
"dead_repo_blacklist_skip_all",
|
||||
serde_json::json!({
|
||||
"repo_key_notification_uri": identity.notification_uri,
|
||||
"repo_key_rsync_base_uri": identity.rsync_base_uri,
|
||||
}),
|
||||
);
|
||||
let envelope = Self::dead_repo_terminal_envelope(
|
||||
&identity,
|
||||
std::slice::from_ref(&requester),
|
||||
&rsync_scope_uri,
|
||||
rsync_failure_scope_uri.clone(),
|
||||
);
|
||||
self.runtime_records.insert(
|
||||
identity.clone(),
|
||||
RepoRuntimeRecord {
|
||||
identity,
|
||||
state: RepoRuntimeState::FailedTerminal,
|
||||
rrdp_notification_key: None,
|
||||
rsync_scope_key: rsync_scope_uri,
|
||||
rsync_failure_scope_key: rsync_failure_scope_uri,
|
||||
requesters: vec![requester],
|
||||
validation_time,
|
||||
priority,
|
||||
last_success: None,
|
||||
terminal_failure: Some(envelope.clone()),
|
||||
},
|
||||
);
|
||||
return TransportRequestAction::ReusedTerminalFailure(envelope);
|
||||
}
|
||||
if rrdp_dead {
|
||||
crate::progress_log::emit(
|
||||
"dead_repo_blacklist_skip_rrdp",
|
||||
serde_json::json!({
|
||||
"repo_key_notification_uri": identity.notification_uri,
|
||||
"repo_key_rsync_base_uri": identity.rsync_base_uri,
|
||||
}),
|
||||
);
|
||||
let mut action = self.register_rsync_request(
|
||||
identity,
|
||||
requester,
|
||||
validation_time,
|
||||
priority,
|
||||
rsync_scope_uri,
|
||||
rsync_failure_scope_uri,
|
||||
);
|
||||
// A blacklisted rrdp transport is a known-persistent failure,
|
||||
// so the rsync fallback must keep the short retry profile
|
||||
// (#141): the prefetch layer records no real rrdp failure in
|
||||
// skipped runs, which would otherwise silently drop the task
|
||||
// back to the default 15s window from the second skipped run
|
||||
// on. The coordinator wrapper only ever sets the flag to true,
|
||||
// so setting it here survives registration.
|
||||
if let TransportRequestAction::Enqueue(task) = &mut action {
|
||||
task.retry_short_timeout = true;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
if sync_preference == SyncPreference::RrdpThenRsync {
|
||||
if let Some(notification_uri) = identity.notification_uri.clone() {
|
||||
if let Some(entry) = self.rrdp_inflight.get_mut(¬ification_uri) {
|
||||
@@ -512,6 +632,7 @@ impl TransportStateTables {
|
||||
rsync_failure_by_scope: &HashMap<String, RepoTransportResultEnvelope>,
|
||||
rsync_failure_probe_inflight: &mut HashMap<String, String>,
|
||||
rsync_failure_scope_reachable: &HashSet<String>,
|
||||
dead_repo_blacklist: Option<&DeadRepoBlacklist>,
|
||||
follow_up_tasks: &mut Vec<RepoTransportTask>,
|
||||
) {
|
||||
let rsync_scope_uri = record.rsync_scope_key.clone();
|
||||
@@ -548,6 +669,34 @@ impl TransportStateTables {
|
||||
}
|
||||
}
|
||||
|
||||
// Dead-repo blacklist (#141): the rsync transport of this repo is
|
||||
// persistently unreachable; terminate instantly instead of enqueueing
|
||||
// another doomed fetch (rrdp was already attempted or skipped).
|
||||
if let Some(blacklist) = dead_repo_blacklist {
|
||||
if blacklist.is_blacklisted(
|
||||
RepoTransportMode::Rsync,
|
||||
record.identity.rsync_base_uri.as_str(),
|
||||
) {
|
||||
crate::progress_log::emit(
|
||||
"dead_repo_blacklist_skip_all",
|
||||
serde_json::json!({
|
||||
"repo_key_notification_uri": record.identity.notification_uri,
|
||||
"repo_key_rsync_base_uri": record.identity.rsync_base_uri,
|
||||
"after_rrdp_failure": true,
|
||||
}),
|
||||
);
|
||||
let envelope = Self::dead_repo_terminal_envelope(
|
||||
&record.identity,
|
||||
&record.requesters,
|
||||
&rsync_scope_uri,
|
||||
record.rsync_failure_scope_key.clone(),
|
||||
);
|
||||
record.state = RepoRuntimeState::FailedTerminal;
|
||||
record.terminal_failure = Some(envelope);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let first_requester = record
|
||||
.requesters
|
||||
.first()
|
||||
@@ -666,6 +815,7 @@ impl TransportStateTables {
|
||||
&self.rsync_failure_by_scope,
|
||||
&mut self.rsync_failure_probe_inflight,
|
||||
&self.rsync_failure_scope_reachable,
|
||||
self.dead_repo_blacklist.as_ref(),
|
||||
&mut follow_up_tasks,
|
||||
);
|
||||
}
|
||||
@@ -718,6 +868,7 @@ impl TransportStateTables {
|
||||
&self.rsync_failure_by_scope,
|
||||
&mut self.rsync_failure_probe_inflight,
|
||||
&self.rsync_failure_scope_reachable,
|
||||
self.dead_repo_blacklist.as_ref(),
|
||||
&mut follow_up_tasks,
|
||||
);
|
||||
}
|
||||
@@ -783,6 +934,7 @@ impl TransportStateTables {
|
||||
&self.rsync_failure_by_scope,
|
||||
&mut self.rsync_failure_probe_inflight,
|
||||
&self.rsync_failure_scope_reachable,
|
||||
self.dead_repo_blacklist.as_ref(),
|
||||
&mut follow_up_tasks,
|
||||
);
|
||||
}
|
||||
@@ -814,7 +966,7 @@ fn reusable_rsync_failure_scope(result: &RepoTransportResultEnvelope) -> Option<
|
||||
}
|
||||
}
|
||||
|
||||
fn is_host_level_rsync_failure(detail: &str) -> bool {
|
||||
pub(crate) fn is_host_level_rsync_failure(detail: &str) -> bool {
|
||||
let lower = detail.to_ascii_lowercase();
|
||||
lower.contains("timeout waiting for daemon connection")
|
||||
|| lower.contains("failed to connect")
|
||||
@@ -1462,6 +1614,7 @@ mod transport_tests {
|
||||
result: RepoTransportResultKind::Failed {
|
||||
detail: "rrdp timeout".to_string(),
|
||||
warnings: Vec::new(),
|
||||
error_class: crate::parallel::types::RepoTransportErrorClass::Unknown,
|
||||
},
|
||||
},
|
||||
time::OffsetDateTime::UNIX_EPOCH,
|
||||
@@ -1567,6 +1720,7 @@ mod transport_tests {
|
||||
result: RepoTransportResultKind::Failed {
|
||||
detail: "rrdp timeout".to_string(),
|
||||
warnings: Vec::new(),
|
||||
error_class: crate::parallel::types::RepoTransportErrorClass::Unknown,
|
||||
},
|
||||
},
|
||||
time::OffsetDateTime::UNIX_EPOCH,
|
||||
@@ -1597,6 +1751,7 @@ mod transport_tests {
|
||||
result: RepoTransportResultKind::Failed {
|
||||
detail: "rsync error: timeout waiting for daemon connection".to_string(),
|
||||
warnings: Vec::new(),
|
||||
error_class: crate::parallel::types::RepoTransportErrorClass::Unknown,
|
||||
},
|
||||
},
|
||||
time::OffsetDateTime::UNIX_EPOCH,
|
||||
@@ -1659,6 +1814,7 @@ mod transport_tests {
|
||||
result: RepoTransportResultKind::Failed {
|
||||
detail: "rsync timeout".to_string(),
|
||||
warnings: Vec::new(),
|
||||
error_class: crate::parallel::types::RepoTransportErrorClass::Unknown,
|
||||
},
|
||||
},
|
||||
time::OffsetDateTime::UNIX_EPOCH,
|
||||
@@ -1920,6 +2076,7 @@ mod transport_tests {
|
||||
result: RepoTransportResultKind::Failed {
|
||||
detail: "rsync file digest mismatch after download".to_string(),
|
||||
warnings: Vec::new(),
|
||||
error_class: crate::parallel::types::RepoTransportErrorClass::Unknown,
|
||||
},
|
||||
},
|
||||
time::OffsetDateTime::UNIX_EPOCH,
|
||||
@@ -1972,6 +2129,7 @@ mod transport_tests {
|
||||
result: RepoTransportResultKind::Failed {
|
||||
detail: "rsync error: failed to connect to daemon".to_string(),
|
||||
warnings: Vec::new(),
|
||||
error_class: crate::parallel::types::RepoTransportErrorClass::Unknown,
|
||||
},
|
||||
},
|
||||
time::OffsetDateTime::UNIX_EPOCH,
|
||||
@@ -2031,6 +2189,7 @@ mod transport_tests {
|
||||
result: RepoTransportResultKind::Failed {
|
||||
detail: "temporary failure in name resolution".to_string(),
|
||||
warnings: Vec::new(),
|
||||
error_class: crate::parallel::types::RepoTransportErrorClass::Unknown,
|
||||
},
|
||||
},
|
||||
time::OffsetDateTime::UNIX_EPOCH,
|
||||
|
||||
Reference in New Issue
Block a user