diff --git a/autoload/vimspector/internal/channel.vim b/autoload/vimspector/internal/channel.vim index eb7cc9d..c58ff64 100644 --- a/autoload/vimspector/internal/channel.vim +++ b/autoload/vimspector/internal/channel.vim @@ -42,6 +42,12 @@ function! s:_Send( msg ) abort call ch_sendraw( s:ch, a:msg ) endfunction +function! vimspector#internal#channel#Timeout( id ) abort + py3 << EOF +_vimspector_session.OnRequestTimeout( vim.eval( 'a:id' ) ) +EOF +endfunction + function! vimspector#internal#channel#StartDebugSession( config ) abort if exists( 's:ch' ) diff --git a/autoload/vimspector/internal/job.vim b/autoload/vimspector/internal/job.vim index d252629..68aa58e 100644 --- a/autoload/vimspector/internal/job.vim +++ b/autoload/vimspector/internal/job.vim @@ -55,7 +55,7 @@ endfunction function! vimspector#internal#job#StartDebugSession( config ) abort if exists( 's:job' ) - echo "Job is already running" + echom "Job is already running" return v:none endif @@ -81,6 +81,10 @@ function! vimspector#internal#job#StartDebugSession( config ) abort endfunction function! vimspector#internal#job#StopDebugSession() abort + if ! exists( 's:job' ) + return + endfunction + if job_status( s:job ) == 'run' call job_stop( s:job, 'term' ) endif diff --git a/python3/vimspector/debug_adapter_connection.py b/python3/vimspector/debug_adapter_connection.py index 1932b5c..0cddf80 100644 --- a/python3/vimspector/debug_adapter_connection.py +++ b/python3/vimspector/debug_adapter_connection.py @@ -15,13 +15,17 @@ import logging import json - -from collections import namedtuple +import vim from vimspector import utils -PendingRequest = namedtuple( 'PendingRequest', - [ 'msg', 'handler', 'failure_handler' ] ) + +class PendingRequest( object ): + def __init__( self, msg, handler, failure_handler, expiry_id ): + self.msg = msg + self.handler = handler + self.failure_handler = failure_handler + self.expiry_id = expiry_id class DebugAdapterConnection( object ): @@ -36,18 +40,40 @@ class DebugAdapterConnection( object ): self._next_message_id = 0 self._outstanding_requests = {} - def DoRequest( self, handler, msg, failure_handler=None ): + def DoRequest( self, + handler, + msg, + failure_handler=None, + timeout = 5000 ): this_id = self._next_message_id self._next_message_id += 1 msg[ 'seq' ] = this_id msg[ 'type' ] = 'request' + # TODO/FIXME: This is so messy + expiry_id = vim.eval( + 'timer_start( {}, "vimspector#internal#channel#Timeout" )'.format( + timeout ) ) + self._outstanding_requests[ this_id ] = PendingRequest( msg, handler, - failure_handler ) + failure_handler, + expiry_id ) self._SendMessage( msg ) + def OnRequestTimeout( self, timer_id ): + request_id = None + for seq, request in self._outstanding_requests.items(): + if request.expiry_id == timer_id: + self._AbortRequest( request, 'Timeout' ) + request_id = seq + break + + # Avoid modifying _outstanding_requests while looping + if request_id is not None: + del self._outstanding_requests[ request_id ] + def DoResponse( self, request, error, response ): this_id = self._next_message_id self._next_message_id += 1 @@ -69,6 +95,21 @@ class DebugAdapterConnection( object ): def Reset( self ): self._Write = None self._handler = None + for _, request in self._outstanding_requests.items(): + self._AbortRequest( request, 'Closing down' ) + self._outstanding_requests.clear() + + def _AbortRequest( self, request, reason ): + self._logger.debug( 'Aborting request {} because {}'.format( + json.dumps( request.msg ), + reason ) ) + + _KillTimer( request ) + if request.failure_handler: + request.failure_handler( reason, {} ) + else: + utils.UserMessage( 'Request aborted: {}'.format( reason ) ) + def OnData( self, data ): data = bytes( data, 'utf-8' ) @@ -170,6 +211,8 @@ class DebugAdapterConnection( object ): self._logger.exception( 'Duplicate response: {}'.format( message ) ) return + _KillTimer( request ) + if message[ 'success' ]: if request.handler: request.handler( message ) @@ -181,7 +224,7 @@ class DebugAdapterConnection( object ): # TODO: Actually make this work reason = fmt else: - message = 'No reason' + reason = 'No reason' self._logger.error( 'Request failed: {0}'.format( reason ) ) if request.failure_handler: @@ -203,3 +246,9 @@ class DebugAdapterConnection( object ): utils.UserMessage( 'Unhandled request: {0}'.format( message[ 'command' ] ), persist = True ) + + +def _KillTimer( request ): + if request.expiry_id is not None: + vim.eval( 'timer_stop( {} )'.format( request.expiry_id ) ) + request.expiry_id = None diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index f3b9cd0..1019b33 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -189,6 +189,10 @@ class DebugSession( object ): if self._connection: self._connection.OnData( data ) + def OnRequestTimeout( self, timer_id ): + if self._connection: + self._connection.OnRequestTimeout( timer_id ) + def OnChannelClosed( self ): self._connection = None @@ -384,7 +388,7 @@ class DebugSession( object ): # scope) state = { 'done': False } - def handler( self ): + def handler( *args ): state[ 'done' ] = True self._connection.DoRequest( handler, { @@ -392,11 +396,10 @@ class DebugSession( object ): 'arguments': { 'terminateDebugee': True }, - } ) + }, failure_handler = handler ) - tries = 0 - while not state[ 'done' ] and tries < 10: - tries = tries + 1 + # This request times out after 5 seconds + while not state[ 'done' ]: vim.eval( 'vimspector#internal#{}#ForceRead()'.format( self._connection_type ) ) @@ -404,7 +407,7 @@ class DebugSession( object ): self._connection_type ) ) def _StopDebugAdapter( self, callback = None ): - def handler( message ): + def handler( *args ): vim.eval( 'vimspector#internal#{}#StopDebugSession()'.format( self._connection_type ) ) @@ -423,7 +426,7 @@ class DebugSession( object ): 'arguments': { 'terminateDebugee': True }, - } ) + }, failure_handler = handler ) def _SelectProcess( self, adapter_config, launch_config ): atttach_config = adapter_config[ 'attach' ]