mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-19 13:01:15 -03:00
Time out terminal queries after a while
Add a timeout of 2 seconds queries; if any query takes longer, warn about that and reduce the timeout so we stop blocking the UI. This 2 second delay could also happen when network latency is momentarily really high, so we might want relax this in future. Note that this timeout is only triggered by a single uninterrupted poll() (and measured from the start of poll(), which should happen shortly after sending the query). Any polls interrupted by signals or uvars/IO port before the timeout would be hit do not matter. We could change this in future. Closes #11108 Closes #11117
This commit is contained in:
@@ -2,13 +2,53 @@
|
||||
use proc_macro::TokenStream;
|
||||
use std::{ffi::OsString, fs::OpenOptions, io::Write};
|
||||
|
||||
fn unescape_multiline_rust_string(s: String) -> String {
|
||||
if !s.contains('\n') {
|
||||
return s;
|
||||
}
|
||||
let mut unescaped = String::new();
|
||||
enum State {
|
||||
Ground,
|
||||
Escaped,
|
||||
ContinuationLineLeadingWhitespace,
|
||||
}
|
||||
use State::*;
|
||||
let mut state = Ground;
|
||||
for c in s.chars() {
|
||||
match state {
|
||||
Ground => match c {
|
||||
'\\' => state = Escaped,
|
||||
_ => {
|
||||
unescaped.push(c);
|
||||
}
|
||||
},
|
||||
Escaped => match c {
|
||||
'\\' => {
|
||||
unescaped.push('\\');
|
||||
state = Ground
|
||||
}
|
||||
'\n' => state = ContinuationLineLeadingWhitespace,
|
||||
_ => panic!("Unsupported escape sequence '\\{c}' in message string '{s}'"),
|
||||
},
|
||||
ContinuationLineLeadingWhitespace => match c {
|
||||
' ' | '\t' => (),
|
||||
_ => {
|
||||
unescaped.push(c);
|
||||
state = Ground
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
unescaped
|
||||
}
|
||||
|
||||
fn append_po_entry_to_file(message: &TokenStream, file_name: &OsString) {
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(file_name)
|
||||
.unwrap_or_else(|e| panic!("Could not open file {file_name:?}: {e}"));
|
||||
let message_string = message.to_string();
|
||||
let message_string = unescape_multiline_rust_string(message.to_string());
|
||||
if message_string.contains('\n') {
|
||||
panic!("Gettext strings may not contain unescaped newlines. Unescaped newline found in '{message_string}'")
|
||||
}
|
||||
|
||||
5
po/de.po
5
po/de.po
@@ -816,6 +816,11 @@ msgstr ""
|
||||
msgid "%s and %s are mutually exclusive"
|
||||
msgstr ""
|
||||
|
||||
#
|
||||
#, c-format
|
||||
msgid "%s could not read response to primary device attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s, version %s"
|
||||
msgstr ""
|
||||
|
||||
5
po/en.po
5
po/en.po
@@ -814,6 +814,11 @@ msgstr ""
|
||||
msgid "%s and %s are mutually exclusive"
|
||||
msgstr ""
|
||||
|
||||
#
|
||||
#, c-format
|
||||
msgid "%s could not read response to primary device attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s, version %s"
|
||||
msgstr ""
|
||||
|
||||
5
po/fr.po
5
po/fr.po
@@ -915,6 +915,11 @@ msgstr ""
|
||||
msgid "%s and %s are mutually exclusive"
|
||||
msgstr ""
|
||||
|
||||
#
|
||||
#, c-format
|
||||
msgid "%s could not read response to primary device attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s, version %s"
|
||||
msgstr ""
|
||||
|
||||
5
po/pl.po
5
po/pl.po
@@ -810,6 +810,11 @@ msgstr ""
|
||||
msgid "%s and %s are mutually exclusive"
|
||||
msgstr ""
|
||||
|
||||
#
|
||||
#, c-format
|
||||
msgid "%s could not read response to primary device attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s, version %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -815,6 +815,11 @@ msgstr ""
|
||||
msgid "%s and %s are mutually exclusive"
|
||||
msgstr ""
|
||||
|
||||
#
|
||||
#, c-format
|
||||
msgid "%s could not read response to primary device attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s, version %s"
|
||||
msgstr ""
|
||||
|
||||
5
po/sv.po
5
po/sv.po
@@ -811,6 +811,11 @@ msgstr ""
|
||||
msgid "%s and %s are mutually exclusive"
|
||||
msgstr ""
|
||||
|
||||
#
|
||||
#, c-format
|
||||
msgid "%s could not read response to primary device attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s, version %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -808,6 +808,11 @@ msgstr ""
|
||||
msgid "%s and %s are mutually exclusive"
|
||||
msgstr ""
|
||||
|
||||
#
|
||||
#, c-format
|
||||
msgid "%s could not read response to primary device attribute query after waiting for %d seconds. This is often due to a missing feature in your terminal. See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. This %s process will no longer wait for outstanding queries, which disables some optional features."
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s, version %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
future_feature_flags,
|
||||
input_common::{
|
||||
match_key_event_to_key, CharEvent, InputEventQueue, InputEventQueuer, KeyEvent,
|
||||
QueryResponseEvent, TerminalQuery,
|
||||
QueryResponse, QueryResultEvent, TerminalQuery,
|
||||
},
|
||||
key::{char_to_symbol, Key},
|
||||
nix::isatty,
|
||||
@@ -95,16 +95,17 @@ fn process_input(streams: &mut IoStreams, continuous_mode: bool, verbose: bool)
|
||||
handoff.enable_tty_protocols();
|
||||
|
||||
while (!first_char_seen || continuous_mode) && !check_exit_loop_maybe_warning(None) {
|
||||
use QueryResultEvent::*;
|
||||
let kevt = match queue.readch() {
|
||||
CharEvent::Key(kevt) => kevt,
|
||||
CharEvent::Readline(_) | CharEvent::Command(_) | CharEvent::Implicit(_) => continue,
|
||||
CharEvent::QueryResponse(QueryResponseEvent::PrimaryDeviceAttributeResponse) => {
|
||||
CharEvent::QueryResult(Response(QueryResponse::PrimaryDeviceAttribute) | Timeout) => {
|
||||
if get_kitty_keyboard_capability() == Capability::Unknown {
|
||||
set_kitty_keyboard_capability(|| {}, Capability::NotSupported);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
CharEvent::QueryResponse(_) => continue,
|
||||
CharEvent::QueryResult(_) => continue,
|
||||
};
|
||||
if verbose {
|
||||
streams.out.append(L!("# decoded from: "));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
use std::os::unix::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Timeout {
|
||||
Duration(Duration),
|
||||
Forever,
|
||||
|
||||
@@ -781,7 +781,7 @@ pub fn read_char(&mut self) -> CharEvent {
|
||||
match evt {
|
||||
Key(_) => true,
|
||||
Implicit(Eof) => true,
|
||||
Readline(_) | Command(_) | Implicit(_) | QueryResponse(_) => false,
|
||||
Readline(_) | Command(_) | Implicit(_) | QueryResult(_) => false,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -823,7 +823,7 @@ pub fn read_char(&mut self) -> CharEvent {
|
||||
self.push_front(evt);
|
||||
self.mapping_execute_matching_or_generic();
|
||||
}
|
||||
CharEvent::Implicit(_) | CharEvent::QueryResponse(_) => {
|
||||
CharEvent::Implicit(_) | CharEvent::QueryResult(_) => {
|
||||
return evt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::common::{
|
||||
fish_reserved_codepoint, is_windows_subsystem_for_linux, read_blocked, shell_modes,
|
||||
str2wcstring, WSL,
|
||||
str2wcstring, PROGRAM_NAME, WSL,
|
||||
};
|
||||
use crate::env::{EnvStack, Environment};
|
||||
use crate::fd_readable_set::{FdReadableSet, Timeout};
|
||||
use crate::flog::{FloggableDebug, FloggableDisplay, FLOG};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::key::{
|
||||
self, alt, canonicalize_control_char, canonicalize_keyed_control_char, char_to_symbol,
|
||||
function_key, shift, Key, Modifiers, ViewportPosition,
|
||||
@@ -411,9 +412,15 @@ pub enum ImplicitEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum QueryResponseEvent {
|
||||
PrimaryDeviceAttributeResponse,
|
||||
CursorPositionResponse(ViewportPosition),
|
||||
pub enum QueryResponse {
|
||||
PrimaryDeviceAttribute,
|
||||
CursorPosition(ViewportPosition),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum QueryResultEvent {
|
||||
Response(QueryResponse),
|
||||
Timeout,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -430,7 +437,7 @@ pub enum CharEvent {
|
||||
/// Any event that has no user-visible representation.
|
||||
Implicit(ImplicitEvent),
|
||||
|
||||
QueryResponse(QueryResponseEvent),
|
||||
QueryResult(QueryResultEvent),
|
||||
}
|
||||
impl FloggableDebug for CharEvent {}
|
||||
|
||||
@@ -532,6 +539,9 @@ enum InputEventTrigger {
|
||||
|
||||
// Our ioport reported a change, so service main thread requests.
|
||||
IOPortNotified,
|
||||
|
||||
// No file descriptor was ready within the query timeout.
|
||||
TimeoutElapsed,
|
||||
}
|
||||
|
||||
fn readb(in_fd: RawFd) -> Option<u8> {
|
||||
@@ -547,7 +557,7 @@ fn readb(in_fd: RawFd) -> Option<u8> {
|
||||
Some(c)
|
||||
}
|
||||
|
||||
fn next_input_event(in_fd: RawFd) -> InputEventTrigger {
|
||||
fn next_input_event(in_fd: RawFd, timeout: Timeout) -> InputEventTrigger {
|
||||
let mut fdset = FdReadableSet::new();
|
||||
loop {
|
||||
fdset.clear();
|
||||
@@ -565,7 +575,7 @@ fn next_input_event(in_fd: RawFd) -> InputEventTrigger {
|
||||
}
|
||||
|
||||
// Here's where we call select().
|
||||
let select_res = fdset.check_readable(Timeout::Forever);
|
||||
let select_res = fdset.check_readable(timeout);
|
||||
if select_res < 0 {
|
||||
let err = errno::errno().0;
|
||||
if err == libc::EINTR || err == libc::EAGAIN {
|
||||
@@ -576,6 +586,10 @@ fn next_input_event(in_fd: RawFd) -> InputEventTrigger {
|
||||
return InputEventTrigger::Eof;
|
||||
}
|
||||
}
|
||||
if select_res == 0 {
|
||||
assert!(!matches!(timeout, Timeout::Forever));
|
||||
return InputEventTrigger::TimeoutElapsed;
|
||||
}
|
||||
|
||||
// select() did not return an error, so we may have a readable fd.
|
||||
// The priority order is: uvars, stdin, ioport.
|
||||
@@ -787,7 +801,7 @@ fn try_pop(&mut self) -> Option<CharEvent> {
|
||||
if self.is_blocked_querying() {
|
||||
use ImplicitEvent::*;
|
||||
match self.get_input_data().queue.front()? {
|
||||
CharEvent::QueryResponse(_) | CharEvent::Implicit(CheckExit | Eof) => {}
|
||||
CharEvent::QueryResult(_) | CharEvent::Implicit(CheckExit | Eof) => {}
|
||||
CharEvent::Key(_)
|
||||
| CharEvent::Readline(_)
|
||||
| CharEvent::Command(_)
|
||||
@@ -817,7 +831,26 @@ fn readch(&mut self) -> CharEvent {
|
||||
return mevt;
|
||||
}
|
||||
|
||||
match next_input_event(self.get_in_fd()) {
|
||||
const INITIAL_QUERY_TIMEOUT_SECONDS: u64 = 2;
|
||||
static ABANDON_WAITING_FOR_QUERIES: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
match next_input_event(
|
||||
self.get_in_fd(),
|
||||
if self.is_blocked_querying() {
|
||||
Timeout::Duration(if ABANDON_WAITING_FOR_QUERIES.load() {
|
||||
// This should small enough so the delay on incompatible terminals is
|
||||
// not noticeable, and high enough so we can still receive some query
|
||||
// responses if we ever get into this state on a compatible terminal,
|
||||
// which can happen after extreme (network) latency exceeds our initial
|
||||
// timeout. In future, we should tolerate this better.
|
||||
Duration::from_millis(30)
|
||||
} else {
|
||||
Duration::from_secs(INITIAL_QUERY_TIMEOUT_SECONDS)
|
||||
})
|
||||
} else {
|
||||
Timeout::Forever
|
||||
},
|
||||
) {
|
||||
InputEventTrigger::Eof => {
|
||||
return CharEvent::Implicit(ImplicitEvent::Eof);
|
||||
}
|
||||
@@ -859,10 +892,12 @@ fn readch(&mut self) -> CharEvent {
|
||||
let mut i = 0;
|
||||
let ok = loop {
|
||||
if i == buffer.len() {
|
||||
buffer.push(match next_input_event(self.get_in_fd()) {
|
||||
InputEventTrigger::Byte(b) => b,
|
||||
_ => 0,
|
||||
});
|
||||
buffer.push(
|
||||
match next_input_event(self.get_in_fd(), Timeout::Forever) {
|
||||
InputEventTrigger::Byte(b) => b,
|
||||
_ => 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
match decode_input_byte(
|
||||
&mut seq,
|
||||
@@ -935,6 +970,26 @@ fn readch(&mut self) -> CharEvent {
|
||||
extra.map(|extra| self.insert_front(extra));
|
||||
return key_evt;
|
||||
}
|
||||
InputEventTrigger::TimeoutElapsed => {
|
||||
if !ABANDON_WAITING_FOR_QUERIES.load() {
|
||||
let program = PROGRAM_NAME.get().unwrap();
|
||||
FLOG!(
|
||||
warning,
|
||||
wgettext_fmt!(
|
||||
"%s could not read response to primary device attribute query after waiting for %d seconds. \
|
||||
This is often due to a missing feature in your terminal. \
|
||||
See 'help terminal-compatibility' or 'man fish-terminal-compatibility'. \
|
||||
This %s process will no longer wait for outstanding queries, \
|
||||
which disables some optional features.",
|
||||
program,
|
||||
INITIAL_QUERY_TIMEOUT_SECONDS,
|
||||
program
|
||||
),
|
||||
);
|
||||
ABANDON_WAITING_FOR_QUERIES.store(true);
|
||||
}
|
||||
return CharEvent::QueryResult(QueryResultEvent::Timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1176,9 +1231,9 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
};
|
||||
FLOG!(reader, "Received cursor position report y:", y, "x:", x);
|
||||
let cursor_pos = ViewportPosition { x, y };
|
||||
self.push_front(CharEvent::QueryResponse(
|
||||
QueryResponseEvent::CursorPositionResponse(cursor_pos),
|
||||
));
|
||||
self.push_front(CharEvent::QueryResult(QueryResultEvent::Response(
|
||||
QueryResponse::CursorPosition(cursor_pos),
|
||||
)));
|
||||
return None;
|
||||
}
|
||||
b'S' => masked_key(function_key(4)),
|
||||
@@ -1229,9 +1284,9 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
_ => return None,
|
||||
},
|
||||
b'c' if private_mode == Some(b'?') => {
|
||||
self.push_front(CharEvent::QueryResponse(
|
||||
QueryResponseEvent::PrimaryDeviceAttributeResponse,
|
||||
));
|
||||
self.push_front(CharEvent::QueryResult(QueryResultEvent::Response(
|
||||
QueryResponse::PrimaryDeviceAttribute,
|
||||
)));
|
||||
return None;
|
||||
}
|
||||
b'u' => {
|
||||
|
||||
@@ -86,9 +86,10 @@
|
||||
SearchFlags, SearchType,
|
||||
};
|
||||
use crate::input::init_input;
|
||||
use crate::input_common::QueryResponse;
|
||||
use crate::input_common::{
|
||||
stop_query, CharEvent, CharInputStyle, CursorPositionQuery, CursorPositionQueryKind,
|
||||
ImplicitEvent, InputData, QueryResponseEvent, ReadlineCmd, TerminalQuery,
|
||||
ImplicitEvent, InputData, QueryResultEvent, ReadlineCmd, TerminalQuery,
|
||||
};
|
||||
use crate::io::IoChain;
|
||||
use crate::key::ViewportPosition;
|
||||
@@ -2569,12 +2570,13 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
);
|
||||
}
|
||||
},
|
||||
CharEvent::QueryResponse(query_result) => {
|
||||
CharEvent::QueryResult(query_result) => {
|
||||
let mut maybe_query = self.blocking_query();
|
||||
let query = &mut maybe_query;
|
||||
use QueryResponseEvent::*;
|
||||
use QueryResponse::*;
|
||||
use QueryResultEvent::*;
|
||||
let query = match (&mut **query, query_result) {
|
||||
(Some(TerminalQuery::Initial), PrimaryDeviceAttributeResponse) => {
|
||||
(Some(TerminalQuery::Initial), Response(PrimaryDeviceAttribute) | Timeout) => {
|
||||
if get_kitty_keyboard_capability() == Capability::Unknown {
|
||||
set_kitty_keyboard_capability(
|
||||
reader_save_screen_state,
|
||||
@@ -2585,14 +2587,14 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
}
|
||||
(
|
||||
Some(TerminalQuery::CursorPosition(cursor_pos_query)),
|
||||
CursorPositionResponse(cursor_pos),
|
||||
Response(CursorPosition(cursor_pos)),
|
||||
) => {
|
||||
cursor_pos_query.result = Some(cursor_pos);
|
||||
maybe_query
|
||||
}
|
||||
(
|
||||
Some(TerminalQuery::CursorPosition(cursor_pos_query)),
|
||||
PrimaryDeviceAttributeResponse,
|
||||
Response(PrimaryDeviceAttribute) | Timeout,
|
||||
) => {
|
||||
let cursor_pos_query = cursor_pos_query.clone();
|
||||
drop(maybe_query);
|
||||
|
||||
Reference in New Issue
Block a user