From f068e1b81ad484a02d40793e1e006ae5b1b8e2b4 Mon Sep 17 00:00:00 2001 From: HwangRock Date: Sun, 26 Jul 2026 17:49:44 +0900 Subject: [PATCH] [ZEPPELIN-6561] Restore default SIGINT handler so python paragraph cancel works under daemon launch --- .../main/resources/python/zeppelin_python.py | 9 +++- .../python/PythonInterpreterTest.java | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/python/src/main/resources/python/zeppelin_python.py b/python/src/main/resources/python/zeppelin_python.py index f3f91861c23..83a6137dde0 100644 --- a/python/src/main/resources/python/zeppelin_python.py +++ b/python/src/main/resources/python/zeppelin_python.py @@ -15,13 +15,20 @@ # limitations under the License. # -import os, sys, traceback, json, re +import os, signal, sys, traceback, json, re from py4j.java_gateway import java_import, JavaGateway, GatewayClient from py4j.protocol import Py4JJavaError import ast +# When Zeppelin is started via zeppelin-daemon.sh (nohup ... &), this process +# inherits SIGINT=SIG_IGN and CPython keeps it ignored instead of installing the +# KeyboardInterrupt handler, so PythonInterpreter.cancel()'s SIGINT would be a +# no-op. Restore the default handler to keep paragraph cancellation working. +if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: + signal.signal(signal.SIGINT, signal.default_int_handler) + class Logger(object): def __init__(self): pass diff --git a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java index 28cfadc5b46..7d0ad4f0e2a 100644 --- a/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java +++ b/python/src/test/java/org/apache/zeppelin/python/PythonInterpreterTest.java @@ -24,6 +24,7 @@ import org.apache.zeppelin.interpreter.InterpreterException; import org.apache.zeppelin.interpreter.InterpreterGroup; import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.InterpreterResultMessage; import org.apache.zeppelin.interpreter.LazyOpenInterpreter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -35,8 +36,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; import java.util.LinkedList; +import java.util.List; import java.util.Properties; import java.util.concurrent.TimeoutException; import java.util.regex.Matcher; @@ -174,4 +179,42 @@ public void testFailtoLaunchPythonProcess() throws InterpreterException { assertTrue(stacktrace.contains("No such file or directory"), stacktrace); } } + + @Test + public void testSigintDefaultHandlerRestoredWhenInheritedIgnored() + throws IOException, InterpreterException { + tearDown(); + + File wrapper = File.createTempFile("python-sigint-wrapper", ".sh"); + wrapper.deleteOnExit(); + Files.write(wrapper.toPath(), Arrays.asList( + "#!/bin/sh", + "trap '' INT", + "exec python \"$@\"")); + wrapper.setExecutable(true); + + intpGroup = new InterpreterGroup(); + + Properties properties = new Properties(); + properties.setProperty("zeppelin.python", wrapper.getAbsolutePath()); + properties.setProperty("zeppelin.python.useIPython", "false"); + properties.setProperty("zeppelin.python.gatewayserver_address", "127.0.0.1"); + + interpreter = new LazyOpenInterpreter(new PythonInterpreter(properties)); + + intpGroup.put("note", new LinkedList()); + intpGroup.get("note").add(interpreter); + interpreter.setInterpreterGroup(intpGroup); + + InterpreterContext.set(getInterpreterContext()); + + InterpreterContext context = getInterpreterContext(); + InterpreterResult result = interpreter.interpret( + "import signal\nprint(signal.getsignal(signal.SIGINT))", context); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + List interpreterResultMessages = + context.out.toInterpreterResultMessage(); + String output = interpreterResultMessages.get(0).getData(); + assertTrue(output.contains("default_int_handler"), output); + } }