mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-02 04:42:09 +08:00
chore: sync dev and trim redundant backend tests
This commit is contained in:
@@ -343,6 +343,12 @@ impl CoreManager {
|
||||
|
||||
let run_path = Config::write_runtime_file(&yaml).await?;
|
||||
self.apply_config(run_path).await?;
|
||||
// Under the lifecycle lock, so a stop for exit waits for this write instead of outrunning it.
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _lifecycle = self.lifecycle_lock.lock().await;
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
}
|
||||
Ok(ValidationOutcome::Valid)
|
||||
}
|
||||
|
||||
|
||||
@@ -515,7 +515,13 @@ impl CoreManager {
|
||||
}
|
||||
|
||||
async fn stop_sidecar_after_proxy_clear(&self) -> Result<()> {
|
||||
run_sidecar_termination_transition(proxy_control::clear, || self.stop_core_by_sidecar_unprepared()).await
|
||||
let result =
|
||||
run_sidecar_termination_transition(proxy_control::clear, || self.stop_core_by_sidecar_unprepared()).await;
|
||||
#[cfg(target_os = "macos")]
|
||||
if matches!(*self.get_running_mode(), RunningMode::NotRunning) {
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub(super) async fn replace_service_core_with_config(&self, config_file: &Path) -> Result<()> {
|
||||
@@ -544,6 +550,8 @@ impl CoreManager {
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("cannot apply system proxy before core readiness").context(SysproxyFailure::CoreNotReady)
|
||||
})?;
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
// At login the app usually beats the network. Leave the write to the network watcher
|
||||
// rather than fail — only if the watcher is actually live; a user toggling while
|
||||
// offline still fails fast.
|
||||
@@ -615,6 +623,8 @@ impl CoreManager {
|
||||
tracing::Span::current().record("decision", tracing::field::debug(&startup));
|
||||
if matches!(startup, StartupDecision::Wait) {
|
||||
self.rollback_failed_start().await;
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
#[cfg(target_os = "windows")]
|
||||
self.try_sidecar_after_service_unavailable().await?;
|
||||
return Ok(());
|
||||
@@ -649,6 +659,8 @@ impl CoreManager {
|
||||
// A failed start must not leave a locally reported running mode.
|
||||
if result.is_err() {
|
||||
self.rollback_failed_start().await;
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -737,11 +749,17 @@ impl CoreManager {
|
||||
|
||||
async fn stop_core_unprepared_inner(&self) -> Result<()> {
|
||||
CLASH_LOGGER.clear_logs();
|
||||
match *self.get_running_mode() {
|
||||
let result = match *self.get_running_mode() {
|
||||
RunningMode::Service => self.stop_core_by_service().await,
|
||||
RunningMode::Sidecar => self.stop_core_by_sidecar_unprepared().await,
|
||||
RunningMode::NotRunning => Ok(()),
|
||||
};
|
||||
// A Sidecar whose exit wait failed is already marked stopped; a core still running keeps its DNS.
|
||||
#[cfg(target_os = "macos")]
|
||||
if matches!(*self.get_running_mode(), RunningMode::NotRunning) {
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
|
||||
@@ -393,6 +393,8 @@ impl CoreManager {
|
||||
self.set_job_handle(None);
|
||||
proxy_control::stop_guard().await;
|
||||
self.core_stopped();
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1784,12 +1784,18 @@ async fn recover_after_owner_loss_while_locked(reason: OwnerRecoveryReason) {
|
||||
);
|
||||
mark_service_unavailable_after_owner_loss(&RUN_STATE, reason);
|
||||
proxy_control::stop_guard().await;
|
||||
let policy = owner_recovery_policy(reason, cfg!(target_os = "macos"));
|
||||
// Clear while still in Service mode with the session: on macOS it routes through the helper.
|
||||
if owner_recovery_policy(reason, cfg!(target_os = "macos")).reset_system_proxy {
|
||||
if policy.reset_system_proxy {
|
||||
clear_proxy_after_owner_loss().await;
|
||||
}
|
||||
clear_active_service_session();
|
||||
CoreManager::global().core_stopped();
|
||||
// A displaced Service may still run TUN for its new owner, and DNS settings are system-wide.
|
||||
#[cfg(target_os = "macos")]
|
||||
if policy.reset_system_proxy {
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn clear_proxy_after_owner_loss() {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use serde_yaml_ng::{Mapping, Value};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::process::AsyncHandler;
|
||||
|
||||
macro_rules! revise {
|
||||
($map: expr, $key: expr, $val: expr) => {
|
||||
let ret_key = Value::String($key.into());
|
||||
@@ -46,22 +43,9 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
if ipv6_val && !dns_val.contains_key(Value::from("fake-ip-range6")) {
|
||||
revise!(dns_val, "fake-ip-range6", "2001:2::0/64");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
AsyncHandler::spawn(move || async move {
|
||||
crate::utils::resolve::dns::restore_public_dns().await;
|
||||
crate::utils::resolve::dns::set_public_dns("114.114.114.114".to_string()).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
revise!(config, "dns", dns_val);
|
||||
} else {
|
||||
#[cfg(target_os = "macos")]
|
||||
AsyncHandler::spawn(move || async move {
|
||||
crate::utils::resolve::dns::restore_public_dns().await;
|
||||
});
|
||||
}
|
||||
|
||||
revise!(tun_val, "enable", enable);
|
||||
|
||||
@@ -55,6 +55,19 @@ pub async fn restart_app() {
|
||||
|
||||
if !cleanup_result.core_stopped {
|
||||
handle::Handle::global().clear_is_exiting();
|
||||
// A failed stop may still have marked the core stopped, and exit cleanup skipped the DNS restore.
|
||||
// A core that kept running keeps its DNS, whatever an unapplied config draft says.
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let manager = CoreManager::global();
|
||||
let _lifecycle = manager.lifecycle_lock.lock().await;
|
||||
if matches!(
|
||||
*manager.get_running_mode(),
|
||||
crate::core::manager::RunningMode::NotRunning
|
||||
) {
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
}
|
||||
}
|
||||
handle::Handle::notice(
|
||||
NoticeStatus::AppRestartCoreStopFailed,
|
||||
cleanup_result.stop_error.unwrap_or_default(),
|
||||
|
||||
@@ -41,9 +41,6 @@ macro_rules! verge_registry {
|
||||
$( $(#[$cfg])* { select_field!(effects, $field, $rule); } )*
|
||||
effects
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
const VERGE_FIELDS: &[&str] = &[ $( $(#[$cfg])* stringify!($field), )* ];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -165,18 +162,6 @@ pub(super) fn clash_effects(patch: &serde_yaml_ng::Mapping) -> Effects {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn every_serialized_verge_field_is_registered() -> Result<(), serde_json::Error> {
|
||||
let fields: std::collections::BTreeMap<String, serde_json::Value> =
|
||||
serde_json::from_value(serde_json::to_value(IVerge::default())?)?;
|
||||
for field in fields.keys() {
|
||||
assert!(VERGE_FIELDS.contains(&field.as_str()), "unregistered field: {field}");
|
||||
}
|
||||
let unique: BTreeSet<_> = VERGE_FIELDS.iter().collect();
|
||||
assert_eq!(unique.len(), VERGE_FIELDS.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verge_patch(mutate: impl FnOnce(&mut IVerge)) -> IVerge {
|
||||
let mut patch = IVerge::default();
|
||||
mutate(&mut patch);
|
||||
|
||||
@@ -82,16 +82,21 @@ where
|
||||
|
||||
async fn restore_dns_after_core_stop() -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
// Outlasts the script's own deadline, so a hung restore is killed with its process group before exit.
|
||||
match timeout(
|
||||
Duration::from_millis(1000),
|
||||
crate::utils::resolve::dns::DNS_SCRIPT_TIMEOUT + Duration::from_secs(1),
|
||||
crate::utils::resolve::dns::restore_public_dns(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
Ok(Ok(())) => {
|
||||
logging!(debug, Type::Window, "DNS设置已恢复");
|
||||
true
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
logging!(warn, Type::Window, "恢复DNS设置失败: {err:#}");
|
||||
false
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(warn, Type::Window, "恢复DNS设置超时");
|
||||
false
|
||||
@@ -129,6 +134,19 @@ pub async fn quit() -> clash_verge_signal::ShutdownOutcome {
|
||||
|
||||
if should_abort_exit_after_cleanup(cleanup_result.core_stopped) {
|
||||
handle::Handle::global().clear_is_exiting();
|
||||
// A failed stop may still have marked the core stopped, and exit cleanup skipped the DNS restore.
|
||||
// A core that kept running keeps its DNS, whatever an unapplied config draft says.
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let manager = CoreManager::global();
|
||||
let _lifecycle = manager.lifecycle_lock.lock().await;
|
||||
if matches!(
|
||||
*manager.get_running_mode(),
|
||||
crate::core::manager::RunningMode::NotRunning
|
||||
) {
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
}
|
||||
}
|
||||
handle::Handle::notice(
|
||||
NoticeStatus::AppQuitCoreStopFailed,
|
||||
cleanup_result.stop_error.unwrap_or_default(),
|
||||
@@ -179,6 +197,9 @@ pub async fn clean_async() -> CleanupResult {
|
||||
pub async fn clean_session_ending_best_effort() -> CleanupResult {
|
||||
#[cfg(target_os = "windows")]
|
||||
let stop_timeout = Duration::from_secs(2);
|
||||
// TODO(macOS): a DNS script already blocked when the session ends holds the lifecycle lock for up to
|
||||
// DNS_SCRIPT_TIMEOUT, past this deadline; the stop is cancelled, the DNS restore skipped, and the
|
||||
// script can still write after the app exits.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let stop_timeout = Duration::from_secs(3);
|
||||
|
||||
|
||||
@@ -1,123 +1,149 @@
|
||||
use anyhow::Context as _;
|
||||
use anyhow::{Context as _, bail, ensure};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use serde_yaml_ng::{Mapping, Value};
|
||||
use std::{
|
||||
io::Read as _,
|
||||
os::unix::process::CommandExt as _,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
const DNS_STATE_FILE: &str = ".original_dns.txt";
|
||||
const DNS_BACKUP_DIR: &str = ".original_dns";
|
||||
// Any address routed into TUN works, since dns-hijack answers it; the value only matters if left behind.
|
||||
const PLACEHOLDER_DNS: &str = "114.114.114.114";
|
||||
// Scripts run under lifecycle_lock, and networksetup waits for other preference writers without a limit.
|
||||
pub(crate) const DNS_SCRIPT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
static DNS_LOCK: Mutex<()> = Mutex::const_new(());
|
||||
|
||||
fn dns_state_dir() -> anyhow::Result<PathBuf> {
|
||||
// The DNS scripts persist .original_dns.txt relative to their working directory.
|
||||
// The DNS scripts persist their backups relative to their working directory.
|
||||
let dir = crate::utils::dirs::app_home_dir()?;
|
||||
std::fs::create_dir_all(&dir).with_context(|| format!("failed to create DNS state directory {}", dir.display()))?;
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn restore_dns_state_dir(resource_dir: &Path, state_dir: PathBuf) -> PathBuf {
|
||||
if resource_dir.join(DNS_STATE_FILE).exists() {
|
||||
if resource_dir.join(DNS_STATE_FILE).exists() || resource_dir.join(DNS_BACKUP_DIR).exists() {
|
||||
resource_dir.to_path_buf()
|
||||
} else {
|
||||
state_dir
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_public_dns(dns_server: String) {
|
||||
use crate::{core::handle, utils::dirs};
|
||||
use tauri_plugin_shell::ShellExt as _;
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
|
||||
logging!(debug, Type::Config, "try to set system dns");
|
||||
let resource_dir = match dirs::app_resources_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
logging!(error, Type::Config, "Failed to get resource directory: {}", e);
|
||||
return;
|
||||
}
|
||||
// Without the placeholder, a LAN resolver is reached outside TUN and fake-ip is bypassed.
|
||||
fn needs_public_dns(config: &Mapping) -> bool {
|
||||
let section = |name: &str| config.get(name).and_then(Value::as_mapping);
|
||||
let enabled = |name: &str| {
|
||||
section(name)
|
||||
.and_then(|map| map.get("enable"))
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
};
|
||||
let script = resource_dir.join("set_dns.sh");
|
||||
if !script.exists() {
|
||||
logging!(error, Type::Config, "DNS script not found: {}", script.display());
|
||||
enabled("tun")
|
||||
&& enabled("dns")
|
||||
&& section("dns")
|
||||
.and_then(|dns| dns.get("enhanced-mode"))
|
||||
.and_then(Value::as_str)
|
||||
== Some("fake-ip")
|
||||
}
|
||||
|
||||
/// Sets the placeholder DNS while the running config needs it, and restores the system DNS otherwise.
|
||||
pub async fn sync_public_dns() {
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{CoreManager, handle::Handle, manager::RunningMode},
|
||||
};
|
||||
|
||||
let _guard = DNS_LOCK.lock().await;
|
||||
// Exit cleanup restores DNS on its own budget; a sync queued behind it must not undo that.
|
||||
if Handle::global().is_exiting() {
|
||||
return;
|
||||
}
|
||||
let script = script.to_string_lossy().into_owned();
|
||||
let state_dir = match dns_state_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
logging!(error, Type::Config, "Failed to get DNS state directory: {e:#}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match app_handle
|
||||
.shell()
|
||||
.command("bash")
|
||||
.args([script, dns_server])
|
||||
.current_dir(state_dir)
|
||||
.status()
|
||||
.await
|
||||
{
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
logging!(info, Type::Config, "set system dns successfully");
|
||||
} else {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
logging!(error, Type::Config, "set system dns failed: {code}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Config, "set system dns failed: {err}");
|
||||
}
|
||||
let core_running = !matches!(*CoreManager::global().get_running_mode(), RunningMode::NotRunning);
|
||||
let runtime = Config::runtime().await.latest_arc();
|
||||
if core_running && runtime.config.as_ref().is_some_and(needs_public_dns) {
|
||||
set_public_dns().await;
|
||||
} else {
|
||||
let _ = unset_public_dns().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn restore_public_dns() {
|
||||
use crate::{core::handle, utils::dirs};
|
||||
use tauri_plugin_shell::ShellExt as _;
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
pub async fn restore_public_dns() -> anyhow::Result<()> {
|
||||
let _guard = DNS_LOCK.lock().await;
|
||||
unset_public_dns().await
|
||||
}
|
||||
|
||||
async fn set_public_dns() {
|
||||
logging!(debug, Type::Config, "try to set system dns");
|
||||
let result = async {
|
||||
let script = crate::utils::dirs::app_resources_dir()?.join("set_dns.sh");
|
||||
run_dns_script(&script, dns_state_dir()?).await
|
||||
}
|
||||
.await;
|
||||
match result {
|
||||
Ok(()) => logging!(info, Type::Config, "set system dns successfully"),
|
||||
Err(err) => logging!(error, Type::Config, "set system dns failed: {err:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn unset_public_dns() -> anyhow::Result<()> {
|
||||
logging!(debug, Type::Config, "try to unset system dns");
|
||||
let resource_dir = match dirs::app_resources_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
logging!(error, Type::Config, "Failed to get resource directory: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let script = resource_dir.join("unset_dns.sh");
|
||||
if !script.exists() {
|
||||
logging!(error, Type::Config, "DNS script not found: {}", script.display());
|
||||
return;
|
||||
let result = async {
|
||||
let resource_dir = crate::utils::dirs::app_resources_dir()?;
|
||||
let state_dir = restore_dns_state_dir(&resource_dir, dns_state_dir()?);
|
||||
run_dns_script(&resource_dir.join("unset_dns.sh"), state_dir).await
|
||||
}
|
||||
let script = script.to_string_lossy().into_owned();
|
||||
let state_dir = match dns_state_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
logging!(error, Type::Config, "Failed to get DNS state directory: {e:#}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let state_dir = restore_dns_state_dir(&resource_dir, state_dir);
|
||||
match app_handle
|
||||
.shell()
|
||||
.command("bash")
|
||||
.args([script])
|
||||
.current_dir(state_dir)
|
||||
.status()
|
||||
.await
|
||||
{
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
logging!(info, Type::Config, "unset system dns successfully");
|
||||
} else {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
logging!(error, Type::Config, "unset system dns failed: {code}");
|
||||
.await;
|
||||
match &result {
|
||||
Ok(()) => logging!(info, Type::Config, "unset system dns successfully"),
|
||||
Err(err) => logging!(error, Type::Config, "unset system dns failed: {err:#}"),
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// On timeout the script's whole process group is killed, so no networksetup it started writes late.
|
||||
async fn run_dns_script(script: &Path, dir: PathBuf) -> anyhow::Result<()> {
|
||||
ensure!(script.exists(), "DNS script not found: {}", script.display());
|
||||
let mut child = Command::new("bash")
|
||||
.arg(script)
|
||||
.arg(PLACEHOLDER_DNS)
|
||||
.current_dir(dir)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::piped())
|
||||
.process_group(0)
|
||||
.spawn()
|
||||
.with_context(|| format!("failed to run {}", script.display()))?;
|
||||
crate::process::AsyncHandler::spawn_blocking(move || {
|
||||
let deadline = Instant::now() + DNS_SCRIPT_TIMEOUT;
|
||||
let status = loop {
|
||||
if let Some(status) = child.try_wait()? {
|
||||
break status;
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
unsafe { libc::killpg(libc::pid_t::try_from(child.id())?, libc::SIGKILL) };
|
||||
child.wait()?;
|
||||
bail!("timed out after {} s", DNS_SCRIPT_TIMEOUT.as_secs());
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
};
|
||||
let mut stderr = String::new();
|
||||
if let Some(mut pipe) = child.stderr.take() {
|
||||
pipe.read_to_string(&mut stderr)?;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Config, "unset system dns failed: {err}");
|
||||
}
|
||||
}
|
||||
ensure!(
|
||||
status.success(),
|
||||
"exit code {}: {}",
|
||||
status.code().unwrap_or(-1),
|
||||
stderr.trim()
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.context("DNS script task failed")?
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -125,7 +151,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::expect_used)]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn restore_dns_state_dir_defaults_to_app_data_but_honors_legacy_file() {
|
||||
use super::restore_dns_state_dir;
|
||||
|
||||
@@ -137,6 +162,11 @@ mod tests {
|
||||
assert_eq!(restore_dns_state_dir(&resource_dir, state_dir.clone()), state_dir);
|
||||
|
||||
std::fs::write(resource_dir.join(".original_dns.txt"), "empty").expect("create legacy DNS state");
|
||||
assert_eq!(restore_dns_state_dir(&resource_dir, state_dir.clone()), resource_dir);
|
||||
|
||||
// A migrated legacy backup whose restore failed must still be retried there.
|
||||
std::fs::remove_file(resource_dir.join(".original_dns.txt")).expect("remove legacy DNS state");
|
||||
std::fs::create_dir(resource_dir.join(".original_dns")).expect("create migrated DNS state");
|
||||
assert_eq!(restore_dns_state_dir(&resource_dir, state_dir), resource_dir);
|
||||
|
||||
std::fs::remove_dir_all(root).expect("remove test directory");
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::{
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use clash_verge_signal;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod dns;
|
||||
mod scheme;
|
||||
pub(crate) mod window;
|
||||
@@ -103,7 +104,7 @@ pub async fn resolve_reset_async() -> Result<(), anyhow::Error> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use dns::restore_public_dns;
|
||||
restore_public_dns().await;
|
||||
restore_public_dns().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -218,20 +218,4 @@ mod tests {
|
||||
assert_eq!(result, Err("terminal"));
|
||||
assert_eq!(visited, [0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backoff_keeps_the_attempt_budget_and_exponential_cap() {
|
||||
let policy = RetryPolicy {
|
||||
attempts: NonZeroUsize::MIN.saturating_add(4),
|
||||
backoff: Backoff::Exponential {
|
||||
base: Duration::from_millis(10),
|
||||
cap: Duration::from_millis(25),
|
||||
},
|
||||
jitter: false,
|
||||
};
|
||||
assert_eq!(
|
||||
policy.delays().collect::<Vec<_>>(),
|
||||
[10, 20, 25, 25].map(Duration::from_millis)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user