Skip to content

Commit 42583a1

Browse files
committed
fix(remote): stale results and deadlock when closing during a call (#398)
- A remote command returning None (e.g. get_current_object_uuid with no current object) returned the result of the previous command instead, such as the panel name returned by call_method: the result is now reset at the start of each request, like the exception. - Closing DataLab while a remote call was waiting for it to be ready (e.g. call_method("close")) hung DataLab: the main window, blocked until the server stopped, could never signal that the request was done. Shutting down the server now releases the waiting call first. Both are covered by new tests, the closing one failing instead of hanging should the deadlock come back. Assisted-by: Claude Opus 5.5
1 parent 63235f9 commit 42583a1

3 files changed

Lines changed: 46 additions & 1 deletion

File tree

‎datalab/control/remote.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def method_wrapper(*args, **kwargs):
5151
self: RemoteServer = args[0] # extracting 'self' from method arguments
5252
self.is_ready = False
5353
self.exception = None
54+
self.result = None
5455
output = func(*args, **kwargs)
5556
while not self.is_ready:
5657
QC.QCoreApplication.processEvents()
@@ -179,6 +180,10 @@ def shutdown_server(self) -> None:
179180
"""Shutdown server"""
180181
# A server still waiting for the main window must be able to stop
181182
self.__window_ready.set()
183+
# Release the remote call waiting for DataLab to be ready, if any (e.g. the
184+
# one closing DataLab): the main window, blocked below until the request is
185+
# done, could not signal it, hence a deadlock
186+
self.is_ready = True
182187
if self.server is not None:
183188
self.server.shutdown()
184189
self.server = None

‎datalab/tests/features/control/remote_requests_unit_test.py‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@
1313
from __future__ import annotations
1414

1515
import tempfile
16+
import threading
1617
import xmlrpc.client
1718
from pathlib import Path
19+
from typing import TYPE_CHECKING
1820

1921
import numpy as np
2022
import pytest
2123
from sigima.objects import create_signal
2224

23-
from datalab.tests import datalab_in_background_context
25+
from datalab.tests import close_datalab_background, datalab_in_background_context
26+
27+
if TYPE_CHECKING:
28+
from datalab.control.proxy import RemoteProxy
2429

2530

2631
@pytest.mark.parametrize("mode", ["files", "directory"])
@@ -56,8 +61,41 @@ def test_remote_command_error() -> None:
5661
assert proxy.get_current_panel() == "signal"
5762

5863

64+
def test_remote_command_result() -> None:
65+
"""The result of a remote command is not returned by the next command"""
66+
with datalab_in_background_context() as proxy:
67+
assert proxy.call_method("get_current_panel") == "signal"
68+
# No object: the result of the previous command must not be returned instead
69+
assert proxy.get_current_object_uuid() is None
70+
71+
72+
def test_remote_close_during_call() -> None:
73+
"""Closing DataLab while a remote call is pending does not hang DataLab"""
74+
errors: list[Exception] = []
75+
76+
def close_datalab(proxy: RemoteProxy) -> None:
77+
"""Close DataLab with a remote call waiting for DataLab to be ready"""
78+
try:
79+
proxy.call_method("close")
80+
except Exception as exc: # pylint: disable=broad-except
81+
errors.append(exc)
82+
83+
with datalab_in_background_context() as proxy:
84+
thread = threading.Thread(target=close_datalab, args=(proxy,), daemon=True)
85+
thread.start()
86+
thread.join(20.0)
87+
hung = thread.is_alive()
88+
if hung:
89+
# Requesting a hung DataLab to close would hang the test as well
90+
close_datalab_background(request_close=False)
91+
assert not hung, "DataLab hung while closing during remote call"
92+
assert not errors
93+
94+
5995
if __name__ == "__main__":
6096
with tempfile.TemporaryDirectory() as tmpdir:
6197
for load_mode in ("files", "directory"):
6298
test_remote_load_at_startup(Path(tmpdir), load_mode)
6399
test_remote_command_error()
100+
test_remote_command_result()
101+
test_remote_close_during_call()

‎doc/release_notes/release_1.04.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,5 @@ DataLab now uses stable UUID-based object references across object trees, result
172172

173173
* Fixed files loaded remotely with `load_from_files` or `load_from_directory` being sometimes silently ignored, or still loading after the call had returned, when requested right after DataLab started: requests received while DataLab is starting are now processed as soon as its main window is ready (fixes [Issue #398](https://github.com/DataLab-Platform/DataLab/issues/398))
174174
* Errors raised by remote commands, such as `calc` with an unknown computation function or `set_object` with an unknown object, are now reported to the client instead of being silently ignored
175+
* Fixed remote commands sometimes returning the result of the previous command instead of their own: for example, `get_current_object_uuid` could return a panel name instead of `None` when there was no current object
176+
* Fixed DataLab freezing when closed while a remote command was still running (e.g. when closing the main window with `call_method("close")`)

0 commit comments

Comments
 (0)