From 71ef45566f7539cb24a8174d720a1b7daaab8b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Fri, 6 Mar 2026 13:17:06 +0100 Subject: [PATCH] ZOOKEEPER-5021: Exit from zkCli when interrupt (Ctrl-C) or EOF (Ctrl-D) is pressed Problem Closing the input stream for a terminal by pressing "Ctrl-D" should automatically exit the interactive shell, but the bin/zkCli.sh does not exit, but does disable JLine support, leaving you at a prompt-less terminal. Pressing "Ctrl-D" a second time exited correctly, and so did entering "quit" (without JLine support enabled). Cause After the JLine 3.x upgrade, pressing Ctrl-C or Ctrl-D results in InvocationTargetException which we caught and set the jlinemissing flag to true to fall back to the promptless shell. Fix When the cause of the InvocationTargetException is either EndOfFileException or UserInterruptException, we should not set the jlinemissing flag to true to not fall back to the promptless shell. --- .../java/org/apache/zookeeper/ZooKeeperMain.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeperMain.java b/zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeperMain.java index ca27348e164..28bd0f178bc 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeperMain.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeperMain.java @@ -310,9 +310,13 @@ void run() throws IOException, InterruptedException { boolean jlinemissing = false; // only use jline if it's in the classpath + Class endOfFileExceptionC = null; + Class userInterruptExceptionC = null; try { Class readerC = Class.forName("org.jline.reader.LineReader"); Class completerC = Class.forName("org.apache.zookeeper.JLineZNodeCompleter"); + endOfFileExceptionC = Class.forName("org.jline.reader.EndOfFileException"); + userInterruptExceptionC = Class.forName("org.jline.reader.UserInterruptException"); System.out.println("JLine support is enabled"); @@ -331,8 +335,15 @@ void run() throws IOException, InterruptedException { | IllegalAccessException | InstantiationException e ) { - LOG.debug("Unable to start jline", e); - jlinemissing = true; + // Only fall back to terminal without JLine when user not interrupted (Ctrl-C) + // or does not want to exit (Ctrl-D / EOF). + Throwable cause = e.getCause(); + boolean isEof = cause != null && cause.getClass().equals(endOfFileExceptionC); + boolean isUserInterrupted = cause != null && cause.getClass().equals(userInterruptExceptionC); + if (!(isEof || isUserInterrupted)) { + LOG.debug("Unable to start jline", e); + jlinemissing = true; + } } if (jlinemissing) {