[
https://issues.apache.org/jira/browse/GEODE-10523?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Jinwoo Hwang updated GEODE-10523:
---------------------------------
Summary: gfsh issues after Spring Shell 3 migration (was: gfsh fails to
start with NullPointerException after Spring Shell 3.x migration)
> gfsh issues after Spring Shell 3 migration
> ------------------------------------------
>
> Key: GEODE-10523
> URL: https://issues.apache.org/jira/browse/GEODE-10523
> Project: Geode
> Issue Type: Bug
> Reporter: Jinwoo Hwang
> Assignee: Jinwoo Hwang
> Priority: Major
> Fix For: 2.0.0
>
>
> h2. Description
> After migrating from Spring Shell 1.x to Spring Shell 3.x, gfsh fails to
> start with a {{NullPointerException}} when attempting to read user input. The
> terminal and line reader components are never initialized before the prompt
> loop begins.
> h2. Steps to Reproduce
> # Build Apache Geode with the Spring Shell 3.x migration changes
> # Navigate to {{geode-assembly/build/install/apache-geode}}
> # Run {{./bin/gfsh}}
> # Observe the error
> h2. Expected Behavior
> gfsh should start successfully, display the banner, and present an
> interactive prompt to the user.
> h2. Actual Behavior
> gfsh crashes with the following error:
> {code:java}
> [error 2025/11/16 12:18:09.159 EST <Gfsh Launcher> tid=0x11] Error in shell
> main loop
> java.lang.NullPointerException: Cannot invoke
> "org.jline.reader.LineReader.readLine(String)" because "reader" is null
> at
> org.apache.geode.management.internal.cli.shell.Gfsh.readLine(Gfsh.java:321)
> at
> org.apache.geode.management.internal.cli.shell.Gfsh.promptLoop(Gfsh.java:1393)
> at
> org.apache.geode.management.internal.cli.shell.Gfsh.run(Gfsh.java:1593)
> at java.base/java.lang.Thread.run(Thread.java:840)
> Exception in thread "main" java.lang.NullPointerException: Cannot invoke
> "org.apache.geode.management.internal.cli.shell.ExitShellRequest.getExitCode()"
> because "exitRequest" is null
> at
> org.apache.geode.management.internal.cli.Launcher.parseOptions(Launcher.java:240)
> at
> org.apache.geode.management.internal.cli.Launcher.parseCommandLine(Launcher.java:248)
> at
> org.apache.geode.management.internal.cli.Launcher.main(Launcher.java:132)
> {code}
> h2. Additional Issues Discovered
> h3. History File Format Incompatibility
> When fixing the NPE issue, a secondary issue was discovered: old JLine 2
> history files are incompatible with JLine 3, causing warnings:
> {code:java}
> WARNING: Failed to load history
> java.lang.IllegalArgumentException: Bad history file syntax! The history file
> `/Users/jihwan/.geode/.gfsh.history` may be an older history: please remove
> it or use a different history file.
> {code}
> h3. Missing Banner
> The gfsh banner was not being displayed because {{printAsInfo()}} was using
> logger instead of stdout in non-headless mode.
> h2. Root Cause
> The Spring Shell 3.x migration removed the automatic terminal initialization
> that Spring Shell 1.x provided. In the migrated code:
> # {*}NPE Issue{*}: The {{terminal}} and {{lineReader}} fields were never
> initialized before {{run()}} called {{{}promptLoop(){}}}, which tried to use
> the null {{{}lineReader{}}}.
> # {*}History Issue{*}: JLine 2 used a different history file format (with
> {{//}} comment lines) that JLine 3 cannot parse.
> # {*}Banner Issue{*}: The {{printAsInfo()}} method was calling
> {{logger.info()}} instead of printing to stdout in interactive mode.
> h2. Solution
> The fix involves three changes:
> h3. 1. Initialize Terminal and LineReader
> In {{{}Gfsh.java{}}}, the {{run()}} method now initializes the terminal and
> line reader before starting the prompt loop:
> {code:java}
> @Override
> public void run() {
> try {
> // Print banner before initializing terminal to ensure it's visible
> printBannerAndWelcome();
> // Initialize terminal and line reader before starting prompt loop
> if (!isHeadlessMode) {
> initializeTerminal();
> createConsoleReader();
> }
> promptLoop();
> } catch (Exception e) {
> gfshFileLogger.severe("Error in shell main loop", e);
> }
> }
> private void initializeTerminal() throws IOException {
> if (terminal == null) {
> terminal = TerminalBuilder.builder()
> .system(true)
> .build();
> }
> }
> {code}
> h3. 2. Migrate Old History Files
> Added {{migrateHistoryFileIfNeeded()}} method in {{Gfsh.java}} to detect and
> migrate old JLine 2 history files:
> {code:java}
> private void migrateHistoryFileIfNeeded() {
> try {
> Path historyPath = Paths.get(getHistoryFileName());
> if (!Files.exists(historyPath)) {
> return;
> }
> // Check if file contains old format markers (lines starting with // or #)
> java.util.List<String> lines = Files.readAllLines(historyPath);
> boolean hasOldFormat = false;
> for (String line : lines) {
> String trimmed = line.trim();
> if (trimmed.startsWith("//") || trimmed.startsWith("#")) {
> hasOldFormat = true;
> break;
> }
> }
> if (hasOldFormat) {
> // Backup old history file
> Path backupPath = historyPath.getParent()
> .resolve(historyPath.getFileName().toString() + ".old");
> Files.move(historyPath, backupPath,
> java.nio.file.StandardCopyOption.REPLACE_EXISTING);
> gfshFileLogger.info("Migrated old history file format. Backup saved to:
> " + backupPath);
> }
> } catch (IOException e) {
> gfshFileLogger.warning("Could not migrate history file", e);
> }
> }
> {code}
> h3. 3. Fix Banner Display
> Modified {{printAsInfo()}} to always print to stdout:
> {code:java}
> public void printAsInfo(String message) {
> // Always print to stdout for user-facing messages like banner
> println(message);
> // Also log to file if not in headless mode
> if (!isHeadlessMode) {
> logger.info(message);
> }
> }
> {code}
> h2. Files Modified
> #
> {{geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java}}
> ** Added {{initializeTerminal()}} method
> ** Added {{migrateHistoryFileIfNeeded()}} method
> ** Modified {{run()}} method to initialize components
> ** Modified {{printAsInfo()}} to print to stdout
> ** Added imports: {{{}java.nio.file.Files{}}}, {{java.nio.file.Path}}
> #
> {{geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/shell/jline/GfshHistory.java}}
> ** Added {{attach()}} override with exception handling for migration
> ** Added {{migrateOldHistoryFile()}} method
> h2. Testing
> After applying the fix:
> * gfsh starts without errors
> * Banner is displayed correctly
> * Interactive prompt works
> * Old history files are automatically migrated
> * Command history works with new format
> h2. Impact
> * {*}Severity{*}: Critical (gfsh completely unusable)
> * {*}Affected Versions{*}: Any version after Spring Shell 3.x migration
> * {*}Workaround{*}: None (gfsh cannot be used)
> h2. Related Issues
> This issue is a direct result of the Spring Shell 1.x to 3.x migration effort.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)