@@ -83,7 +83,7 @@ def __init__(
83
83
84
84
self .__installing_server = False
85
85
self .__starting_server = False
86
- self .__creating_connection = False
86
+ self .__connection_task : asyncio . Task | None = None
87
87
88
88
# For logging
89
89
self .logger = logging .getLogger (
@@ -100,9 +100,10 @@ def __init__(
100
100
async def __create_events (self ):
101
101
self .__server_installed = asyncio .Event ()
102
102
self .__starting_event = asyncio .Event ()
103
- self .__connection_established = asyncio .Event ()
103
+ self .__abort_requested = asyncio .Event ()
104
+ self .__lock = asyncio .Lock ()
104
105
105
- def _emit_connection_status (self , status , message ):
106
+ def _emit_connection_status (self , status : ConnectionStatus , message : str ):
106
107
if self ._plugin is not None :
107
108
self ._plugin .sig_connection_status_changed .emit (
108
109
ConnectionInfo (
@@ -119,11 +120,22 @@ def server_started(self):
119
120
return self .__starting_event .is_set () and not self .__starting_server
120
121
121
122
@property
122
- def connected (self ):
123
- """Check if SSH connection is open ."""
123
+ def connected (self ) -> bool :
124
+ """Check if a connection is currently established ."""
124
125
return (
125
- self .__connection_established .is_set ()
126
- and not self .__creating_connection
126
+ self .__connection_task is not None
127
+ and self .__connection_task .done ()
128
+ and not self .__connection_task .cancelled ()
129
+ and not self .__connection_task .exception ()
130
+ and self .__connection_task .result () is True
131
+ )
132
+
133
+ @property
134
+ def is_connecting (self ) -> bool :
135
+ """Check if a connection attempt is ongoing."""
136
+ return (
137
+ self .__connection_task is not None
138
+ and not self .__connection_task .done ()
127
139
)
128
140
129
141
@property
@@ -162,7 +174,7 @@ async def close(self):
162
174
await self .close_connection ()
163
175
164
176
def _handle_connection_lost (self , exc : Exception | None = None ):
165
- self .__connection_established . clear ()
177
+ self .__connection_task = None
166
178
self .__starting_event .clear ()
167
179
self ._port_forwarder = None
168
180
if exc :
@@ -232,6 +244,10 @@ async def start_remote_server(self):
232
244
return self .server_started
233
245
234
246
self .__starting_server = True
247
+ self ._emit_connection_status (
248
+ ConnectionStatus .Starting ,
249
+ _ ("Starting Spyder remote services." ),
250
+ )
235
251
try :
236
252
if await self ._start_remote_server ():
237
253
self .__starting_event .set ()
@@ -278,20 +294,64 @@ async def _install_remote_server(self):
278
294
raise NotImplementedError (msg )
279
295
280
296
async def create_new_connection (self ) -> bool :
281
- if self .__creating_connection :
282
- await self .__connection_established .wait ()
283
- return self .connected
297
+ """Create a new connection, supporting user abort."""
298
+ if self .connected :
299
+ self .logger .debug (
300
+ "Atempting to create a new connection with an existing for %s" ,
301
+ self .server_name ,
302
+ )
303
+ await self .close_connection ()
284
304
285
- self .__creating_connection = True
286
- try :
287
- if await self ._create_new_connection ():
288
- self .__connection_established .set ()
289
- return True
290
- finally :
291
- self .__creating_connection = False
305
+ async with self .__lock :
306
+ if self .__connection_task is not None :
307
+ return self .connected
292
308
293
- self .__connection_established .clear ()
294
- return False
309
+ self ._emit_connection_status (
310
+ ConnectionStatus .Connecting ,
311
+ _ ("We're establishing the connection. Please be patient" ),
312
+ )
313
+
314
+ self .__abort_requested .clear ()
315
+ self .__connection_task = asyncio .create_task (self ._create_new_connection ())
316
+
317
+ await asyncio .wait (
318
+ [self .__connection_task , self .__abort_requested .wait ()],
319
+ return_when = asyncio .FIRST_COMPLETED ,
320
+ )
321
+
322
+ # User aborted
323
+ if self .__abort_requested .is_set ():
324
+ self .__connection_task .cancel ()
325
+ self .logger .warning ("Connection attempt aborted by user" )
326
+ self ._emit_connection_status (
327
+ ConnectionStatus .Inactive ,
328
+ _ ("The connection attempt was cancelled" ),
329
+ )
330
+ return False
331
+
332
+ # Connection completed
333
+ try :
334
+ if await self .__connection_task :
335
+ self ._emit_connection_status (
336
+ ConnectionStatus .Connected ,
337
+ _ ("The connection was successfully established" ),
338
+ )
339
+ return True
340
+ except BaseException :
341
+ self .logger .exception (
342
+ "Error creating a new connection for %s" , self .server_name
343
+ )
344
+ self ._emit_connection_status (
345
+ ConnectionStatus .Error ,
346
+ _ ("There was an error establishing the connection" ),
347
+ )
348
+
349
+ return False
350
+
351
+ async def abort_connection (self ):
352
+ """Abort an ongoing connection attempt."""
353
+ if self .is_connecting :
354
+ self .__abort_requested .set ()
295
355
296
356
@abstractmethod
297
357
async def _create_new_connection (self ) -> bool :
@@ -337,7 +397,7 @@ async def close_connection(self):
337
397
338
398
def _reset_connection_stablished (self ):
339
399
"""Reset the connection status."""
340
- self .__connection_established . clear ()
400
+ self .__connection_task = None
341
401
342
402
@staticmethod
343
403
def get_free_port ():
0 commit comments