Cancel an async request on timeout so no spurious data is sent to the tty
This commit is contained in:
parent
2a637e4220
commit
6241369b6c
@ -108,6 +108,22 @@ class ParsingOfArgsFailed(ValueError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncResponder:
|
||||||
|
|
||||||
|
def __init__(self, payload_get: PayloadGetType, window: Optional[Window]) -> None:
|
||||||
|
self.async_id: str = payload_get('async_id', missing='')
|
||||||
|
self.peer_id: int = payload_get('peer_id', missing=0)
|
||||||
|
self.window_id: int = getattr(window, 'id', 0)
|
||||||
|
|
||||||
|
def send_data(self, data: Any) -> None:
|
||||||
|
from kitty.remote_control import send_response_to_client
|
||||||
|
send_response_to_client(data=data, peer_id=self.peer_id, window_id=self.window_id, async_id=self.async_id)
|
||||||
|
|
||||||
|
def send_error(self, error: str) -> None:
|
||||||
|
from kitty.remote_control import send_response_to_client
|
||||||
|
send_response_to_client(error=error, peer_id=self.peer_id, window_id=self.window_id, async_id=self.async_id)
|
||||||
|
|
||||||
|
|
||||||
class RemoteCommand:
|
class RemoteCommand:
|
||||||
|
|
||||||
name: str = ''
|
name: str = ''
|
||||||
@ -121,6 +137,7 @@ class RemoteCommand:
|
|||||||
args_count: Optional[int] = None
|
args_count: Optional[int] = None
|
||||||
args_completion: Optional[Dict[str, Tuple[str, Union[Callable[[], Iterable[str]], Tuple[str, ...]]]]] = None
|
args_completion: Optional[Dict[str, Tuple[str, Union[Callable[[], Iterable[str]], Tuple[str, ...]]]]] = None
|
||||||
defaults: Optional[Dict[str, Any]] = None
|
defaults: Optional[Dict[str, Any]] = None
|
||||||
|
is_asynchronous: bool = False
|
||||||
options_class: Type[RCOptions] = RCOptions
|
options_class: Type[RCOptions] = RCOptions
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -191,6 +208,9 @@ class RemoteCommand:
|
|||||||
windows += list(tab)
|
windows += list(tab)
|
||||||
return windows
|
return windows
|
||||||
|
|
||||||
|
def create_async_responder(self, payload_get: PayloadGetType, window: Optional[Window]) -> AsyncResponder:
|
||||||
|
return AsyncResponder(payload_get, window)
|
||||||
|
|
||||||
def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType:
|
def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions,
|
MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions,
|
||||||
RemoteCommand, ResponseType, Window, no_response
|
RemoteCommand, ResponseType, Window, no_response
|
||||||
@ -39,21 +37,20 @@ type=bool-set
|
|||||||
If specified the tab containing the window this command is run in is used
|
If specified the tab containing the window this command is run in is used
|
||||||
instead of the active tab.
|
instead of the active tab.
|
||||||
'''
|
'''
|
||||||
|
is_asynchronous = True
|
||||||
|
|
||||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||||
ans = {'self': opts.self, 'match': opts.match}
|
ans = {'self': opts.self, 'match': opts.match}
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
||||||
peer_id: int = payload_get('peer_id', missing=0)
|
responder = self.create_async_responder(payload_get, window)
|
||||||
window_id: int = getattr(window, 'id', 0)
|
|
||||||
|
|
||||||
def callback(tab: Optional['Tab'], window: Optional[Window]) -> None:
|
def callback(tab: Optional['Tab'], window: Optional[Window]) -> None:
|
||||||
from kitty.remote_control import send_response_to_client
|
|
||||||
if window:
|
if window:
|
||||||
send_response_to_client(data=window.id, peer_id=peer_id, window_id=window_id)
|
responder.send_data(window.id)
|
||||||
else:
|
else:
|
||||||
send_response_to_client(error='No window selected', peer_id=peer_id, window_id=window_id)
|
responder.send_error('No window selected')
|
||||||
for tab in self.tabs_for_match_payload(boss, window, payload_get):
|
for tab in self.tabs_for_match_payload(boss, window, payload_get):
|
||||||
if tab:
|
if tab:
|
||||||
boss.visual_window_select_action(tab, callback, 'Choose window')
|
boss.visual_window_select_action(tab, callback, 'Choose window')
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
from time import monotonic
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import (
|
from typing import (
|
||||||
@ -24,6 +25,9 @@ from .typing import BossType, WindowType
|
|||||||
from .utils import TTYIO, parse_address_spec
|
from .utils import TTYIO, parse_address_spec
|
||||||
|
|
||||||
|
|
||||||
|
active_async_requests: Dict[str, float] = {}
|
||||||
|
|
||||||
|
|
||||||
def encode_response_for_peer(response: Any) -> bytes:
|
def encode_response_for_peer(response: Any) -> bytes:
|
||||||
import json
|
import json
|
||||||
return b'\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\x1b\\'
|
return b'\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\x1b\\'
|
||||||
@ -40,7 +44,16 @@ def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str
|
|||||||
c = command_for_name(cmd['cmd'])
|
c = command_for_name(cmd['cmd'])
|
||||||
payload = cmd.get('payload') or {}
|
payload = cmd.get('payload') or {}
|
||||||
payload['peer_id'] = peer_id
|
payload['peer_id'] = peer_id
|
||||||
|
async_id = str(cmd.get('async', ''))
|
||||||
|
if async_id:
|
||||||
|
if 'cancel_async' in cmd:
|
||||||
|
active_async_requests.pop(async_id, None)
|
||||||
|
return None
|
||||||
|
active_async_requests[async_id] = monotonic()
|
||||||
|
payload['async_id'] = async_id
|
||||||
|
if len(active_async_requests) > 32:
|
||||||
|
oldest = next(iter(active_async_requests))
|
||||||
|
del active_async_requests[oldest]
|
||||||
try:
|
try:
|
||||||
ans = c.response_from_kitty(boss, window, PayloadGetter(c, payload))
|
ans = c.response_from_kitty(boss, window, PayloadGetter(c, payload))
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -159,14 +172,20 @@ def parse_rc_args(args: List[str]) -> Tuple[RCOptions, List[str]]:
|
|||||||
return parse_args(args[1:], global_options_spec, 'command ...', msg, f'{appname} @', result_class=RCOptions)
|
return parse_args(args[1:], global_options_spec, 'command ...', msg, f'{appname} @', result_class=RCOptions)
|
||||||
|
|
||||||
|
|
||||||
def create_basic_command(name: str, payload: Any = None, no_response: bool = False) -> Dict[str, Any]:
|
def create_basic_command(name: str, payload: Any = None, no_response: bool = False, is_asynchronous: bool = False) -> Dict[str, Any]:
|
||||||
ans = {'cmd': name, 'version': version, 'no_response': no_response}
|
ans = {'cmd': name, 'version': version, 'no_response': no_response}
|
||||||
if payload is not None:
|
if payload is not None:
|
||||||
ans['payload'] = payload
|
ans['payload'] = payload
|
||||||
|
if is_asynchronous:
|
||||||
|
from kitty.short_uuid import uuid4
|
||||||
|
ans['async'] = uuid4()
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def send_response_to_client(data: Any = None, error: str = '', peer_id: int = 0, window_id: int = 0) -> None:
|
def send_response_to_client(data: Any = None, error: str = '', peer_id: int = 0, window_id: int = 0, async_id: str = '') -> None:
|
||||||
|
ts = active_async_requests.pop(async_id, None)
|
||||||
|
if ts is None:
|
||||||
|
return
|
||||||
if error:
|
if error:
|
||||||
response: Dict[str, Union[bool, int, str]] = {'ok': False, 'error': error}
|
response: Dict[str, Union[bool, int, str]] = {'ok': False, 'error': error}
|
||||||
else:
|
else:
|
||||||
@ -203,13 +222,16 @@ def main(args: List[str]) -> None:
|
|||||||
response_timeout = c.response_timeout
|
response_timeout = c.response_timeout
|
||||||
if hasattr(opts, 'response_timeout'):
|
if hasattr(opts, 'response_timeout'):
|
||||||
response_timeout = opts.response_timeout
|
response_timeout = opts.response_timeout
|
||||||
send = create_basic_command(cmd, payload=payload, no_response=no_response)
|
send = create_basic_command(cmd, payload=payload, no_response=no_response, is_asynchronous=c.is_asynchronous)
|
||||||
if not global_opts.to and 'KITTY_LISTEN_ON' in os.environ:
|
if not global_opts.to and 'KITTY_LISTEN_ON' in os.environ:
|
||||||
global_opts.to = os.environ['KITTY_LISTEN_ON']
|
global_opts.to = os.environ['KITTY_LISTEN_ON']
|
||||||
import socket
|
import socket
|
||||||
try:
|
try:
|
||||||
response = do_io(global_opts.to, send, no_response, response_timeout)
|
response = do_io(global_opts.to, send, no_response, response_timeout)
|
||||||
except (TimeoutError, socket.timeout):
|
except (TimeoutError, socket.timeout):
|
||||||
|
send.pop('payload', None)
|
||||||
|
send['cancel_async'] = True
|
||||||
|
do_io(global_opts.to, send, True, 10)
|
||||||
raise SystemExit(f'Timed out after {response_timeout} seconds waiting for response form kitty')
|
raise SystemExit(f'Timed out after {response_timeout} seconds waiting for response form kitty')
|
||||||
if no_response:
|
if no_response:
|
||||||
return
|
return
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user