Skip to content

Commit 2c655c8

Browse files
committed
Fix xeus-octave startup by retrying kernel_info and increasing timeout
xeus-octave was failing with "Connection reset by peer" or timeout because Octave takes longer to initialize than other kernels. Two fixes: 1. Retry kernel_info_request up to 3 times with 2s delays between attempts, so slow-starting kernels get a chance to bind their ports 2. Increase per-test timeout to 30s for xeus-octave in CI
1 parent 6ee071f commit 2c655c8

2 files changed

Lines changed: 42 additions & 20 deletions

File tree

.github/workflows/conformance.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ jobs:
242242
run: |
243243
if [ "${{ matrix.kernel.name }}" = "xeus-octave" ]; then
244244
xvfb-run -a ./target/release/jupyter-kernel-test ${{ matrix.kernel.kernel-name }} \
245+
--timeout 30000 \
245246
--format json \
246247
--output ${{ matrix.kernel.name }}-report.json
247248
else
@@ -254,7 +255,7 @@ jobs:
254255
- name: Display results
255256
run: |
256257
if [ "${{ matrix.kernel.name }}" = "xeus-octave" ]; then
257-
xvfb-run -a ./target/release/jupyter-kernel-test ${{ matrix.kernel.kernel-name }} || true
258+
xvfb-run -a ./target/release/jupyter-kernel-test ${{ matrix.kernel.kernel-name }} --timeout 30000 || true
258259
else
259260
./target/release/jupyter-kernel-test ${{ matrix.kernel.kernel-name }} || true
260261
fi

src/harness.rs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -182,29 +182,50 @@ impl KernelUnderTest {
182182
}
183183

184184
/// Fetch kernel_info and update snippets.
185+
///
186+
/// Retries the kernel_info_request up to 3 times to handle slow-starting
187+
/// kernels (e.g., xeus-octave) where the initial request may be sent before
188+
/// the kernel has bound to its ports.
185189
async fn fetch_kernel_info(&mut self) -> Result<()> {
186-
let request: JupyterMessage = KernelInfoRequest {}.into();
187-
self.shell
188-
.send(request)
189-
.await
190-
.map_err(|e| HarnessError::ProtocolError(e.to_string()))?;
190+
let max_attempts = 3;
191+
let mut last_error = None;
191192

192-
// Read reply with timeout
193-
let reply = timeout(self.test_timeout, self.shell.read())
194-
.await
195-
.map_err(|_| HarnessError::Timeout("kernel_info_reply".to_string()))?
196-
.map_err(|e| HarnessError::ProtocolError(e.to_string()))?;
193+
for attempt in 0..max_attempts {
194+
if attempt > 0 {
195+
// Wait longer between retries to give the kernel time to start
196+
tokio::time::sleep(Duration::from_secs(2)).await;
197+
}
197198

198-
if let JupyterMessageContent::KernelInfoReply(info) = reply.content {
199-
self.snippets = LanguageSnippets::for_language(&info.language_info.name);
200-
self.kernel_info = Some(*info);
201-
Ok(())
202-
} else {
203-
Err(HarnessError::ProtocolError(format!(
204-
"Expected kernel_info_reply, got {:?}",
205-
reply.content.message_type()
206-
)))
199+
let request: JupyterMessage = KernelInfoRequest {}.into();
200+
if let Err(e) = self.shell.send(request).await {
201+
last_error = Some(HarnessError::ProtocolError(e.to_string()));
202+
continue;
203+
}
204+
205+
match timeout(self.test_timeout, self.shell.read()).await {
206+
Ok(Ok(reply)) => {
207+
if let JupyterMessageContent::KernelInfoReply(info) = reply.content {
208+
self.snippets =
209+
LanguageSnippets::for_language(&info.language_info.name);
210+
self.kernel_info = Some(*info);
211+
return Ok(());
212+
} else {
213+
return Err(HarnessError::ProtocolError(format!(
214+
"Expected kernel_info_reply, got {:?}",
215+
reply.content.message_type()
216+
)));
217+
}
218+
}
219+
Ok(Err(e)) => {
220+
last_error = Some(HarnessError::ProtocolError(e.to_string()));
221+
}
222+
Err(_) => {
223+
last_error = Some(HarnessError::Timeout("kernel_info_reply".to_string()));
224+
}
225+
}
207226
}
227+
228+
Err(last_error.unwrap_or_else(|| HarnessError::Timeout("kernel_info_reply".to_string())))
208229
}
209230

210231
/// Get kernel info.

0 commit comments

Comments
 (0)