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