[ 
https://issues.apache.org/jira/browse/GEODE-10523?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Jinwoo Hwang updated GEODE-10523:
---------------------------------
    Description: 
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.

  was:
Create the support/2.0 branch from develop for Apache Geode 2.0 release. This 
branch will be used to stabilize the release and accept critical fixes before 
creating release candidates.
h2. Prerequisites
 * LICENSE and NOTICE files have been reviewed and updated on develop

h2. Tasks
h3. 1. Create Support Branch
 * Create support/2.0 branch on all projects (geode, geode-examples, 
geode-native, geode-benchmarks)
 * Update version numbers
 * Creating the support pipeline
 * Create a PR to bump develop to the next version

h3. 2. Review Benchmark Baseline on Support Branch

Check the benchmark baseline configuration in the support/2.0 branch
h3. 3. Create Pull Request for Develop Bump
h2. Acceptance Criteria
 * [ ] support/2.0 branches created on all repositories (geode, geode-examples, 
geode-native, geode-benchmarks)
 * [ ] Version numbers updated correctly on support/2.0 (2.0.0-build.0)
 * [ ] PR for develop version bump created and merged
 * [ ] BumpMinor job executed on develop pipeline
 * [ ] Community notification email sent to dev list

h2. Branch Protection Rules

Once the support/2.0 branch is created, establish rules for backporting:
 * Critical fixes only during stabilization period
 * All fixes must go to develop first, then be cherry-picked to support/2.0
 * Require PR approval before merging to support/2.0

h2. Notes
 * Keep the support branch as stable as possible during the stabilization period
 * This is a major release, so breaking changes are acceptable


> gfsh fails to start with NullPointerException after Spring Shell 3.x 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