[ 
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)

Reply via email to