fix(gateway): 禁用 Windows 控制台 QuickEdit 并改为非阻塞日志,修复终端输出卡住需按回车的问题
This commit is contained in:
parent
b0c24d64f0
commit
accdfeed3b
@ -1,4 +1,5 @@
|
||||
pub fn initialize_process_runtime() {
|
||||
crate::platform::disable_console_quick_edit();
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
|
||||
// Install a global panic hook so that any panic in a spawned task
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use chrono::Utc;
|
||||
use chrono_tz::Tz;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
use tracing_subscriber::{
|
||||
EnvFilter, Layer, fmt, fmt::time::FormatTime, layer::SubscriberExt, util::SubscriberInitExt,
|
||||
@ -8,6 +10,13 @@ use tracing_subscriber::{
|
||||
|
||||
use crate::config::LogFormat;
|
||||
|
||||
/// Keeps the non-blocking writer worker threads alive for the process lifetime.
|
||||
///
|
||||
/// Dropping a [`WorkerGuard`] shuts down its worker, after which all log
|
||||
/// writes are silently discarded. `init_logging` runs exactly once per
|
||||
/// process, so the guards are parked in a process-wide static.
|
||||
static NON_BLOCKING_GUARDS: OnceLock<Vec<WorkerGuard>> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct ConfiguredTimestamp {
|
||||
timezone: Tz,
|
||||
@ -74,13 +83,24 @@ pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
||||
// Create file appender with daily rotation
|
||||
let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, "picobot.log");
|
||||
|
||||
// Wrap both writers in non-blocking mode: the actual console/file writes
|
||||
// happen on dedicated worker threads. On Windows the console can freeze
|
||||
// (QuickEdit/mark mode, or a selection in Windows Terminal blocks every
|
||||
// console write until Enter is pressed); with synchronous writers that
|
||||
// would stall whichever tokio worker thread emits a log and cascade into
|
||||
// a full gateway freeze. Here only the log worker thread blocks while
|
||||
// the runtime keeps serving requests.
|
||||
let (file_writer, file_guard) = tracing_appender::non_blocking(file_appender);
|
||||
let (console_writer, console_guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||
let _ = NON_BLOCKING_GUARDS.set(vec![file_guard, console_guard]);
|
||||
|
||||
// Build subscriber with both console and file output
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
// 文件层:根据 log_format 选择 text 或 json
|
||||
let file_layer = match log_format {
|
||||
LogFormat::Json => fmt::layer()
|
||||
.with_writer(file_appender)
|
||||
.with_writer(file_writer)
|
||||
.with_timer(ConfiguredTimestamp { timezone })
|
||||
.with_ansi(false)
|
||||
.with_target(true)
|
||||
@ -89,7 +109,7 @@ pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
||||
.json()
|
||||
.boxed(),
|
||||
LogFormat::Text => fmt::layer()
|
||||
.with_writer(file_appender)
|
||||
.with_writer(file_writer)
|
||||
.with_timer(ConfiguredTimestamp { timezone })
|
||||
.with_ansi(false)
|
||||
.with_target(true)
|
||||
@ -100,6 +120,7 @@ pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
||||
|
||||
// 控制台层:始终文本格式
|
||||
let console_layer = fmt::layer()
|
||||
.with_writer(console_writer)
|
||||
.with_timer(ConfiguredTimestamp { timezone })
|
||||
.with_target(true)
|
||||
.with_level(true);
|
||||
@ -121,7 +142,13 @@ pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
||||
pub fn init_logging_console_only(timezone: Tz) {
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
// See init_logging: non-blocking writes keep a frozen Windows console
|
||||
// from stalling the runtime.
|
||||
let (console_writer, console_guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||
let _ = NON_BLOCKING_GUARDS.set(vec![console_guard]);
|
||||
|
||||
let console_layer = fmt::layer()
|
||||
.with_writer(console_writer)
|
||||
.with_timer(ConfiguredTimestamp { timezone })
|
||||
.with_target(true)
|
||||
.with_level(true);
|
||||
|
||||
@ -329,6 +329,99 @@ fn windows_is_process_waiting_on_stdin(pid: u32) -> Option<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Disable Windows console QuickEdit mode.
|
||||
///
|
||||
/// With QuickEdit enabled (the default), clicking the console window enters
|
||||
/// mark/selection mode and silently blocks every console write from this
|
||||
/// process until the user presses Enter — making the gateway look frozen.
|
||||
/// Disabling it keeps keyboard input working while preventing mouse-click
|
||||
/// freezes. No-op on non-Windows platforms or when no console is attached.
|
||||
///
|
||||
/// If stdin is redirected (so `GetStdHandle(STD_INPUT_HANDLE)` is not a
|
||||
/// console handle), falls back to opening `CONIN$` to reach the attached
|
||||
/// console directly. Note that a manual "Edit → Mark" from the title-bar
|
||||
/// menu can still freeze console writes; non-blocking log writers in
|
||||
/// `logging.rs` keep the runtime alive in that case.
|
||||
pub fn disable_console_quick_edit() {
|
||||
#[cfg(target_os = "windows")]
|
||||
windows_disable_console_quick_edit();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn windows_disable_console_quick_edit() {
|
||||
const STD_INPUT_HANDLE: i32 = -10;
|
||||
const ENABLE_QUICK_EDIT_MODE: u32 = 0x0040;
|
||||
const ENABLE_EXTENDED_FLAGS: u32 = 0x0080;
|
||||
const GENERIC_READ: u32 = 0x8000_0000;
|
||||
const GENERIC_WRITE: u32 = 0x4000_0000;
|
||||
const FILE_SHARE_READ: u32 = 0x0000_0001;
|
||||
const FILE_SHARE_WRITE: u32 = 0x0000_0002;
|
||||
const OPEN_EXISTING: u32 = 3;
|
||||
const INVALID_HANDLE_VALUE: isize = -1;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
unsafe extern "system" {
|
||||
fn GetStdHandle(n_std_handle: i32) -> isize;
|
||||
fn GetConsoleMode(h_console_handle: isize, lp_mode: *mut u32) -> i32;
|
||||
fn SetConsoleMode(h_console_handle: isize, dw_mode: u32) -> i32;
|
||||
fn CreateFileW(
|
||||
lp_file_name: *const u16,
|
||||
dw_desired_access: u32,
|
||||
dw_share_mode: u32,
|
||||
lp_security_attributes: *const std::ffi::c_void,
|
||||
dw_creation_disposition: u32,
|
||||
dw_flags_and_attributes: u32,
|
||||
h_template_file: isize,
|
||||
) -> isize;
|
||||
fn CloseHandle(h_object: isize) -> i32;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut mode: u32 = 0;
|
||||
let mut opened_conin = false;
|
||||
|
||||
let stdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||
let console =
|
||||
if stdin != 0 && stdin != INVALID_HANDLE_VALUE && GetConsoleMode(stdin, &mut mode) != 0
|
||||
{
|
||||
stdin
|
||||
} else {
|
||||
// stdin is redirected/closed but a console may still be attached
|
||||
// (e.g. output shown in a window started via `start /B`). Open
|
||||
// CONIN$ to reach the console input buffer directly.
|
||||
let name: Vec<u16> = "CONIN$\0".encode_utf16().collect();
|
||||
let conin = CreateFileW(
|
||||
name.as_ptr(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
std::ptr::null(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
if conin == INVALID_HANDLE_VALUE || GetConsoleMode(conin, &mut mode) == 0 {
|
||||
if conin != INVALID_HANDLE_VALUE {
|
||||
let _ = CloseHandle(conin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
opened_conin = true;
|
||||
conin
|
||||
};
|
||||
|
||||
if mode & ENABLE_QUICK_EDIT_MODE != 0 {
|
||||
let new_mode = (mode & !ENABLE_QUICK_EDIT_MODE) | ENABLE_EXTENDED_FLAGS;
|
||||
let _ = SetConsoleMode(console, new_mode);
|
||||
}
|
||||
|
||||
// The mode change is a property of the console itself, so the
|
||||
// CONIN$ handle can be released immediately.
|
||||
if opened_conin {
|
||||
let _ = CloseHandle(console);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the user's home directory.
|
||||
///
|
||||
/// Supports environment variable overrides for testing:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user