Skip to content

Bug/ap 25397 python pref selection error#77

Open
marc-lehner wants to merge 5 commits intomasterfrom
bug/AP-25397-python-pref-selection-error
Open

Bug/ap 25397 python pref selection error#77
marc-lehner wants to merge 5 commits intomasterfrom
bug/AP-25397-python-pref-selection-error

Conversation

@marc-lehner
Copy link
Contributor

  • Added a custom override of createProcessBuilder() in SimplePythonCommand to prepend necessary conda/pixi environment directories to the PATH variable on Windows. This ensures Python 3.12+ subprocesses start correctly by locating required DLLs, avoiding Windows SxS assembly errors. Only existing directories are prepended to prevent unnecessary pollution of PATH. (org.knime.python3/src/main/java/org/knime/python3/SimplePythonCommand.java)

  • Improved subprocess output handling in PythonKernelTester.java by reading stdout and stderr concurrently in separate threads. This avoids potential deadlocks when large outputs are generated, increasing robustness of the Python kernel testing logic. (org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes/prefs/PythonKernelTester.java)

  • Fixed a bug where additional required modules were not being passed correctly to the Python kernel tester; previously, optional modules were incorrectly used in place of required ones. (org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes/prefs/PythonKernelTester.java)

  • Changed the method for obtaining the Python kernel tester script path to use getCanonicalPath() instead of getAbsolutePath(), ensuring correct path resolution in cases involving symbolic links and spaces. (org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes/prefs/PythonKernelTester.java)

  • Removed the check for the deprecated imp module in the Python kernel tester, reflecting modern Python best practices. (org.knime.python3.scripting.nodes/python-kernel-tester/PythonKernelTester.py)

Copilot AI review requested due to automatic review settings February 27, 2026 11:04
@marc-lehner marc-lehner requested a review from a team as a code owner February 27, 2026 11:04
@marc-lehner marc-lehner requested review from knime-ghub-bot and removed request for a team February 27, 2026 11:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Python environment startup and preference testing reliability in the KNIME Python integration, particularly addressing Windows-specific DLL lookup issues (Python 3.12+) and making the Python kernel tester more robust.

Changes:

  • Add a SimplePythonCommand#createProcessBuilder() override to prepend relevant environment directories to PATH (intended to fix Windows conda/pixi DLL resolution issues).
  • Prevent potential subprocess I/O deadlocks in PythonKernelTester by reading stdout and stderr concurrently.
  • Fix argument passing for required additional modules (was incorrectly using optional modules) and modernize the kernel tester script by removing the deprecated imp check.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
org.knime.python3/src/main/java/org/knime/python3/SimplePythonCommand.java Prepends inferred env directories to PATH when creating a ProcessBuilder.
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes/prefs/PythonKernelTester.java Uses canonical script path, fixes module arg wiring, and reads process output concurrently.
org.knime.python3.scripting.nodes/python-kernel-tester/PythonKernelTester.py Removes deprecated imp module requirement check.

Comment on lines +100 to +118
public ProcessBuilder createProcessBuilder() {
final var pb = super.createProcessBuilder();
final Path executableDir = getExecutablePath().getParent();
if (executableDir != null) {
final var env = pb.environment();
// ProcessBuilder.environment() on Windows stores keys case-insensitively but
// preserves the original casing. Find the existing key to avoid duplicates.
final String pathKey = env.keySet().stream() //
.filter(k -> k.equalsIgnoreCase("PATH")) //
.findFirst() //
.orElse("PATH");
final String existingPath = env.getOrDefault(pathKey, "");

// Standard conda/pixi activation paths for Windows, in activation order.
final var dirsToAdd = new StringBuilder();
for (final String subDir : new String[]{"", "Library\\bin", "Library\\mingw-w64\\bin",
"Library\\usr\\bin", "Scripts"}) {
final var candidate = subDir.isEmpty() ? executableDir : executableDir.resolve(subDir);
if (candidate.toFile().isDirectory()) {
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createProcessBuilder() currently prepends PATH entries on all OSes whenever the executable path has a parent directory. The Javadoc/PR description says this is a Windows-only conda/pixi workaround; consider guarding with a Windows check (e.g., SystemUtils.IS_OS_WINDOWS) and only applying when a conda/pixi layout is detected, otherwise this can unnecessarily alter PATH for non-conda installs and on non-Windows platforms.

Copilot uses AI. Check for mistakes.
}
}
if (dirsToAdd.length() > 0) {
env.put(pathKey, dirsToAdd + File.pathSeparator + existingPath);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When existingPath is empty, env.put(pathKey, dirsToAdd + File.pathSeparator + existingPath) leaves a trailing path separator, which creates an empty PATH element. On Windows an empty PATH element can be interpreted as the current working directory, which is a security risk; build the new PATH so no empty segment is introduced (only append the separator + existingPath when it is non-empty).

Suggested change
env.put(pathKey, dirsToAdd + File.pathSeparator + existingPath);
final String newPath = existingPath.isEmpty()
? dirsToAdd.toString()
: dirsToAdd + File.pathSeparator + existingPath;
env.put(pathKey, newPath);

Copilot uses AI. Check for mistakes.
Comment on lines +156 to 171
final var stderrReader = new Thread(() -> {
try (var err = process.getErrorStream()) {
IOUtils.copy(err, errorWriter, StandardCharsets.UTF_8);
} catch (final IOException e) {
LOGGER.debug("Error reading stderr of Python kernel tester", e);
}
}, "PythonKernelTester-stderr");
stderrReader.start();
try (var in = process.getInputStream()) {
IOUtils.copy(in, outputWriter, StandardCharsets.UTF_8);
}
try {
stderrReader.join();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If reading stdout (IOUtils.copy(in, ...)) throws, the method exits via the outer catch (IOException ...) without joining the stderr thread or ensuring the process/streams are cleaned up, which can leave a background thread running and resources open. Consider joining the stderr reader in a finally block (and/or using an executor) so it is always terminated before returning, even on exceptions.

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
62.2% Coverage on New Code (required ≥ 85%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants