Skip to content

Commit ecc9b9d

Browse files
authored
Use lock to avoid kernel address conflicts in nb-tester (#3143)
This PR uses a lock to try and avoid this error when running in a GitHub action: ``` zmq.error.ZMQError: Address already in use (addr='tcp://127.0.0.1:45769') ``` This is a bit of a shot in the dark because I can't reproduce it locally. This is related to #2387, but is not necessarily a full fix as I believe there is more than one thing going on there. *** Inspired by jupyter/nbclient#327
1 parent ab4ac60 commit ecc9b9d

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

scripts/nb-tester/qiskit_docs_notebook_tester/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@ async def _main() -> None:
3232

3333
start_time = datetime.now()
3434
print("Executing notebooks:")
35-
results = await asyncio.gather(*(execute_notebook(job) for job in jobs))
35+
36+
# New kernels choose a port on creation. They usually detect if the port is
37+
# in use and will choose another if so, but there can be race conditions
38+
# when creating many kernels at once. We use a lock to avoid this.
39+
kernel_setup_lock = asyncio.Lock()
40+
results = await asyncio.gather(
41+
*(execute_notebook(job, kernel_setup_lock) for job in jobs)
42+
)
3643

3744
if not args.ignore_trailing_jobs:
3845
print("Checking for trailing jobs...")

scripts/nb-tester/qiskit_docs_notebook_tester/execute.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from __future__ import annotations
1414

1515

16+
import asyncio
1617
import tempfile
1718
import textwrap
1819
from dataclasses import dataclass
@@ -65,7 +66,7 @@ def extract_warnings(notebook: nbformat.NotebookNode) -> list[NotebookWarning]:
6566
return notebook_warnings
6667

6768

68-
async def execute_notebook(job: NotebookJob) -> Result:
69+
async def execute_notebook(job: NotebookJob, kernel_setup_lock: asyncio.Lock) -> Result:
6970
"""
7071
Wrapper function for `_execute_notebook` to print status and write result
7172
"""
@@ -80,7 +81,7 @@ async def execute_notebook(job: NotebookJob) -> Result:
8081
nbclient.exceptions.CellTimeoutError,
8182
)
8283
try:
83-
nb = await _execute_notebook(job, working_directory.name)
84+
nb = await _execute_notebook(job, working_directory.name, kernel_setup_lock)
8485
except execution_exceptions as err:
8586
print(f"❌ Problem in {job.path}:\n{err}")
8687
return Result(False, reason="Exception in notebook")
@@ -118,7 +119,7 @@ async def _execute_in_kernel(kernel: AsyncKernelClient, code: str) -> None:
118119

119120

120121
async def _execute_notebook(
121-
job: NotebookJob, working_directory: str
122+
job: NotebookJob, working_directory: str, kernel_setup_lock: asyncio.Lock
122123
) -> nbformat.NotebookNode:
123124
"""
124125
Use nbclient to execute notebook. The steps are:
@@ -130,11 +131,16 @@ async def _execute_notebook(
130131
"""
131132
nb = nbformat.read(job.path, as_version=4)
132133

133-
kernel_manager, kernel = await start_new_async_kernel(
134-
kernel_name="python3",
135-
extra_arguments=["--InlineBackend.figure_format='svg'"],
136-
cwd=working_directory,
137-
)
134+
async with kernel_setup_lock:
135+
# New kernels choose a port on creation. They usually detect if the
136+
# port is in use and will choose another if so, but there can be race
137+
# conditions when creating many kernels at once.The lock avoids this.
138+
# This might be fixed by https://github.com/jupyter/nbclient/pull/327
139+
kernel_manager, kernel = await start_new_async_kernel(
140+
kernel_name="python3",
141+
extra_arguments=["--InlineBackend.figure_format='svg'"],
142+
cwd=working_directory,
143+
)
138144

139145
await _execute_in_kernel(kernel, job.pre_execute_code)
140146
if job.backend_patch:

0 commit comments

Comments
 (0)