mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-02 21:12:13 +08:00
Compare commits
2
Commits
dev
...
b3502ea46a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3502ea46a | ||
|
|
86cada4cff |
Generated
-10
@@ -1119,7 +1119,6 @@ dependencies = [
|
|||||||
"clash-verge-limiter",
|
"clash-verge-limiter",
|
||||||
"clash-verge-logging",
|
"clash-verge-logging",
|
||||||
"clash-verge-media-unlock",
|
"clash-verge-media-unlock",
|
||||||
"clash-verge-signal",
|
|
||||||
"clash_verge_logger",
|
"clash_verge_logger",
|
||||||
"clash_verge_service_ipc",
|
"clash_verge_service_ipc",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
@@ -1234,15 +1233,6 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clash-verge-signal"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"clash-verge-logging",
|
|
||||||
"log",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clash_verge_logger"
|
name = "clash_verge_logger"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|||||||
+8
-4
@@ -4,7 +4,6 @@ members = [
|
|||||||
"crates/clash-verge-draft",
|
"crates/clash-verge-draft",
|
||||||
"crates/clash-verge-media-unlock",
|
"crates/clash-verge-media-unlock",
|
||||||
"crates/clash-verge-logging",
|
"crates/clash-verge-logging",
|
||||||
"crates/clash-verge-signal",
|
|
||||||
"crates/tauri-plugin-clash-verge-sysinfo",
|
"crates/tauri-plugin-clash-verge-sysinfo",
|
||||||
"crates/clash-verge-i18n",
|
"crates/clash-verge-i18n",
|
||||||
"crates/clash-verge-limiter",
|
"crates/clash-verge-limiter",
|
||||||
@@ -50,7 +49,6 @@ split-debuginfo = "unpacked"
|
|||||||
clash-verge-draft = { path = "crates/clash-verge-draft" }
|
clash-verge-draft = { path = "crates/clash-verge-draft" }
|
||||||
clash-verge-media-unlock = { path = "crates/clash-verge-media-unlock" }
|
clash-verge-media-unlock = { path = "crates/clash-verge-media-unlock" }
|
||||||
clash-verge-logging = { path = "crates/clash-verge-logging" }
|
clash-verge-logging = { path = "crates/clash-verge-logging" }
|
||||||
clash-verge-signal = { path = "crates/clash-verge-signal" }
|
|
||||||
clash-verge-i18n = { path = "crates/clash-verge-i18n" }
|
clash-verge-i18n = { path = "crates/clash-verge-i18n" }
|
||||||
clash-verge-limiter = { path = "crates/clash-verge-limiter" }
|
clash-verge-limiter = { path = "crates/clash-verge-limiter" }
|
||||||
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
|
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
|
||||||
@@ -70,7 +68,13 @@ tokio = { version = "1.53.1", features = [
|
|||||||
flexi_logger = "0.31.10"
|
flexi_logger = "0.31.10"
|
||||||
log = "0.4.33"
|
log = "0.4.33"
|
||||||
regex = "1.13.1"
|
regex = "1.13.1"
|
||||||
reqwest = { version = "0.13.4", features = ["json", "cookies", "gzip", "rustls", "form"] }
|
reqwest = { version = "0.13.4", features = [
|
||||||
|
"json",
|
||||||
|
"cookies",
|
||||||
|
"gzip",
|
||||||
|
"rustls",
|
||||||
|
"form",
|
||||||
|
] }
|
||||||
rust_iso3166 = "0.2.0"
|
rust_iso3166 = "0.2.0"
|
||||||
|
|
||||||
smartstring = { version = "1.0.1" }
|
smartstring = { version = "1.0.1" }
|
||||||
@@ -92,7 +96,7 @@ windows-sys = { version = "0.61.2", features = [
|
|||||||
"Win32_Security_Authorization",
|
"Win32_Security_Authorization",
|
||||||
"Win32_Storage_FileSystem",
|
"Win32_Storage_FileSystem",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_System_JobObjects"
|
"Win32_System_JobObjects",
|
||||||
] }
|
] }
|
||||||
# *********************************
|
# *********************************
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "clash-verge-signal"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
rust-version = "1.91"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clash-verge-logging = { workspace = true }
|
|
||||||
log = { workspace = true }
|
|
||||||
tokio = { workspace = true, features = ["signal"] }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
use std::sync::{
|
|
||||||
OnceLock,
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
use clash_verge_logging::{Type, logging};
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
mod unix;
|
|
||||||
#[cfg(windows)]
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
pub(crate) static RUNTIME: OnceLock<Option<tokio::runtime::Runtime>> = OnceLock::new();
|
|
||||||
pub(crate) static SHUTDOWN_LATCH: ShutdownLatch = ShutdownLatch::new();
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum ShutdownOutcome {
|
|
||||||
Committed,
|
|
||||||
Canceled,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct ShutdownLatch {
|
|
||||||
is_cleaning_up: AtomicBool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShutdownLatch {
|
|
||||||
const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
is_cleaning_up: AtomicBool::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_begin(&self) -> bool {
|
|
||||||
self.is_cleaning_up
|
|
||||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
|
||||||
.is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(&self, outcome: ShutdownOutcome) {
|
|
||||||
if matches!(outcome, ShutdownOutcome::Canceled) {
|
|
||||||
self.is_cleaning_up.store(false, Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register<F, Fut>(f: F)
|
|
||||||
where
|
|
||||||
F: Fn() -> Fut + Send + Sync + 'static,
|
|
||||||
Fut: Future<Output = ShutdownOutcome> + Send + 'static,
|
|
||||||
{
|
|
||||||
RUNTIME.get_or_init(|| match tokio::runtime::Runtime::new() {
|
|
||||||
Ok(rt) => Some(rt),
|
|
||||||
Err(e) => {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::SystemSignal,
|
|
||||||
"register shutdown signal failed, create tokio runtime error: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
unix::register(f);
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
windows::register(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::{ShutdownLatch, ShutdownOutcome};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn canceled_shutdown_releases_latch_and_permits_next_attempt() {
|
|
||||||
let latch = ShutdownLatch::new();
|
|
||||||
|
|
||||||
assert!(latch.try_begin());
|
|
||||||
assert!(!latch.try_begin());
|
|
||||||
|
|
||||||
latch.finish(ShutdownOutcome::Canceled);
|
|
||||||
|
|
||||||
assert!(latch.try_begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn committed_shutdown_retains_latch_and_suppresses_repeats() {
|
|
||||||
let latch = ShutdownLatch::new();
|
|
||||||
|
|
||||||
assert!(latch.try_begin());
|
|
||||||
latch.finish(ShutdownOutcome::Committed);
|
|
||||||
|
|
||||||
assert!(!latch.try_begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
use clash_verge_logging::{Type, logging};
|
|
||||||
use tokio::signal::unix::{SignalKind, signal};
|
|
||||||
|
|
||||||
use crate::{RUNTIME, SHUTDOWN_LATCH, ShutdownOutcome};
|
|
||||||
|
|
||||||
pub fn register<F, Fut>(f: F)
|
|
||||||
where
|
|
||||||
F: Fn() -> Fut + Send + Sync + 'static,
|
|
||||||
Fut: Future<Output = ShutdownOutcome> + Send + 'static,
|
|
||||||
{
|
|
||||||
if let Some(Some(rt)) = RUNTIME.get() {
|
|
||||||
rt.spawn(async move {
|
|
||||||
let mut sigterm = match signal(SignalKind::terminate()) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register SIGTERM: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut sigint = match signal(SignalKind::interrupt()) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register SIGINT: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut sighup = match signal(SignalKind::hangup()) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register SIGHUP: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let signal_name;
|
|
||||||
tokio::select! {
|
|
||||||
_ = sigterm.recv() => {
|
|
||||||
signal_name = "SIGTERM";
|
|
||||||
}
|
|
||||||
_ = sigint.recv() => {
|
|
||||||
signal_name = "SIGINT";
|
|
||||||
}
|
|
||||||
_ = sighup.recv() => {
|
|
||||||
signal_name = "SIGHUP";
|
|
||||||
}
|
|
||||||
else => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !SHUTDOWN_LATCH.try_begin() {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::SystemSignal,
|
|
||||||
"Already shutting down, ignoring repeated signal: {}",
|
|
||||||
signal_name
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
logging!(info, Type::SystemSignal, "Caught signal {}", signal_name);
|
|
||||||
|
|
||||||
SHUTDOWN_LATCH.finish(f().await);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::SystemSignal,
|
|
||||||
"register shutdown signal failed, RUNTIME is not available"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
use clash_verge_logging::{Type, logging};
|
|
||||||
use tokio::signal::windows;
|
|
||||||
|
|
||||||
use crate::{RUNTIME, SHUTDOWN_LATCH, ShutdownOutcome};
|
|
||||||
|
|
||||||
pub fn register<F, Fut>(f: F)
|
|
||||||
where
|
|
||||||
F: Fn() -> Fut + Send + Sync + 'static,
|
|
||||||
Fut: Future<Output = ShutdownOutcome> + Send + 'static,
|
|
||||||
{
|
|
||||||
if let Some(Some(rt)) = RUNTIME.get() {
|
|
||||||
rt.spawn(async move {
|
|
||||||
let mut ctrl_c = match windows::ctrl_c() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+C: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctrl_close = match windows::ctrl_close() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Close: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctrl_shutdown = match windows::ctrl_shutdown() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Shutdown: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctrl_logoff = match windows::ctrl_logoff() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Logoff: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let signal_name;
|
|
||||||
tokio::select! {
|
|
||||||
_ = ctrl_c.recv() => {
|
|
||||||
signal_name = "Ctrl+C";
|
|
||||||
}
|
|
||||||
_ = ctrl_close.recv() => {
|
|
||||||
signal_name = "Ctrl+Close";
|
|
||||||
}
|
|
||||||
_ = ctrl_shutdown.recv() => {
|
|
||||||
signal_name = "Ctrl+Shutdown";
|
|
||||||
}
|
|
||||||
_ = ctrl_logoff.recv() => {
|
|
||||||
signal_name = "Ctrl+Logoff";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !SHUTDOWN_LATCH.try_begin() {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::SystemSignal,
|
|
||||||
"Already shutting down, ignoring repeated signal: {}",
|
|
||||||
signal_name
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
logging!(info, Type::SystemSignal, "Caught Windows signal: {}", signal_name);
|
|
||||||
|
|
||||||
SHUTDOWN_LATCH.finish(f().await);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::SystemSignal,
|
|
||||||
"register shutdown signal failed, RUNTIME is not available"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,10 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
[features]
|
[features]
|
||||||
default = ["custom-protocol"]
|
default = ["custom-protocol"]
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
verge-dev = ["clash_verge_logger/color", "clash_verge_service_ipc/development-channel"]
|
verge-dev = [
|
||||||
|
"clash_verge_logger/color",
|
||||||
|
"clash_verge_service_ipc/development-channel",
|
||||||
|
]
|
||||||
dev-sidecar = ["verge-dev"]
|
dev-sidecar = ["verge-dev"]
|
||||||
tauri-dev = []
|
tauri-dev = []
|
||||||
tokio-trace = ["console-subscriber"]
|
tokio-trace = ["console-subscriber"]
|
||||||
@@ -34,7 +37,6 @@ tauri-build = { version = "2.6.3", features = [] }
|
|||||||
clash-verge-draft = { workspace = true }
|
clash-verge-draft = { workspace = true }
|
||||||
clash-verge-media-unlock = { workspace = true }
|
clash-verge-media-unlock = { workspace = true }
|
||||||
clash-verge-logging = { workspace = true }
|
clash-verge-logging = { workspace = true }
|
||||||
clash-verge-signal = { workspace = true }
|
|
||||||
clash-verge-i18n = { workspace = true }
|
clash-verge-i18n = { workspace = true }
|
||||||
clash-verge-limiter = { workspace = true }
|
clash-verge-limiter = { workspace = true }
|
||||||
tauri-plugin-clash-verge-sysinfo = { workspace = true }
|
tauri-plugin-clash-verge-sysinfo = { workspace = true }
|
||||||
@@ -60,7 +62,9 @@ bitflags = { workspace = true }
|
|||||||
socket2 = { workspace = true }
|
socket2 = { workspace = true }
|
||||||
warp = { version = "0.4.3", features = ["server"] }
|
warp = { version = "0.4.3", features = ["server"] }
|
||||||
open = "5.4.1"
|
open = "5.4.1"
|
||||||
rfd = { version = "0.16.0", default-features = false, features = ["common-controls-v6"] }
|
rfd = { version = "0.16.0", default-features = false, features = [
|
||||||
|
"common-controls-v6",
|
||||||
|
] }
|
||||||
dunce = "1.0.5"
|
dunce = "1.0.5"
|
||||||
nanoid = "0.5"
|
nanoid = "0.5"
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ pub async fn open_or_close_dashboard() {
|
|||||||
logging!(info, Type::Window, "Window toggle result: {result:?}");
|
logging!(info, Type::Window, "Window toggle result: {result:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn quit() -> clash_verge_signal::ShutdownOutcome {
|
pub async fn quit() {
|
||||||
logging!(debug, Type::System, "启动退出流程");
|
logging!(debug, Type::System, "启动退出流程");
|
||||||
// 设置退出标志
|
// 设置退出标志
|
||||||
handle::Handle::global().set_is_exiting();
|
handle::Handle::global().set_is_exiting();
|
||||||
@@ -133,13 +133,11 @@ pub async fn quit() -> clash_verge_signal::ShutdownOutcome {
|
|||||||
"app_quit::core_stop_failed",
|
"app_quit::core_stop_failed",
|
||||||
cleanup_result.stop_error.unwrap_or_default(),
|
cleanup_result.stop_error.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
return clash_verge_signal::ShutdownOutcome::Canceled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::server::shutdown_embedded_server();
|
utils::server::shutdown_embedded_server();
|
||||||
let app_handle = handle::Handle::app_handle();
|
let app_handle = handle::Handle::app_handle();
|
||||||
app_handle.exit(if cleanup_result.all_success { 0 } else { 1 });
|
app_handle.exit(if cleanup_result.all_success { 0 } else { 1 });
|
||||||
clash_verge_signal::ShutdownOutcome::Committed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clean_async() -> CleanupResult {
|
pub async fn clean_async() -> CleanupResult {
|
||||||
|
|||||||
@@ -302,7 +302,6 @@ pub fn run() -> std::process::ExitCode {
|
|||||||
if let Err(panic) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
if let Err(panic) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||||
resolve::resolve_setup_async();
|
resolve::resolve_setup_async();
|
||||||
resolve::resolve_setup_sync();
|
resolve::resolve_setup_sync();
|
||||||
resolve::init_signal();
|
|
||||||
logging!(info, Type::Setup, "初始化已启动");
|
logging!(info, Type::Setup, "初始化已启动");
|
||||||
})) {
|
})) {
|
||||||
log_setup_panic("window-core", panic);
|
log_setup_panic("window-core", panic);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ use crate::{
|
|||||||
utils::{init, server, window_manager::WindowManager},
|
utils::{init, server, window_manager::WindowManager},
|
||||||
};
|
};
|
||||||
use clash_verge_logging::{Type, logging, logging_error};
|
use clash_verge_logging::{Type, logging, logging_error};
|
||||||
use clash_verge_signal;
|
|
||||||
|
|
||||||
pub mod dns;
|
pub mod dns;
|
||||||
mod scheme;
|
mod scheme;
|
||||||
@@ -179,11 +178,6 @@ async fn init_silent_updater() {
|
|||||||
logging!(info, Type::Setup, "Silent updater initialized");
|
logging!(info, Type::Setup, "Silent updater initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn init_signal() {
|
|
||||||
logging!(info, Type::Setup, "Initializing signal handlers...");
|
|
||||||
clash_verge_signal::register(feat::quit);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn init_work_config() {
|
async fn init_work_config() {
|
||||||
logging_error!(Type::Setup, init::init_config().await);
|
logging_error!(Type::Setup, init::init_config().await);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ static INSTANCE_LOCK: OnceCell<std::fs::File> = OnceCell::new();
|
|||||||
const PAC_INITIAL_AVAILABLE: bool = false;
|
const PAC_INITIAL_AVAILABLE: bool = false;
|
||||||
static PAC_AVAILABLE: AtomicBool = AtomicBool::new(PAC_INITIAL_AVAILABLE);
|
static PAC_AVAILABLE: AtomicBool = AtomicBool::new(PAC_INITIAL_AVAILABLE);
|
||||||
static COMMANDS_READY: AtomicBool = AtomicBool::new(false);
|
static COMMANDS_READY: AtomicBool = AtomicBool::new(false);
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
static DEV_QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum SingletonDisposition {
|
pub enum SingletonDisposition {
|
||||||
@@ -144,55 +142,12 @@ fn instance_auth(token: String) -> impl warp::Filter<Extract = (), Error = warp:
|
|||||||
.untuple_one()
|
.untuple_one()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
fn dev_quit_route<F>(
|
|
||||||
token: String,
|
|
||||||
commands_ready: &'static AtomicBool,
|
|
||||||
quit_requested: &'static AtomicBool,
|
|
||||||
trigger: F,
|
|
||||||
) -> impl warp::Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone
|
|
||||||
where
|
|
||||||
F: Fn() + Clone + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
instance_auth(token)
|
|
||||||
.and(warp::path!("commands" / "dev" / "quit"))
|
|
||||||
.and(warp::post())
|
|
||||||
.and_then(move || {
|
|
||||||
let trigger = trigger.clone();
|
|
||||||
async move {
|
|
||||||
if !commands_ready.load(Ordering::Acquire) {
|
|
||||||
return Ok::<_, warp::Rejection>(warp::reply::with_status(
|
|
||||||
String::from("starting"),
|
|
||||||
warp::http::StatusCode::SERVICE_UNAVAILABLE,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if quit_requested
|
|
||||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
trigger();
|
|
||||||
}
|
|
||||||
Ok(warp::reply::with_status(
|
|
||||||
String::from("accepted"),
|
|
||||||
warp::http::StatusCode::ACCEPTED,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
fn release_dev_quit_latch(quit_requested: &AtomicBool, outcome: clash_verge_signal::ShutdownOutcome) {
|
|
||||||
if matches!(outcome, clash_verge_signal::ShutdownOutcome::Canceled) {
|
|
||||||
quit_requested.store(false, Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_embedded_server(listener: tokio::net::TcpListener, token: String) {
|
fn start_embedded_server(listener: tokio::net::TcpListener, token: String) {
|
||||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||||
let _ = SHUTDOWN_SENDER.set(Mutex::new(Some(shutdown_tx)));
|
let _ = SHUTDOWN_SENDER.set(Mutex::new(Some(shutdown_tx)));
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
#[cfg(feature = "verge-dev")]
|
||||||
let auth = instance_auth(token.clone());
|
let auth = instance_auth(token);
|
||||||
#[cfg(not(feature = "verge-dev"))]
|
#[cfg(not(feature = "verge-dev"))]
|
||||||
let auth = instance_auth(token);
|
let auth = instance_auth(token);
|
||||||
|
|
||||||
@@ -256,12 +211,6 @@ fn start_embedded_server(listener: tokio::net::TcpListener, token: String) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let commands = auth.clone().and(visible).or(auth.and(scheme)).or(pac);
|
let commands = auth.clone().and(visible).or(auth.and(scheme)).or(pac);
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
let commands = commands.or(dev_quit_route(token, &COMMANDS_READY, &DEV_QUIT_REQUESTED, || {
|
|
||||||
AsyncHandler::spawn(|| async {
|
|
||||||
release_dev_quit_latch(&DEV_QUIT_REQUESTED, crate::feat::quit().await);
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
warp::serve(commands)
|
warp::serve(commands)
|
||||||
.incoming(listener)
|
.incoming(listener)
|
||||||
@@ -432,113 +381,10 @@ fn write_instance_record(path: &Path, record: &InstanceRecord) -> Result<()> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{InstanceRecord, bind_primary_listener, read_instance_record, write_instance_record};
|
||||||
InstanceRecord, PAC_INITIAL_AVAILABLE, bind_primary_listener, read_instance_record, write_instance_record,
|
|
||||||
};
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use super::{open_instance_lock, try_lock_instance};
|
use super::{open_instance_lock, try_lock_instance};
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
use super::{INSTANCE_TOKEN_HEADER, dev_quit_route, release_dev_quit_latch};
|
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
use std::sync::{
|
|
||||||
Arc,
|
|
||||||
atomic::{AtomicBool, AtomicUsize},
|
|
||||||
};
|
|
||||||
#[test]
|
|
||||||
fn pac_is_fail_closed_before_core_readiness() {
|
|
||||||
// Asserts the default rather than the live flag: PAC availability is now derived from
|
|
||||||
// the Running Mode by `core::runstate`, so any test that drives a Core transition
|
|
||||||
// legitimately moves the live flag. That the derivation itself can never disagree with
|
|
||||||
// the Running Mode is covered in `core::runstate` against a fake environment.
|
|
||||||
const { assert!(!PAC_INITIAL_AVAILABLE) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "verge-dev")]
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
||||||
async fn dev_quit_route_requires_private_auth_ready_state_and_dispatches_once() -> anyhow::Result<()> {
|
|
||||||
static READY: AtomicBool = AtomicBool::new(false);
|
|
||||||
static REQUESTED: AtomicBool = AtomicBool::new(false);
|
|
||||||
READY.store(false, Ordering::Release);
|
|
||||||
REQUESTED.store(false, Ordering::Release);
|
|
||||||
let dispatches = Arc::new(AtomicUsize::new(0));
|
|
||||||
let dispatches_for_route = Arc::clone(&dispatches);
|
|
||||||
let token = "ab".repeat(32);
|
|
||||||
let route = dev_quit_route(token.clone(), &READY, &REQUESTED, move || {
|
|
||||||
dispatches_for_route.fetch_add(1, Ordering::AcqRel);
|
|
||||||
});
|
|
||||||
|
|
||||||
let missing = warp::test::request()
|
|
||||||
.method("POST")
|
|
||||||
.path("/commands/dev/quit")
|
|
||||||
.reply(&route)
|
|
||||||
.await;
|
|
||||||
assert_eq!(missing.status(), warp::http::StatusCode::NOT_FOUND);
|
|
||||||
|
|
||||||
let wrong_token = warp::test::request()
|
|
||||||
.method("POST")
|
|
||||||
.path("/commands/dev/quit")
|
|
||||||
.header(INSTANCE_TOKEN_HEADER, "cd".repeat(32))
|
|
||||||
.reply(&route)
|
|
||||||
.await;
|
|
||||||
assert_eq!(wrong_token.status(), warp::http::StatusCode::NOT_FOUND);
|
|
||||||
|
|
||||||
let not_ready = warp::test::request()
|
|
||||||
.method("POST")
|
|
||||||
.path("/commands/dev/quit")
|
|
||||||
.header(INSTANCE_TOKEN_HEADER, &token)
|
|
||||||
.reply(&route)
|
|
||||||
.await;
|
|
||||||
assert_eq!(not_ready.status(), warp::http::StatusCode::SERVICE_UNAVAILABLE);
|
|
||||||
assert!(!REQUESTED.load(Ordering::Acquire));
|
|
||||||
|
|
||||||
let wrong_method = warp::test::request()
|
|
||||||
.method("GET")
|
|
||||||
.path("/commands/dev/quit")
|
|
||||||
.header(INSTANCE_TOKEN_HEADER, &token)
|
|
||||||
.reply(&route)
|
|
||||||
.await;
|
|
||||||
assert_eq!(wrong_method.status(), warp::http::StatusCode::METHOD_NOT_ALLOWED);
|
|
||||||
assert!(!REQUESTED.load(Ordering::Acquire));
|
|
||||||
|
|
||||||
READY.store(true, Ordering::Release);
|
|
||||||
let barrier = Arc::new(tokio::sync::Barrier::new(8));
|
|
||||||
let accepted = futures::future::join_all((0..8).map(|_| {
|
|
||||||
let barrier = Arc::clone(&barrier);
|
|
||||||
let route = route.clone();
|
|
||||||
let token = token.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
barrier.wait().await;
|
|
||||||
warp::test::request()
|
|
||||||
.method("POST")
|
|
||||||
.path("/commands/dev/quit")
|
|
||||||
.header(INSTANCE_TOKEN_HEADER, token)
|
|
||||||
.reply(&route)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.await;
|
|
||||||
for accepted in accepted {
|
|
||||||
let accepted = accepted?;
|
|
||||||
assert_eq!(accepted.status(), warp::http::StatusCode::ACCEPTED);
|
|
||||||
}
|
|
||||||
assert_eq!(dispatches.load(Ordering::Acquire), 1);
|
|
||||||
|
|
||||||
release_dev_quit_latch(&REQUESTED, clash_verge_signal::ShutdownOutcome::Canceled);
|
|
||||||
assert!(!REQUESTED.load(Ordering::Acquire));
|
|
||||||
let retry = warp::test::request()
|
|
||||||
.method("POST")
|
|
||||||
.path("/commands/dev/quit")
|
|
||||||
.header(INSTANCE_TOKEN_HEADER, &token)
|
|
||||||
.reply(&route)
|
|
||||||
.await;
|
|
||||||
assert_eq!(retry.status(), warp::http::StatusCode::ACCEPTED);
|
|
||||||
assert_eq!(dispatches.load(Ordering::Acquire), 2);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn retained_record_supplies_the_next_primarys_preferred_port() -> anyhow::Result<()> {
|
async fn retained_record_supplies_the_next_primarys_preferred_port() -> anyhow::Result<()> {
|
||||||
let root = std::env::temp_dir().join(format!("singleton-sticky-port-{}", std::process::id()));
|
let root = std::env::temp_dir().join(format!("singleton-sticky-port-{}", std::process::id()));
|
||||||
|
|||||||
Reference in New Issue
Block a user