mirror of
https://github.com/by-sonic/tglock.git
synced 2026-08-12 09:50:27 +03:00
fix: диагностика перестаёт врать — падения маршрутов и потеря секрета (#38)
Два дефекта одного класса: состояние, которое не отражает реальность. Оба найдены по данным из #32 и #37. #32. Присланный лог показывал «сбоев 0» при том, что DC2 и DC4 всегда шли через «Запасной Telegram IP», а DC203 всегда через «Системный DNS» — то есть основные закреплённые адреса не использовались ни разу. Причина в счётчике: ws_failures растёт только когда упали ВСЕ маршруты и соединение не состоялось. Падения отдельных маршрутов через record_failure не попадали никуда, поэтому перебор с откатом на запасной адрес выглядел как полное отсутствие проблем. Добавлен route_failures: растёт на каждое падение маршрута, виден в строке статуса CLI и в диагностике интерфейса. Теперь по логу сразу видно, что закреплённый адрес мёртв, а не приходится это выводить. #37. Симптом: Telegram пишет «прокси настроен неверно и будет отключён», при этом Check status показывает Available. Это картина несовпадения секрета: TCP проходит, init не разбирается под другим секретом, соединение закрывается. Секрет мог меняться молча: #[cfg(not(unix))] fn write_secret_file(path: &Path, value: &str) { let _ = std::fs::write(path, value); // ошибка выброшена } create_dir_all рядом — так же. Если запись в %APPDATA%\TGLock\secret не удавалась, программа генерировала новый секрет при каждом запуске и ничего об этом не сообщала. Теперь write_secret_file возвращает Result, load_or_create_secret_at отдаёт StoredSecret с полем write_error, а оба интерфейса показывают предупреждение: CLI строкой при старте, GUI записью в журнал. Прокси при этом продолжает работать — просто до перезапуска. Тесты: every_route_failure_is_counted, a_failed_write_is_reported_instead_of _swallowed (родитель пути — файл, поэтому каталог не создать), a_successful_write_reports_no_error (секрет переживает второй запуск). В диагностике интерфейса добавлена подсказка: падения маршрутов больше нуля при работающем Telegram — норма, значит закреплённый адрес недоступен и подключение идёт через запасной. Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -113,6 +114,14 @@ struct HealthState {
|
||||
pub struct TransportEngine {
|
||||
health: Mutex<HealthState>,
|
||||
worker_domains: Mutex<Vec<String>>,
|
||||
/// Сколько раз отдельный маршрут не ответил.
|
||||
///
|
||||
/// Считается отдельно от `Stats::ws_failures`, который растёт только когда
|
||||
/// упали ВСЕ маршруты. Из-за этого диагностика показывала «сбоев 0», пока
|
||||
/// закреплённый адрес был недоступен и каждое холодное соединение молча
|
||||
/// откатывалось на следующий маршрут, тратя на это до восьми секунд
|
||||
/// (by-sonic/tglock#32).
|
||||
route_failures: AtomicU32,
|
||||
#[cfg(test)]
|
||||
forced_routes: Mutex<Vec<Route>>,
|
||||
}
|
||||
@@ -258,7 +267,13 @@ impl TransportEngine {
|
||||
health.preferred.insert(key, route.clone());
|
||||
}
|
||||
|
||||
/// Сколько отдельных маршрутов не ответило за время работы.
|
||||
pub fn route_failures(&self) -> u32 {
|
||||
self.route_failures.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn record_failure(&self, route: &Route) {
|
||||
self.route_failures.fetch_add(1, Ordering::Relaxed);
|
||||
let mut health = self.health.lock().unwrap();
|
||||
let failures = health
|
||||
.routes
|
||||
@@ -656,6 +671,38 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_route_failure_is_counted() {
|
||||
// Диагностика показывала «сбоев 0», пока закреплённый адрес был мёртв и
|
||||
// соединения молча откатывались на запасной. Счётчик маршрутов должен
|
||||
// видеть каждое такое падение.
|
||||
let engine = TransportEngine::new();
|
||||
let routes = routes_for_dc(2, false);
|
||||
assert_eq!(engine.route_failures(), 0);
|
||||
|
||||
engine.record_failure(&routes[0]);
|
||||
assert_eq!(engine.route_failures(), 1);
|
||||
|
||||
engine.record_failure(&routes[0]);
|
||||
engine.record_failure(&routes[1]);
|
||||
assert_eq!(
|
||||
engine.route_failures(),
|
||||
3,
|
||||
"считаются все падения, включая повторные по тому же маршруту"
|
||||
);
|
||||
|
||||
// Успех не обнуляет историю: она нужна, чтобы понять, что маршруты
|
||||
// перебирались, даже когда в итоге всё соединилось.
|
||||
engine.record_success(
|
||||
DcKey {
|
||||
dc: 2,
|
||||
media: false,
|
||||
},
|
||||
&routes[1],
|
||||
);
|
||||
assert_eq!(engine.route_failures(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn documented_worker_contract_matches_the_requested_path() {
|
||||
// docs/CLOUDFLARE_WORKER.md promises exactly this shape.
|
||||
|
||||
Reference in New Issue
Block a user