Skip to content

Commit 46df832

Browse files
committed
feat: add method to abort connections and improve new connections handler
1 parent 72742d4 commit 46df832

File tree

3 files changed

+91
-65
lines changed

3 files changed

+91
-65
lines changed

spyder/plugins/remoteclient/api/manager/base.py

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def __init__(
8383

8484
self.__installing_server = False
8585
self.__starting_server = False
86-
self.__creating_connection = False
86+
self.__connection_task: asyncio.Task | None = None
8787

8888
# For logging
8989
self.logger = logging.getLogger(
@@ -100,9 +100,10 @@ def __init__(
100100
async def __create_events(self):
101101
self.__server_installed = asyncio.Event()
102102
self.__starting_event = asyncio.Event()
103-
self.__connection_established = asyncio.Event()
103+
self.__abort_requested = asyncio.Event()
104+
self.__lock = asyncio.Lock()
104105

105-
def _emit_connection_status(self, status, message):
106+
def _emit_connection_status(self, status: ConnectionStatus, message: str):
106107
if self._plugin is not None:
107108
self._plugin.sig_connection_status_changed.emit(
108109
ConnectionInfo(
@@ -119,11 +120,22 @@ def server_started(self):
119120
return self.__starting_event.is_set() and not self.__starting_server
120121

121122
@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."""
124125
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()
127139
)
128140

129141
@property
@@ -162,7 +174,7 @@ async def close(self):
162174
await self.close_connection()
163175

164176
def _handle_connection_lost(self, exc: Exception | None = None):
165-
self.__connection_established.clear()
177+
self.__connection_task = None
166178
self.__starting_event.clear()
167179
self._port_forwarder = None
168180
if exc:
@@ -232,6 +244,10 @@ async def start_remote_server(self):
232244
return self.server_started
233245

234246
self.__starting_server = True
247+
self._emit_connection_status(
248+
ConnectionStatus.Starting,
249+
_("Starting Spyder remote services."),
250+
)
235251
try:
236252
if await self._start_remote_server():
237253
self.__starting_event.set()
@@ -278,20 +294,64 @@ async def _install_remote_server(self):
278294
raise NotImplementedError(msg)
279295

280296
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()
284304

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
292308

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()
295355

296356
@abstractmethod
297357
async def _create_new_connection(self) -> bool:
@@ -337,7 +397,7 @@ async def close_connection(self):
337397

338398
def _reset_connection_stablished(self):
339399
"""Reset the connection status."""
340-
self.__connection_established.clear()
400+
self.__connection_task = None
341401

342402
@staticmethod
343403
def get_free_port():

spyder/plugins/remoteclient/api/manager/jupyterhub.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -217,18 +217,7 @@ async def _install_remote_server(self):
217217
return False
218218

219219
async def _create_new_connection(self) -> bool:
220-
if self.connected:
221-
self.logger.debug(
222-
"Atempting to create a new connection with an existing for %s",
223-
self.server_name,
224-
)
225-
await self.close_connection()
226-
227-
self._emit_connection_status(
228-
ConnectionStatus.Connecting,
229-
_("We're establishing the connection. Please be patient"),
230-
)
231-
220+
"""Create a new SSH connection."""
232221
self.logger.debug("Connecting to jupyterhub at %s", self.hub_url)
233222

234223
self._session = aiohttp.ClientSession(
@@ -237,13 +226,9 @@ async def _create_new_connection(self) -> bool:
237226
)
238227

239228
user_data = None
240-
try:
241-
async with self._session.get("hub/api/user") as response:
242-
if response.ok:
243-
user_data = await response.json()
244-
except aiohttp.ClientError:
245-
self.logger.exception("Error connecting to JupyterHub")
246-
return False
229+
async with self._session.get("hub/api/user") as response:
230+
if response.ok:
231+
user_data = await response.json()
247232

248233
if user_data is None:
249234
self.logger.error(

spyder/plugins/remoteclient/api/manager/ssh.py

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ async def _install_remote_server(self):
329329
return True
330330

331331
async def _create_new_connection(self) -> bool:
332-
"""Creates a new SSH connection to the remote server machine.
332+
"""Create a new SSH connection to the remote server machine.
333333
334334
Args
335335
----
@@ -341,37 +341,18 @@ async def _create_new_connection(self) -> bool:
341341
bool
342342
True if the connection was successful, False otherwise.
343343
"""
344-
if self.connected:
345-
self.logger.debug(
346-
f"Atempting to create a new connection with an existing for "
347-
f"{self.peer_host}"
348-
)
349-
await self.close_connection()
350-
351-
self._emit_connection_status(
352-
ConnectionStatus.Connecting,
353-
_("We're establishing the connection. Please be patient"),
354-
)
355-
356344
connect_kwargs = {
357345
k: v
358346
for k, v in self.options.items()
359347
if k not in self._extra_options
360348
}
361349
self.logger.debug("Opening SSH connection")
362-
try:
363-
self._ssh_connection = await asyncssh.connect(
364-
**connect_kwargs, client_factory=self.client_factory
365-
)
366-
except (OSError, asyncssh.Error) as e:
367-
self.logger.error(f"Failed to open ssh connection: {e}")
368-
self._emit_connection_status(
369-
ConnectionStatus.Error,
370-
_("It was not possible to open a connection to this machine"),
371-
)
372-
return False
373350

374-
self.logger.info(f"SSH connection opened for {self.peer_host}")
351+
self._ssh_connection = await asyncssh.connect(
352+
**connect_kwargs, client_factory=self.client_factory
353+
)
354+
355+
self.logger.info("SSH connection established for %s", self.peer_host)
375356

376357
return True
377358

0 commit comments

Comments
 (0)