mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-14 23:02:14 +08:00
perf(enhance): cut fixed config-generation cost
Every settings toggle reparsed the profile and chain files, ran four Boa interpreters, serialized the runtime config twice, and re-ran mihomo -t over bytes the core had just accepted. Builtin and template scripts now run without Boa, custom scripts bind the config through JSON.parse and take ownership instead of per-script clones, the config serializes once, and mihomo -t is skipped unless the bytes, core build, or geo assets changed. Heavy parses and serializations moved to spawn_blocking. Large-subscription toggles drop from hundreds of milliseconds to ~140ms with no new retained state.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use super::{CoreManager, PROFILE_SELECTIONS_PENDING_COMMIT, RunningMode};
|
||||
use crate::core::service::StageRequest;
|
||||
use crate::{
|
||||
config::{Config, ConfigType, IProfiles, runtime::IRuntime},
|
||||
config::{Config, IProfiles, runtime::IRuntime},
|
||||
constants::timing,
|
||||
core::{
|
||||
handle,
|
||||
@@ -299,12 +299,16 @@ impl CoreManager {
|
||||
|
||||
/// Validates and applies the caller's transaction, committing only on success.
|
||||
async fn validate_and_apply(&self, transaction: DraftTransaction<'_>) -> Result<ValidationOutcome> {
|
||||
let outcome = CoreConfigValidator::global().validate_config_outcome().await?;
|
||||
// One serialization feeds check and run files; the core never applies unvalidated bytes.
|
||||
let yaml = Config::runtime_config_yaml().await?;
|
||||
let outcome = CoreConfigValidator::global()
|
||||
.validate_config_outcome_with(&yaml)
|
||||
.await?;
|
||||
if !outcome.is_valid() {
|
||||
return Ok(outcome);
|
||||
}
|
||||
|
||||
let run_path = Config::generate_file(ConfigType::Run).await?;
|
||||
let run_path = Config::write_runtime_file(&yaml).await?;
|
||||
self.apply_config(run_path).await?;
|
||||
transaction.commit();
|
||||
Ok(ValidationOutcome::Valid)
|
||||
|
||||
@@ -104,7 +104,7 @@ impl CoreManager {
|
||||
handle::Handle::app_handle()
|
||||
.mihomo()
|
||||
.update_socket_path(dirs::path_to_str(&sidecar_ipc)?.to_owned())?;
|
||||
let config_file = Config::generate_file(crate::config::ConfigType::Run).await?;
|
||||
let config_file = Config::generate_file().await?;
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
let clash_core = Config::verge().await.latest_arc().get_valid_clash_core();
|
||||
let config_dir = dirs::app_home_dir()?;
|
||||
@@ -255,7 +255,7 @@ impl CoreManager {
|
||||
pub(super) async fn start_core_by_service(&self) -> Result<()> {
|
||||
self.core_starting();
|
||||
let service_ipc = dirs::ipc_path()?;
|
||||
let config_file = Config::generate_file(crate::config::ConfigType::Run).await?;
|
||||
let config_file = Config::generate_file().await?;
|
||||
handle::Handle::app_handle()
|
||||
.mihomo()
|
||||
.update_socket_path(dirs::path_to_str(&service_ipc)?.to_owned())?;
|
||||
|
||||
@@ -5,7 +5,7 @@ use serde_yaml_ng::Value;
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
const GEO_ASSETS: &[&str] = &[
|
||||
pub(crate) const GEO_ASSETS: &[&str] = &[
|
||||
"Country.mmdb",
|
||||
"geoip.dat",
|
||||
"geosite.dat",
|
||||
|
||||
@@ -4,17 +4,49 @@ use serde::Serialize;
|
||||
use smartstring::alias::String;
|
||||
use std::{
|
||||
fmt,
|
||||
hash::{DefaultHasher, Hash as _, Hasher as _},
|
||||
sync::LazyLock,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use tauri_plugin_shell::ShellExt as _;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::config::{Config, ConfigType};
|
||||
use crate::config::Config;
|
||||
use crate::core::handle;
|
||||
use crate::singleton;
|
||||
use crate::utils::dirs;
|
||||
use crate::utils::{dirs, help};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
const SYNTAX_CHECK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
|
||||
static LAST_VALIDATED: LazyLock<parking_lot::Mutex<Option<u64>>> = LazyLock::new(|| parking_lot::Mutex::new(None));
|
||||
|
||||
async fn validation_fingerprint(yaml: &str) -> u64 {
|
||||
let core = Config::verge().await.latest_arc().get_valid_clash_core();
|
||||
let mut hasher = DefaultHasher::new();
|
||||
yaml.hash(&mut hasher);
|
||||
core.as_str().hash(&mut hasher);
|
||||
let binary = std::env::current_exe()
|
||||
.into_iter()
|
||||
.map(|exe| exe.with_file_name(format!("{}{}", core.as_str(), std::env::consts::EXE_SUFFIX)));
|
||||
// The test run reads geo databases from the app dir; a geo update must revalidate.
|
||||
let geo = dirs::app_home_dir().into_iter().flat_map(|dir| {
|
||||
super::runtime_bundle::GEO_ASSETS
|
||||
.iter()
|
||||
.map(move |asset| dir.join(asset))
|
||||
});
|
||||
for path in binary.chain(geo) {
|
||||
match std::fs::metadata(&path) {
|
||||
Ok(meta) => {
|
||||
meta.len().hash(&mut hasher);
|
||||
meta.modified().ok().hash(&mut hasher);
|
||||
}
|
||||
Err(_) => 0u8.hash(&mut hasher),
|
||||
}
|
||||
}
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub struct CoreConfigValidator {
|
||||
is_processing: AtomicBool,
|
||||
}
|
||||
@@ -223,28 +255,33 @@ impl CoreConfigValidator {
|
||||
};
|
||||
|
||||
logging!(debug, Type::Validate, "验证脚本文件: {}", path);
|
||||
let has_main =
|
||||
content.contains("function main") || content.contains("const main") || content.contains("let main");
|
||||
|
||||
use boa_engine::{Context, Source};
|
||||
// Boa parsing is pure CPU; keep it off the async worker, with a timeout.
|
||||
let syntax = crate::process::AsyncHandler::spawn_blocking(move || {
|
||||
use boa_engine::{Context, Source};
|
||||
|
||||
let mut context = Context::default();
|
||||
let _ = context.eval(Source::from_bytes(
|
||||
"var console = Object.freeze({
|
||||
log(...data){},
|
||||
info(...data){},
|
||||
error(...data){},
|
||||
debug(...data){},
|
||||
});",
|
||||
));
|
||||
let result = context.eval(Source::from_bytes(&content));
|
||||
let mut context = Context::default();
|
||||
let _ = context.eval(Source::from_bytes(
|
||||
"var console = Object.freeze({log(...data){},info(...data){},error(...data){},debug(...data){}});",
|
||||
));
|
||||
context
|
||||
.eval(Source::from_bytes(&content))
|
||||
.map(|_| ())
|
||||
.map_err(|err| err.to_string())
|
||||
});
|
||||
let result = match tokio::time::timeout(SYNTAX_CHECK_TIMEOUT, syntax).await {
|
||||
Ok(Ok(evaluated)) => evaluated,
|
||||
Ok(Err(join_err)) => Err(format!("syntax check task failed: {join_err}")),
|
||||
Err(_) => Err("syntax check timed out".to_owned()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(()) => {
|
||||
logging!(debug, Type::Validate, "脚本语法验证通过: {}", path);
|
||||
|
||||
if !content.contains("function main")
|
||||
&& !content.contains("const main")
|
||||
&& !content.contains("let main")
|
||||
{
|
||||
if !has_main {
|
||||
let error_msg = "Script must contain a main function";
|
||||
logging!(warn, Type::Validate, "脚本缺少main函数: {}", path);
|
||||
return Ok(ValidationOutcome::invalid_from_message(error_msg));
|
||||
@@ -371,7 +408,8 @@ impl CoreConfigValidator {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn validate_config_outcome(&self) -> Result<ValidationOutcome> {
|
||||
/// Skips the subprocess when these bytes were already accepted by the same core build.
|
||||
pub async fn validate_config_outcome_with(&self, yaml: &str) -> Result<ValidationOutcome> {
|
||||
if !self.try_start() {
|
||||
logging!(info, Type::Validate, "验证已在进行中,跳过新的验证请求");
|
||||
return Ok(ValidationOutcome::Busy);
|
||||
@@ -381,9 +419,20 @@ impl CoreConfigValidator {
|
||||
}
|
||||
logging!(info, Type::Validate, "生成临时配置文件用于验证");
|
||||
|
||||
let config_path = Config::generate_file(ConfigType::Check).await?;
|
||||
let config_path = dirs::path_to_str(&config_path)?;
|
||||
Self::validate_config_internal_outcome(config_path).await
|
||||
let fingerprint = validation_fingerprint(yaml).await;
|
||||
if *LAST_VALIDATED.lock() == Some(fingerprint) {
|
||||
logging!(info, Type::Validate, "配置与上次通过验证的字节一致,跳过内核检查");
|
||||
return Ok(ValidationOutcome::Valid);
|
||||
}
|
||||
|
||||
let check_path = dirs::app_home_dir()?.join(crate::constants::files::CHECK_CONFIG);
|
||||
help::save_yaml_str(&check_path, yaml).await?;
|
||||
let outcome = Self::validate_config_internal_outcome(dirs::path_to_str(&check_path)?).await?;
|
||||
if outcome.is_valid() {
|
||||
*LAST_VALIDATED.lock() = Some(fingerprint);
|
||||
let _ = fs::remove_file(&check_path).await;
|
||||
}
|
||||
Ok(outcome)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user