Skip to content

Commit 0000010

Browse files
lahodajpull[bot]
authored andcommitted
8333086: Using Console.println is unnecessarily slow due to JLine initalization
Reviewed-by: asotona, naoto
1 parent 4c93126 commit 0000010

File tree

2 files changed

+225
-13
lines changed

2 files changed

+225
-13
lines changed

src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java

Lines changed: 128 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.io.IOException;
2929
import java.io.PrintWriter;
3030
import java.io.Reader;
31-
import java.io.UncheckedIOException;
3231
import java.nio.charset.Charset;
3332
import java.util.Locale;
3433

@@ -51,18 +50,134 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider {
5150
*/
5251
@Override
5352
public JdkConsole console(boolean isTTY, Charset charset) {
54-
try {
55-
Terminal terminal = TerminalBuilder.builder().encoding(charset)
56-
.exec(false)
57-
.systemOutput(SystemOutput.SysOut)
58-
.build();
59-
return new JdkConsoleImpl(terminal);
60-
} catch (IllegalStateException ise) {
61-
//cannot create a non-dumb, non-exec terminal,
62-
//use the standard Console:
63-
return null;
64-
} catch (IOException ioe) {
65-
throw new UncheckedIOException(ioe);
53+
return new LazyDelegatingJdkConsoleImpl(charset);
54+
}
55+
56+
private static class LazyDelegatingJdkConsoleImpl implements JdkConsole {
57+
private final Charset charset;
58+
private volatile boolean jlineInitialized;
59+
private volatile JdkConsole delegate;
60+
61+
public LazyDelegatingJdkConsoleImpl(Charset charset) {
62+
this.charset = charset;
63+
this.delegate = new jdk.internal.io.JdkConsoleImpl(charset);
64+
}
65+
66+
@Override
67+
public PrintWriter writer() {
68+
return getDelegate(true).writer();
69+
}
70+
71+
@Override
72+
public Reader reader() {
73+
return getDelegate(true).reader();
74+
}
75+
76+
@Override
77+
public JdkConsole println(Object obj) {
78+
JdkConsole delegate = getDelegate(false);
79+
80+
delegate.println(obj);
81+
flushOldDelegateIfNeeded(delegate);
82+
83+
return this;
84+
}
85+
86+
@Override
87+
public JdkConsole print(Object obj) {
88+
JdkConsole delegate = getDelegate(false);
89+
90+
delegate.print(obj);
91+
flushOldDelegateIfNeeded(delegate);
92+
93+
return this;
94+
}
95+
96+
@Override
97+
public String readln(String prompt) {
98+
return getDelegate(true).readln(prompt);
99+
}
100+
101+
@Override
102+
public JdkConsole format(Locale locale, String format, Object... args) {
103+
JdkConsole delegate = getDelegate(false);
104+
105+
delegate.format(locale, format, args);
106+
flushOldDelegateIfNeeded(delegate);
107+
108+
return this;
109+
}
110+
111+
@Override
112+
public String readLine(Locale locale, String format, Object... args) {
113+
return getDelegate(true).readLine(locale, format, args);
114+
}
115+
116+
@Override
117+
public String readLine() {
118+
return getDelegate(true).readLine();
119+
}
120+
121+
@Override
122+
public char[] readPassword(Locale locale, String format, Object... args) {
123+
return getDelegate(true).readPassword(locale, format, args);
124+
}
125+
126+
@Override
127+
public char[] readPassword() {
128+
return getDelegate(true).readPassword();
129+
}
130+
131+
@Override
132+
public void flush() {
133+
getDelegate(false).flush();
134+
}
135+
136+
@Override
137+
public Charset charset() {
138+
return charset;
139+
}
140+
141+
private void flushOldDelegateIfNeeded(JdkConsole oldDelegate) {
142+
if (oldDelegate != getDelegate(false)) {
143+
//if the delegate changed in the mean time, make sure the original
144+
//delegate is flushed:
145+
oldDelegate.flush();
146+
}
147+
}
148+
149+
private JdkConsole getDelegate(boolean needsJLine) {
150+
if (!needsJLine || jlineInitialized) {
151+
return delegate;
152+
}
153+
154+
return initializeJLineDelegate();
155+
}
156+
157+
private synchronized JdkConsole initializeJLineDelegate() {
158+
JdkConsole newDelegate = delegate;
159+
160+
if (jlineInitialized) {
161+
return newDelegate;
162+
}
163+
164+
try {
165+
Terminal terminal = TerminalBuilder.builder().encoding(charset)
166+
.exec(false)
167+
.systemOutput(SystemOutput.SysOut)
168+
.build();
169+
newDelegate = new JdkConsoleImpl(terminal);
170+
} catch (IllegalStateException ise) {
171+
//cannot create a non-dumb, non-exec terminal,
172+
//use the standard Console:
173+
} catch (IOException ioe) {
174+
//something went wrong, keep the existing delegate
175+
}
176+
177+
delegate = newDelegate;
178+
jlineInitialized = true;
179+
180+
return newDelegate;
66181
}
67182
}
68183

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/**
25+
* @test
26+
* @bug 8333086
27+
* @summary Verify the JLine backend is not initialized for simple printing.
28+
* @enablePreview
29+
* @modules jdk.internal.le/jdk.internal.org.jline.reader
30+
* jdk.internal.le/jdk.internal.org.jline.terminal
31+
* @library /test/lib
32+
* @run main LazyJdkConsoleProvider
33+
*/
34+
35+
import java.io.IO;
36+
import jdk.internal.org.jline.reader.LineReader;
37+
import jdk.internal.org.jline.terminal.Terminal;
38+
39+
import jdk.test.lib.process.OutputAnalyzer;
40+
import jdk.test.lib.process.ProcessTools;
41+
42+
public class LazyJdkConsoleProvider {
43+
44+
public static void main(String... args) throws Throwable {
45+
switch (args.length > 0 ? args[0] : "default") {
46+
case "write" -> {
47+
System.console().println("Hello!");
48+
System.console().print("Hello!");
49+
System.console().format("\nHello!\n");
50+
System.console().flush();
51+
IO.println("Hello!");
52+
IO.print("Hello!");
53+
}
54+
case "read" -> System.console().readLine("Hello!");
55+
case "IO-read" -> {
56+
IO.readln("Hello!");
57+
}
58+
case "default" -> {
59+
new LazyJdkConsoleProvider().runTest();
60+
}
61+
}
62+
}
63+
64+
void runTest() throws Exception {
65+
record TestCase(String testKey, String expected, String notExpected) {}
66+
TestCase[] testCases = new TestCase[] {
67+
new TestCase("write", null, Terminal.class.getName()),
68+
new TestCase("read", LineReader.class.getName(), null),
69+
new TestCase("IO-read", LineReader.class.getName(), null)
70+
};
71+
for (TestCase tc : testCases) {
72+
ProcessBuilder builder =
73+
ProcessTools.createTestJavaProcessBuilder("--enable-preview",
74+
"-verbose:class",
75+
"-Djdk.console=jdk.internal.le",
76+
LazyJdkConsoleProvider.class.getName(),
77+
tc.testKey());
78+
OutputAnalyzer output = ProcessTools.executeProcess(builder, "");
79+
80+
output.waitFor();
81+
82+
if (output.getExitValue() != 0) {
83+
throw new AssertionError("Unexpected return value: " + output.getExitValue() +
84+
", actualOut: " + output.getStdout() +
85+
", actualErr: " + output.getStderr());
86+
}
87+
if (tc.expected() != null) {
88+
output.shouldContain(tc.expected());
89+
}
90+
91+
if (tc.notExpected() != null) {
92+
output.shouldNotContain(tc.notExpected());
93+
}
94+
}
95+
}
96+
97+
}

0 commit comments

Comments
 (0)