From 721b659b4be8ff6a76540fd8d8dff8b29b61bfe5 Mon Sep 17 00:00:00 2001 From: rain-bus Date: Wed, 3 Jun 2026 21:16:42 +0800 Subject: [PATCH] fix: latency cache staleness, reduce duplicate probes, fix CI arch detection and nfpm compression --- .github/workflows/release.yml | 5 +-- nfpm.yaml | 4 +-- src/app/latency.rs | 12 ++++++- src/ui/app.rs | 68 ++++++++++++++++++++++++++--------- src/ui/view/home_list.rs | 2 +- sshell.iss | 10 ++++++ 6 files changed, 77 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 343556c..6c25b7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -126,12 +126,9 @@ jobs: uses: Minionguyjpro/Inno-Setup-Action@v1.2.8 env: SHELL_VERSION: ${{ env.SHELL_VERSION }} + SSH_TARGET: ${{ matrix.target }} with: path: sshell.iss - options: >- - /DMyArch=${{ matrix.target == 'x86_64-pc-windows-msvc' && 'x64compatible' || 'arm64' }} - /DMyArchAllowed=${{ matrix.target == 'x86_64-pc-windows-msvc' && 'x64compatible' || 'arm64' }} - /DMyArchInstall64=${{ matrix.target == 'x86_64-pc-windows-msvc' && 'x64compatible' || 'arm64' }} - name: Upload artifact uses: actions/upload-artifact@v4 diff --git a/nfpm.yaml b/nfpm.yaml index 1952f1e..b89563a 100644 --- a/nfpm.yaml +++ b/nfpm.yaml @@ -19,10 +19,10 @@ contents: mode: 0755 deb: - compression: gz + compression: gzip rpm: - compression: gz + compression: gzip archlinux: pkgbase: sshell diff --git a/src/app/latency.rs b/src/app/latency.rs index 984e505..79d5dba 100644 --- a/src/app/latency.rs +++ b/src/app/latency.rs @@ -16,9 +16,19 @@ pub enum LatencyStatus { Unknown, } +/// A cached probe result with a timestamp. +#[derive(Debug, Clone)] +pub struct CacheEntry { + pub status: LatencyStatus, + pub checked_at: Instant, +} + +/// How long before a cached entry is considered stale and re-probed. +pub const STALE_SECS: u64 = 30; + /// Shared latency cache keyed by "host:port" strings. /// Background threads write, draw reads. -pub type LatencyCache = Arc>>; +pub type LatencyCache = Arc>>; /// Perform a TCP connect to measure latency. /// Timeout is 3 seconds. Called from spawned threads. diff --git a/src/ui/app.rs b/src/ui/app.rs index a300803..82c5b6a 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -8,7 +8,6 @@ use crossterm::{ terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; use ratatui::{Terminal, backend::CrosstermBackend}; -use std::collections::HashSet; use std::io; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; @@ -33,12 +32,12 @@ pub fn run() -> Result<()> { if app.session.should_quit { break; } + spawn_latency_probes(&app); if event::poll(Duration::from_millis(200))? && let Event::Key(key) = event::read()? && key.kind == KeyEventKind::Press { handle_key(&mut app, key)?; - spawn_latency_probes(&app); } } @@ -90,25 +89,62 @@ fn handle_key(app: &mut App, key: KeyEvent) -> Result<()> { } fn spawn_latency_probes(app: &App) { - let cache = app.session.latency.lock().unwrap(); - let existing: HashSet = cache.keys().cloned().collect(); - drop(cache); + use crate::app::latency::{CacheEntry, STALE_SECS}; + use std::time::Instant; + let stale_duration = Duration::from_secs(STALE_SECS); + let now = Instant::now(); + + // Collect keys that need probing (missing or stale) + let cache = app.session.latency.lock().unwrap(); + let mut to_probe: Vec = Vec::new(); for (_, profile) in app.entries() { if let ConnectionType::Ssh { host, port, .. } = &profile.kind { let key = format!("{host}:{port}"); - if existing.contains(&key) { - continue; + let needs_probe = match cache.get(&key) { + None => true, + Some(entry) => now.duration_since(entry.checked_at) >= stale_duration, + }; + if needs_probe && !to_probe.contains(&key) { + to_probe.push(key); } - let cache_clone = app.session.latency.clone(); - let host = host.clone(); - let port = *port; - std::thread::spawn(move || { - let status = crate::app::latency::probe(&host, port); - if let Ok(mut cache) = cache_clone.lock() { - cache.insert(key, status); - } - }); } } + drop(cache); + + for key in to_probe { + // Re-check under lock to avoid duplicate spawns + { + let cache = app.session.latency.lock().unwrap(); + if let Some(entry) = cache.get(&key) { + if now.duration_since(entry.checked_at) < stale_duration { + continue; + } + } + } + // Mark as "in-flight" by inserting a fresh entry + { + let mut cache = app.session.latency.lock().unwrap(); + cache.insert(key.clone(), CacheEntry { + status: crate::app::latency::LatencyStatus::Unknown, + checked_at: now, + }); + } + let cache_clone = app.session.latency.clone(); + let host_port = key.clone(); + std::thread::spawn(move || { + let parts: Vec<&str> = host_port.splitn(2, ':').collect(); + let (host, port) = match parts.as_slice() { + [h, p] => (*h, p.parse::().unwrap_or(22)), + _ => return, + }; + let status = crate::app::latency::probe(host, port); + if let Ok(mut cache) = cache_clone.lock() { + cache.insert(host_port, CacheEntry { + status, + checked_at: Instant::now(), + }); + } + }); + } } diff --git a/src/ui/view/home_list.rs b/src/ui/view/home_list.rs index 62bf67a..9e8f76b 100644 --- a/src/ui/view/home_list.rs +++ b/src/ui/view/home_list.rs @@ -269,7 +269,7 @@ fn connection_row( ConnectionType::Ssh { host, port, .. } => { let key = format!("{host}:{port}"); let cache = app.session.latency.lock().unwrap(); - match cache.get(&key) { + match cache.get(&key).map(|e| &e.status) { Some(LatencyStatus::Reachable { ms }) => { let text = format!("{ms} ms"); let color = if *ms < 100 { diff --git a/sshell.iss b/sshell.iss index 2701866..5f659a5 100644 --- a/sshell.iss +++ b/sshell.iss @@ -7,6 +7,16 @@ #define MyAppURL "https://github.com/Rain-Bus/sshell" #define MyAppExeName "sshell.exe" +#if GetEnv("SSH_TARGET") == "aarch64-pc-windows-msvc" + #define MyArch "arm64" + #define MyArchAllowed "arm64" + #define MyArchInstall64 "arm64" +#else + #define MyArch "x64compatible" + #define MyArchAllowed "x64compatible" + #define MyArchInstall64 "x64compatible" +#endif + [Setup] AppId={{sshell-2024-1} AppName={#MyAppName}