8333086: Using Console.println is unnecessarily slow due to JLine initalization

Reviewed-by: asotona, naoto
This commit is contained in:
Jan Lahoda 2024-06-05 17:20:44 +00:00
parent 9b3694c4fc
commit f7dbb98fe6
2 changed files with 225 additions and 13 deletions
src/jdk.internal.le/share/classes/jdk/internal/org/jline
test/jdk/jdk/internal/jline

@ -28,7 +28,6 @@ package jdk.internal.org.jline;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.util.Locale;
@ -51,18 +50,134 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider {
*/
@Override
public JdkConsole console(boolean isTTY, Charset charset) {
try {
Terminal terminal = TerminalBuilder.builder().encoding(charset)
.exec(false)
.systemOutput(SystemOutput.SysOut)
.build();
return new JdkConsoleImpl(terminal);
} catch (IllegalStateException ise) {
//cannot create a non-dumb, non-exec terminal,
//use the standard Console:
return null;
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
return new LazyDelegatingJdkConsoleImpl(charset);
}
private static class LazyDelegatingJdkConsoleImpl implements JdkConsole {
private final Charset charset;
private volatile boolean jlineInitialized;
private volatile JdkConsole delegate;
public LazyDelegatingJdkConsoleImpl(Charset charset) {
this.charset = charset;
this.delegate = new jdk.internal.io.JdkConsoleImpl(charset);
}
@Override
public PrintWriter writer() {
return getDelegate(true).writer();
}
@Override
public Reader reader() {
return getDelegate(true).reader();
}
@Override
public JdkConsole println(Object obj) {
JdkConsole delegate = getDelegate(false);
delegate.println(obj);
flushOldDelegateIfNeeded(delegate);
return this;
}
@Override
public JdkConsole print(Object obj) {
JdkConsole delegate = getDelegate(false);
delegate.print(obj);
flushOldDelegateIfNeeded(delegate);
return this;
}
@Override
public String readln(String prompt) {
return getDelegate(true).readln(prompt);
}
@Override
public JdkConsole format(Locale locale, String format, Object... args) {
JdkConsole delegate = getDelegate(false);
delegate.format(locale, format, args);
flushOldDelegateIfNeeded(delegate);
return this;
}
@Override
public String readLine(Locale locale, String format, Object... args) {
return getDelegate(true).readLine(locale, format, args);
}
@Override
public String readLine() {
return getDelegate(true).readLine();
}
@Override
public char[] readPassword(Locale locale, String format, Object... args) {
return getDelegate(true).readPassword(locale, format, args);
}
@Override
public char[] readPassword() {
return getDelegate(true).readPassword();
}
@Override
public void flush() {
getDelegate(false).flush();
}
@Override
public Charset charset() {
return charset;
}
private void flushOldDelegateIfNeeded(JdkConsole oldDelegate) {
if (oldDelegate != getDelegate(false)) {
//if the delegate changed in the mean time, make sure the original
//delegate is flushed:
oldDelegate.flush();
}
}
private JdkConsole getDelegate(boolean needsJLine) {
if (!needsJLine || jlineInitialized) {
return delegate;
}
return initializeJLineDelegate();
}
private synchronized JdkConsole initializeJLineDelegate() {
JdkConsole newDelegate = delegate;
if (jlineInitialized) {
return newDelegate;
}
try {
Terminal terminal = TerminalBuilder.builder().encoding(charset)
.exec(false)
.systemOutput(SystemOutput.SysOut)
.build();
newDelegate = new JdkConsoleImpl(terminal);
} catch (IllegalStateException ise) {
//cannot create a non-dumb, non-exec terminal,
//use the standard Console:
} catch (IOException ioe) {
//something went wrong, keep the existing delegate
}
delegate = newDelegate;
jlineInitialized = true;
return newDelegate;
}
}

@ -0,0 +1,97 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8333086
* @summary Verify the JLine backend is not initialized for simple printing.
* @enablePreview
* @modules jdk.internal.le/jdk.internal.org.jline.reader
* jdk.internal.le/jdk.internal.org.jline.terminal
* @library /test/lib
* @run main LazyJdkConsoleProvider
*/
import java.io.IO;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
public class LazyJdkConsoleProvider {
public static void main(String... args) throws Throwable {
switch (args.length > 0 ? args[0] : "default") {
case "write" -> {
System.console().println("Hello!");
System.console().print("Hello!");
System.console().format("\nHello!\n");
System.console().flush();
IO.println("Hello!");
IO.print("Hello!");
}
case "read" -> System.console().readLine("Hello!");
case "IO-read" -> {
IO.readln("Hello!");
}
case "default" -> {
new LazyJdkConsoleProvider().runTest();
}
}
}
void runTest() throws Exception {
record TestCase(String testKey, String expected, String notExpected) {}
TestCase[] testCases = new TestCase[] {
new TestCase("write", null, Terminal.class.getName()),
new TestCase("read", LineReader.class.getName(), null),
new TestCase("IO-read", LineReader.class.getName(), null)
};
for (TestCase tc : testCases) {
ProcessBuilder builder =
ProcessTools.createTestJavaProcessBuilder("--enable-preview",
"-verbose:class",
"-Djdk.console=jdk.internal.le",
LazyJdkConsoleProvider.class.getName(),
tc.testKey());
OutputAnalyzer output = ProcessTools.executeProcess(builder, "");
output.waitFor();
if (output.getExitValue() != 0) {
throw new AssertionError("Unexpected return value: " + output.getExitValue() +
", actualOut: " + output.getStdout() +
", actualErr: " + output.getStderr());
}
if (tc.expected() != null) {
output.shouldContain(tc.expected());
}
if (tc.notExpected() != null) {
output.shouldNotContain(tc.notExpected());
}
}
}
}