Skip to content

Commit 56df36b

Browse files
Backport PR #882 on branch 7.x (Fix connection reconciliation to handle restarts) (#883)
Co-authored-by: Kevin Bates <[email protected]>
1 parent f71daff commit 56df36b

File tree

2 files changed

+29
-36
lines changed

2 files changed

+29
-36
lines changed

jupyter_client/connect.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,8 @@ def _reconcile_connection_info(self, info: KernelConnectionInfo) -> None:
573573
Because some provisioners (like derivations of LocalProvisioner) may have already
574574
written the connection file, this method needs to ensure that, if the connection
575575
file exists, its contents match that of what was returned by the provisioner. If
576-
the file does exist and its contents do not match, a ValueError is raised.
576+
the file does exist and its contents do not match, the file will be replaced with
577+
the provisioner information (which is considered the truth).
577578
578579
If the file does not exist, the connection information in 'info' is loaded into the
579580
KernelManager and written to the file.
@@ -586,24 +587,24 @@ def _reconcile_connection_info(self, info: KernelConnectionInfo) -> None:
586587
if os.path.exists(self.connection_file):
587588
with open(self.connection_file) as f:
588589
file_info = json.load(f)
589-
# Prior to the following comparison, we need to adjust the value of "key" to
590-
# be bytes, otherwise the comparison below will fail.
591-
file_info["key"] = file_info["key"].encode()
592-
if not self._equal_connections(info, file_info):
593-
raise ValueError(
594-
"Connection file already exists and does not match "
595-
"the expected values returned from provisioner!"
596-
)
590+
# Prior to the following comparison, we need to adjust the value of "key" to
591+
# be bytes, otherwise the comparison below will fail.
592+
file_info["key"] = file_info["key"].encode()
593+
if not self._equal_connections(info, file_info):
594+
os.remove(self.connection_file) # Contents mismatch - remove the file
595+
self._connection_file_written = False
596+
else:
597597
file_exists = True
598598

599599
if not file_exists:
600-
# Load the connection info and write out file. Note, this does not necessarily
601-
# overwrite non-zero port values, so we'll validate afterward.
600+
# Load the connection info and write out file, clearing existing
601+
# port-based attributes so they will be reloaded
602+
for name in port_names:
603+
setattr(self, name, 0)
602604
self.load_connection_info(info)
603605
self.write_connection_file()
604606

605-
# Ensure what is in KernelManager is what we expect. This will catch issues if the file
606-
# already existed, yet it's contents differed from the KernelManager's (and provisioner).
607+
# Ensure what is in KernelManager is what we expect.
607608
km_info = self.get_connection_info()
608609
if not self._equal_connections(info, km_info):
609610
raise ValueError(

jupyter_client/tests/test_connect.py

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,15 @@ def test_mixin_cleanup_random_ports():
240240

241241

242242
param_values = [
243-
(True, True, None),
244-
(True, False, ValueError),
245-
(False, True, None),
246-
(False, False, ValueError),
243+
(True, True),
244+
(True, False),
245+
(False, True),
246+
(False, False),
247247
]
248248

249249

250-
@pytest.mark.parametrize("file_exists, km_matches, expected_exception", param_values)
251-
def test_reconcile_connection_info(file_exists, km_matches, expected_exception):
250+
@pytest.mark.parametrize("file_exists, km_matches", param_values)
251+
def test_reconcile_connection_info(file_exists, km_matches):
252252

253253
expected_info = sample_info
254254
mismatched_info = sample_info.copy()
@@ -272,9 +272,10 @@ def test_reconcile_connection_info(file_exists, km_matches, expected_exception):
272272
provisioner_info = info
273273
km.load_connection_info(provisioner_info)
274274
else:
275-
# Let this be the case where the connection file exists, the KM has no values
276-
# prior to reconciliation, but the provisioner has returned different values
277-
# and a ValueError is expected.
275+
# Let this be the case where the connection file exists, and the KM has those values
276+
# that differ from the ones returned by the provisioner. This is the restart-with-
277+
# changed-ports case (typical for remote provisioners).
278+
km.load_connection_info(expected_info)
278279
provisioner_info = mismatched_info
279280
else: # connection file does not exist
280281
if km_matches:
@@ -284,20 +285,11 @@ def test_reconcile_connection_info(file_exists, km_matches, expected_exception):
284285
provisioner_info = expected_info
285286
else:
286287
# Let this be the case where the connection file does not exist, yet the KM
287-
# has values that do not match those returned from the provisioner and a
288-
# ValueError is expected.
288+
# has values that do not match those returned from the provisioner. This case
289+
# is probably not practical and is equivalent to the True, False case.
289290
km.load_connection_info(expected_info)
290291
provisioner_info = mismatched_info
291292

292-
if expected_exception is None:
293-
km._reconcile_connection_info(provisioner_info)
294-
km_info = km.get_connection_info()
295-
assert km._equal_connections(km_info, provisioner_info)
296-
else:
297-
with pytest.raises(expected_exception) as ex:
298-
km._reconcile_connection_info(provisioner_info)
299-
if file_exists:
300-
assert "Connection file already exists" in str(ex.value)
301-
else:
302-
assert "KernelManager's connection information already exists" in str(ex.value)
303-
assert km._equal_connections(km.get_connection_info(), provisioner_info) is False
293+
km._reconcile_connection_info(provisioner_info)
294+
km_info = km.get_connection_info()
295+
assert km._equal_connections(km_info, provisioner_info)

0 commit comments

Comments
 (0)