Update On Thu Oct 2 20:41:06 CEST 2025

This commit is contained in:
github-action[bot]
2025-10-02 20:41:06 +02:00
parent 1b22512c47
commit c14ab7cdfe
18 changed files with 485 additions and 143 deletions
+1
View File
@@ -1138,3 +1138,4 @@ Update On Sun Sep 28 20:32:41 CEST 2025
Update On Mon Sep 29 20:38:41 CEST 2025
Update On Tue Sep 30 20:35:53 CEST 2025
Update On Wed Oct 1 20:41:09 CEST 2025
Update On Thu Oct 2 20:40:58 CEST 2025
@@ -287,6 +287,11 @@ pub struct IVerge {
#[serde(skip_serializing_if = "Option::is_none")]
pub network_statistic_widget: Option<NetworkStatisticWidgetConfig>,
/// PAC URL for automatic proxy configuration
/// This field is used to set PAC proxy without exposing it to the frontend UI
#[serde(skip_serializing_if = "Option::is_none")]
pub pac_url: Option<String>,
/// enable tray text display on Linux systems
/// When enabled, shows proxy and TUN mode status as text next to the tray icon
/// When disabled, only shows status via icon changes (prevents text display issues on Wayland)
@@ -4,6 +4,7 @@ pub mod handle;
pub mod hotkey;
pub mod logger;
pub mod manager;
pub mod pac;
pub mod service;
pub mod storage;
pub mod sysopt;
@@ -0,0 +1,332 @@
use crate::{config::Config, log_err};
use anyhow::{Context, Result};
use std::{path::PathBuf, time::Duration};
use sysproxy::Autoproxy;
use tokio::fs;
/// PAC module for handling Proxy Auto-Configuration
pub struct PacManager;
// Constants for PAC handling
const PAC_DOWNLOAD_TIMEOUT: u64 = 30; // seconds
const PAC_MAX_RETRIES: u32 = 3;
const PAC_RETRY_DELAY: u64 = 5; // seconds
impl PacManager {
/// Get PAC URL from config
pub fn get_pac_url() -> Option<String> {
Config::verge().latest().pac_url.clone()
}
/// Check if PAC is enabled (URL is set)
pub fn is_pac_enabled() -> bool {
Self::get_pac_url().is_some_and(|url| !url.is_empty())
}
/// Download PAC script from URL with retry logic
pub async fn download_pac_script(url: &str) -> Result<String> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(PAC_DOWNLOAD_TIMEOUT))
.build()
.context("failed to build HTTP client")?;
// Retry logic
let mut last_error = None;
for attempt in 1..=PAC_MAX_RETRIES {
match client.get(url).send().await {
Ok(response) => {
if response.status().is_success() {
match response.text().await {
Ok(content) => return Ok(content),
Err(e) => {
let err =
anyhow::anyhow!("failed to read PAC script content: {}", e);
log::warn!(target: "app", "Attempt {}/{} failed: {}", attempt, PAC_MAX_RETRIES, err);
last_error = Some(err);
}
}
} else {
let err = anyhow::anyhow!(
"failed to download PAC script, status: {}",
response.status()
);
log::warn!(target: "app", "Attempt {}/{} failed: {}", attempt, PAC_MAX_RETRIES, err);
last_error = Some(err);
}
}
Err(e) => {
let err = anyhow::anyhow!("failed to download PAC script: {}", e);
log::warn!(target: "app", "Attempt {}/{} failed: {}", attempt, PAC_MAX_RETRIES, err);
last_error = Some(err);
}
}
// Wait before retrying (except on last attempt)
if attempt < PAC_MAX_RETRIES {
tokio::time::sleep(Duration::from_secs(PAC_RETRY_DELAY)).await;
}
}
Err(last_error.unwrap_or_else(|| {
anyhow::anyhow!(
"failed to download PAC script after {} attempts",
PAC_MAX_RETRIES
)
}))
}
/// Save PAC script to cache directory
pub async fn save_pac_script(script: &str) -> Result<PathBuf> {
let cache_dir = crate::utils::dirs::cache_dir()?;
let pac_file = cache_dir.join("pac.js");
fs::write(&pac_file, script)
.await
.context("failed to save PAC script")?;
Ok(pac_file)
}
/// Basic validation of PAC script structure - check for required functions
pub async fn validate_pac_script(script: &str) -> Result<()> {
// A basic validation without using the JS engine - just check if FindProxyForURL function exists
if !script.contains("FindProxyForURL") {
return Err(anyhow::anyhow!(
"PAC script must contain FindProxyForURL function"
));
}
// Additional basic checks could be added here if needed
Ok(())
}
/// Set system proxy to use PAC URL
pub fn set_pac_proxy(url: &str) -> Result<()> {
// Check if Autoproxy is supported on this platform
if !Autoproxy::is_support() {
return Err(anyhow::anyhow!(
"PAC proxy is not supported on this platform"
));
}
let autoproxy = Autoproxy {
enable: true,
url: url.to_string(),
};
autoproxy
.set_auto_proxy()
.context("failed to set PAC proxy")?;
Ok(())
}
/// Disable PAC proxy and revert to direct proxy
pub fn disable_pac_proxy() -> Result<()> {
// Check if Autoproxy is supported on this platform
if !Autoproxy::is_support() {
log::info!(target: "app", "PAC proxy is not supported on this platform, skipping disable");
return Ok(());
}
let autoproxy = Autoproxy {
enable: false,
url: String::new(),
};
autoproxy
.set_auto_proxy()
.context("failed to disable PAC proxy")?;
Ok(())
}
/// Fallback to direct proxy when PAC fails
pub fn fallback_to_direct_proxy() -> Result<()> {
log::warn!(target: "app", "Falling back to direct proxy mode");
// Check if Sysproxy is supported on this platform
if !sysproxy::Sysproxy::is_support() {
return Err(anyhow::anyhow!(
"Direct proxy is not supported on this platform"
));
}
// Get the standard proxy settings
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
let (enable, bypass) = {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.unwrap_or(false),
verge.system_proxy_bypass.clone(),
)
};
#[cfg(target_os = "windows")]
let default_bypass = "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;<local>";
#[cfg(target_os = "linux")]
let default_bypass = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1";
#[cfg(target_os = "macos")]
let default_bypass = "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
let sysproxy = sysproxy::Sysproxy {
enable,
host: String::from("127.0.0.1"),
port,
bypass: bypass.unwrap_or(default_bypass.into()),
};
sysproxy
.set_system_proxy()
.context("failed to set direct proxy as fallback")?;
log::info!(target: "app", "Fallback to direct proxy successful");
Ok(())
}
/// Update PAC configuration with error handling and fallback
pub async fn update_pac() -> Result<()> {
if !Self::is_pac_enabled() {
log::info!(target: "app", "PAC is not enabled, skipping update");
return Ok(());
}
// Check if Autoproxy is supported on this platform
if !Autoproxy::is_support() {
log::warn!(target: "app", "PAC proxy is not supported on this platform");
// Try to fallback to direct proxy
log_err!(Self::fallback_to_direct_proxy());
return Err(anyhow::anyhow!(
"PAC proxy is not supported on this platform"
));
}
let pac_url = Self::get_pac_url().unwrap();
log::info!(target: "app", "Updating PAC from URL: {}", pac_url);
// Download PAC script
let script = match Self::download_pac_script(&pac_url).await {
Ok(script) => script,
Err(e) => {
log::error!(target: "app", "Failed to download PAC script: {}", e);
// Try to fallback to direct proxy
log_err!(Self::fallback_to_direct_proxy());
return Err(e);
}
};
// Validate PAC script
if let Err(e) = Self::validate_pac_script(&script).await {
log::error!(target: "app", "PAC script validation failed: {}", e);
// Try to fallback to direct proxy
log_err!(Self::fallback_to_direct_proxy());
return Err(e);
}
// Save PAC script to cache
if let Err(e) = Self::save_pac_script(&script).await {
log::warn!(target: "app", "Failed to save PAC script to cache: {}", e);
// This is not critical, continue with setting the proxy
}
// Set system proxy to use PAC
if let Err(e) = Self::set_pac_proxy(&pac_url) {
log::error!(target: "app", "Failed to set PAC proxy: {}", e);
// Try to fallback to direct proxy
log_err!(Self::fallback_to_direct_proxy());
return Err(e);
}
log::info!(target: "app", "PAC updated successfully");
Ok(())
}
/// Initialize PAC proxy on startup with error handling
pub async fn init_pac_proxy() -> Result<()> {
if !Self::is_pac_enabled() {
log::info!(target: "app", "PAC is not enabled, skipping initialization");
return Ok(());
}
log::info!(target: "app", "Initializing PAC proxy");
if let Err(e) = Self::update_pac().await {
log::error!(target: "app", "Failed to initialize PAC proxy: {}", e);
return Err(e);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio;
#[tokio::test]
async fn test_pac_download() {
// Test with a known good PAC URL
let pac_url = "https://raw.githubusercontent.com/Slinetrac/clash-nyanpasu/main/test.pac";
match PacManager::download_pac_script(pac_url).await {
Ok(script) => {
assert!(!script.is_empty());
println!(
"Downloaded PAC script: {}",
&script[..std::cmp::min(100, script.len())]
);
}
Err(e) => {
eprintln!("Failed to download PAC script: {}", e);
// This might fail in test environment, so we won't assert failure
}
}
}
#[tokio::test]
async fn test_pac_validation() {
let valid_pac_script = r#"
function FindProxyForURL(url, host) {
return "DIRECT";
}
"#;
assert!(
PacManager::validate_pac_script(valid_pac_script)
.await
.is_ok()
);
let invalid_pac_script = r#"
function SomeOtherFunction(url, host) {
// This script does not contain the required function
return "PROXY proxy.example.com:8080";
}
"#;
assert!(
PacManager::validate_pac_script(invalid_pac_script)
.await
.is_err()
);
}
#[tokio::test]
async fn test_pac_save() {
let script = "function FindProxyForURL(url, host) { return 'DIRECT'; }";
match PacManager::save_pac_script(script).await {
Ok(path) => {
assert!(path.exists());
// Clean up
let _ = tokio::fs::remove_file(path).await;
}
Err(e) => {
eprintln!("Failed to save PAC script: {}", e);
}
}
}
}
@@ -7,6 +7,10 @@ use std::sync::Arc;
use sysproxy::Sysproxy;
use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe};
// Import PAC manager
#[cfg(feature = "default-meta")]
use crate::core::pac::PacManager;
#[cfg(target_os = "linux")]
use std::process::Command;
@@ -66,6 +70,22 @@ impl Sysopt {
/// init the sysproxy
pub fn init_sysproxy(&self) -> Result<()> {
// Check if PAC is enabled first
#[cfg(feature = "default-meta")]
if PacManager::is_pac_enabled() {
log::info!(target: "app", "Initializing PAC proxy");
// For PAC, we don't set the regular system proxy
// Instead, we let the PAC manager handle it
tauri::async_runtime::spawn(async {
if let Err(e) = PacManager::init_pac_proxy().await {
log::error!(target: "app", "Failed to initialize PAC proxy: {}", e);
}
});
// run the system proxy guard
self.guard_proxy();
return Ok(());
}
let port = Config::verge()
.latest()
.verge_mixed_port
@@ -89,7 +109,10 @@ impl Sysopt {
if enable {
let old = Sysproxy::get_system_proxy().ok();
current.set_system_proxy()?;
if let Err(e) = current.set_system_proxy() {
log::error!(target: "app", "Failed to set system proxy: {}", e);
return Err(e.into()); // Convert sysproxy::Error to anyhow::Error
}
*self.old_sysproxy.lock() = old;
*self.cur_sysproxy.lock() = Some(current);
@@ -102,6 +125,18 @@ impl Sysopt {
/// update the system proxy
pub fn update_sysproxy(&self) -> Result<()> {
// Check if PAC is enabled first
#[cfg(feature = "default-meta")]
if PacManager::is_pac_enabled() {
log::info!(target: "app", "Updating PAC proxy");
// For PAC, we don't set the regular system proxy
// Instead, we let the PAC manager handle it
tauri::async_runtime::spawn(async {
log_err!(PacManager::update_pac().await);
});
return Ok(());
}
let mut cur_sysproxy = self.cur_sysproxy.lock();
let old_sysproxy = self.old_sysproxy.lock();
@@ -132,6 +167,14 @@ impl Sysopt {
/// reset the sysproxy
pub fn reset_sysproxy(&self) -> Result<()> {
// Check if PAC is enabled first
#[cfg(feature = "default-meta")]
if PacManager::is_pac_enabled() {
log::info!(target: "app", "Resetting PAC proxy");
// Disable PAC proxy
log_err!(PacManager::disable_pac_proxy());
}
let mut cur_sysproxy = self.cur_sysproxy.lock();
let mut old_sysproxy = self.old_sysproxy.lock();
@@ -1068,6 +1068,11 @@ export type IVerge = {
* 是否启用网络统计信息浮窗
*/
network_statistic_widget?: NetworkStatisticWidgetConfig | null
/**
* PAC URL for automatic proxy configuration
* This field is used to set PAC proxy without exposing it to the frontend UI
*/
pac_url?: string | null
/**
* enable tray text display on Linux systems
* When enabled, shows proxy and TUN mode status as text next to the tray icon
+17 -4
View File
@@ -1,8 +1,21 @@
Package/en8811h-firmware = $(call Package/firmware-default,Airoha EN8811H PHY firmware)
define Package/en8811h-firmware/install
Package/airoha-en8811h-firmware = $(call Package/firmware-default,Airoha EN8811H 2.5G Ethernet PHY firmware)
define Package/airoha-en8811h-firmware/install
$(INSTALL_DIR) $(1)/lib/firmware/airoha
$(CP) \
$(PKG_BUILD_DIR)/airoha/*.bin \
$(PKG_BUILD_DIR)/airoha/EthMD32.dm.bin \
$(PKG_BUILD_DIR)/airoha/EthMD32.DSP.bin \
$(1)/lib/firmware/airoha
endef
$(eval $(call BuildPackage,en8811h-firmware))
$(eval $(call BuildPackage,airoha-en8811h-firmware))
Package/airoha-en7581-npu-firmware = $(call Package/firmware-default,Airoha EN7581 NPU firmware)
define Package/airoha-en7581-npu-firmware/install
$(INSTALL_DIR) $(1)/lib/firmware/airoha
$(CP) \
$(PKG_BUILD_DIR)/airoha/en7581_npu_data.bin \
$(PKG_BUILD_DIR)/airoha/en7581_npu_rv32.bin \
$(1)/lib/firmware/airoha
endef
$(eval $(call BuildPackage,airoha-en7581-npu-firmware))
+2 -2
View File
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=hysteria
PKG_VERSION:=2.6.3
PKG_VERSION:=2.6.4
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)?
PKG_HASH:=bed1ece93dfaa07fbf709136efadaf4ccb09e0375844de3e28c5644ebe518eb0
PKG_HASH:=9d989174736654f8608a4ba7c1c49306e4b97c6a19d65d926c10a8521de7b2be
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-app-v$(PKG_VERSION)
PKG_LICENSE:=MIT
@@ -74,7 +74,8 @@ func (r *rootEnvImpl) DropProxyEnvironment(tag string) error {
if err != nil {
return err
}
return transientStorage.DropScope(r.ctx, tag)
transientStorage.Clear(r.ctx)
return r.transientStorage.DropScope(r.ctx, tag)
}
type appEnvImpl struct {
@@ -56,7 +56,16 @@ func (s *scopedTransientStorageImpl) List(ctx context.Context, keyPrefix string)
func (s *scopedTransientStorageImpl) Clear(ctx context.Context) {
s.access.Lock()
defer s.access.Unlock()
for _, v := range s.values {
if sw, ok := v.(storage.TransientStorageLifecycleReceiver); ok {
_ = sw.Close()
}
}
s.values = map[string]interface{}{}
for _, v := range s.scopes {
v.Clear(ctx)
}
s.scopes = map[string]storage.ScopedTransientStorage{}
}
func (s *scopedTransientStorageImpl) NarrowScope(ctx context.Context, key string) (storage.ScopedTransientStorage, error) {
@@ -74,6 +83,9 @@ func (s *scopedTransientStorageImpl) NarrowScope(ctx context.Context, key string
func (s *scopedTransientStorageImpl) DropScope(ctx context.Context, key string) error {
s.access.Lock()
defer s.access.Unlock()
if v, ok := s.scopes[key]; ok {
v.Clear(ctx)
}
delete(s.scopes, key)
return nil
}
@@ -3,6 +3,7 @@ package storage
import (
"context"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/features"
)
@@ -32,3 +33,8 @@ type ScopedPersistentStorageService interface {
}
var ScopedPersistentStorageServiceType = (*ScopedPersistentStorageService)(nil)
type TransientStorageLifecycleReceiver interface {
IsTransientStorageLifecycleReceiver()
common.Closable
}
+1 -1
View File
@@ -24,7 +24,7 @@ require (
github.com/pion/dtls/v2 v2.2.12
github.com/pion/transport/v2 v2.2.10
github.com/pires/go-proxyproto v0.8.1
github.com/quic-go/quic-go v0.54.0
github.com/quic-go/quic-go v0.54.1
github.com/refraction-networking/utls v1.8.0
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
github.com/stretchr/testify v1.11.1
+2 -2
View File
@@ -440,8 +440,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg=
github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE=
github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
+50 -16
View File
@@ -17,6 +17,8 @@ import (
core "github.com/v2fly/v2ray-core/v5"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/environment"
"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/session"
"github.com/v2fly/v2ray-core/v5/transport/internet"
@@ -38,12 +40,25 @@ func init() {
common.Must(internet.RegisterTransportDialer(protocolName, Dial))
}
type dialerCanceller func()
type transportConnectionState struct {
scopedDialerMap map[net.Destination]*grpc.ClientConn
scopedDialerAccess sync.Mutex
}
var (
globalDialerMap map[net.Destination]*grpc.ClientConn
globalDialerAccess sync.Mutex
)
func (t *transportConnectionState) IsTransientStorageLifecycleReceiver() {
}
func (t *transportConnectionState) Close() error {
t.scopedDialerAccess.Lock()
defer t.scopedDialerAccess.Unlock()
for _, conn := range t.scopedDialerMap {
_ = conn.Close()
}
t.scopedDialerMap = nil
return nil
}
type dialerCanceller func()
func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {
grpcSettings := streamSettings.ProtocolSettings.(*Config)
@@ -70,25 +85,36 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne
}
func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.DialOption, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, dialerCanceller, error) {
globalDialerAccess.Lock()
defer globalDialerAccess.Unlock()
transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment)
state, err := transportEnvironment.TransientStorage().Get(ctx, "grpc-transport-connection-state")
if err != nil {
state = &transportConnectionState{}
transportEnvironment.TransientStorage().Put(ctx, "grpc-transport-connection-state", state)
state, err = transportEnvironment.TransientStorage().Get(ctx, "grpc-transport-connection-state")
if err != nil {
return nil, nil, newError("failed to get grpc transport connection state").Base(err)
}
}
stateTyped := state.(*transportConnectionState)
if globalDialerMap == nil {
globalDialerMap = make(map[net.Destination]*grpc.ClientConn)
stateTyped.scopedDialerAccess.Lock()
defer stateTyped.scopedDialerAccess.Unlock()
if stateTyped.scopedDialerMap == nil {
stateTyped.scopedDialerMap = make(map[net.Destination]*grpc.ClientConn)
}
canceller := func() {
globalDialerAccess.Lock()
defer globalDialerAccess.Unlock()
delete(globalDialerMap, dest)
stateTyped.scopedDialerAccess.Lock()
defer stateTyped.scopedDialerAccess.Unlock()
delete(stateTyped.scopedDialerMap, dest)
}
// TODO Should support chain proxy to the same destination
if client, found := globalDialerMap[dest]; found && client.GetState() != connectivity.Shutdown {
if client, found := stateTyped.scopedDialerMap[dest]; found && client.GetState() != connectivity.Shutdown {
return client, canceller, nil
}
conn, err := grpc.Dial(
conn, err := grpc.NewClient(
dest.Address.String()+":"+dest.Port.String(),
dialOption,
grpc.WithConnectParams(grpc.ConnectParams{
@@ -117,6 +143,14 @@ func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.Di
return internet.DialSystem(detachedContext, net.TCPDestination(address, port), streamSettings.SocketSettings)
}),
)
globalDialerMap[dest] = conn
canceller = func() {
stateTyped.scopedDialerAccess.Lock()
defer stateTyped.scopedDialerAccess.Unlock()
delete(stateTyped.scopedDialerMap, dest)
if err != nil {
conn.Close()
}
}
stateTyped.scopedDialerMap[dest] = conn
return conn, canceller, err
}
@@ -67,116 +67,4 @@ public static class ProcUtils
Logging.SaveLog(_tag, ex);
}
}
public static async Task ProcessKill(int pid)
{
try
{
await ProcessKill(Process.GetProcessById(pid), false);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
public static async Task ProcessKill(Process? proc, bool review)
{
if (proc is null)
{
return;
}
GetProcessKeyInfo(proc, review, out var procId, out var fileName, out var processName);
try
{
if (Utils.IsNonWindows())
{
proc?.Kill(true);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Kill();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Close();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Dispose();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
await Task.Delay(300);
await ProcessKillByKeyInfo(review, procId, fileName, processName);
}
private static void GetProcessKeyInfo(Process? proc, bool review, out int? procId, out string? fileName, out string? processName)
{
procId = null;
fileName = null;
processName = null;
if (!review)
{
return;
}
try
{
procId = proc?.Id;
fileName = proc?.MainModule?.FileName;
processName = proc?.ProcessName;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private static async Task ProcessKillByKeyInfo(bool review, int? procId, string? fileName, string? processName)
{
if (review && procId != null && fileName != null)
{
try
{
var lstProc = Process.GetProcessesByName(processName);
foreach (var proc2 in lstProc)
{
if (proc2.Id == procId)
{
Logging.SaveLog($"{_tag}, KillProcess not completing the job, procId");
await ProcessKill(proc2, false);
}
if (proc2.MainModule != null && proc2.MainModule?.FileName == fileName)
{
Logging.SaveLog($"{_tag}, KillProcess not completing the job, fileName");
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
}
}
@@ -7,11 +7,11 @@ namespace ServiceLib.Common;
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
*/
public sealed class Job : IDisposable
public sealed class WindowsJob : IDisposable
{
private IntPtr handle = IntPtr.Zero;
public Job()
public WindowsJob()
{
handle = CreateJobObject(IntPtr.Zero, null);
var extendedInfoPtr = IntPtr.Zero;
@@ -94,7 +94,7 @@ namespace ServiceLib.Common;
}
}
~Job()
~WindowsJob()
{
Dispose(false);
}
@@ -8,7 +8,7 @@ public sealed class AppManager
private Config _config;
private int? _statePort;
private int? _statePort2;
private Job? _processJob;
private WindowsJob? _processJob;
public static AppManager Instance => _instance.Value;
public Config Config => _config;
@@ -178,5 +178,6 @@ public class ProcessService : IDisposable
}
_isDisposed = true;
GC.SuppressFinalize(this);
}
}