This is an automated email from the ASF dual-hosted git repository. sdedic pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new ba5bb8a [NETBEANS-5744] Avoid java.io deadlock of close vs. pending read. new 1ca0470 Merge pull request #2986 from sdedic/lsp/consoleInputLockup ba5bb8a is described below commit ba5bb8ab4977793354265d0d9f46ef3a4da98c0f Author: Svata Dedic <svatopluk.de...@oracle.com> AuthorDate: Thu Jun 3 15:36:32 2021 +0200 [NETBEANS-5744] Avoid java.io deadlock of close vs. pending read. --- .../server/debugging/launch/NbProcessConsole.java | 14 ++- .../server/ui/AbstractLspInputOutputProvider.java | 10 +- .../debugging/launch/NbProcessConsoleTest.java | 114 +++++++++++++++++++++ .../modules/java/lsp/server/ui/LspIOAccessor.java | 36 +++++++ 4 files changed, 171 insertions(+), 3 deletions(-) diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbProcessConsole.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbProcessConsole.java index ce857f6..55d39da 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbProcessConsole.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbProcessConsole.java @@ -71,7 +71,18 @@ public final class NbProcessConsole extends IOContext { protected InputStream getStdIn() throws IOException { synchronized (this) { if (inputSink == null) { - inputSink = new PipedInputStream(inputSource); + inputSink = new PipedInputStream(inputSource) { + @Override + public void close() throws IOException { + synchronized(this) { + super.close(); + // Bug in Piped*Stream: in.close() closes, but does + // not unblock waiters, nor returns -1 from writer to + // stop waiting. Close the writer - will also notifyAll(). + inputSource.close(); + } + } + }; } } return inputSink; @@ -85,6 +96,7 @@ public final class NbProcessConsole extends IOContext { } inputBuffer.write(line); inputBuffer.newLine(); + inputBuffer.flush(); } @Override diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractLspInputOutputProvider.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractLspInputOutputProvider.java index fb9acfd..e128015 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractLspInputOutputProvider.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractLspInputOutputProvider.java @@ -30,7 +30,6 @@ import org.netbeans.api.io.OutputColor; import org.netbeans.api.io.ShowOperation; import org.netbeans.modules.java.lsp.server.ui.AbstractLspInputOutputProvider.LspIO; import org.netbeans.spi.io.InputOutputProvider; -import org.openide.util.Exceptions; import org.openide.util.Lookup; public abstract class AbstractLspInputOutputProvider implements InputOutputProvider<LspIO, PrintWriter, Void, Void> { @@ -145,7 +144,14 @@ public abstract class AbstractLspInputOutputProvider implements InputOutputProvi this.err = new PrintWriter(new LspWriter(false)); Reader in; try { - in = new InputStreamReader(ioCtx.getStdIn(), "UTF-8"); + in = new InputStreamReader(ioCtx.getStdIn(), "UTF-8") { + @Override + public void close() throws IOException { + // the underlying StreamDecoder would just block on synchronized read(); close the underlying stream. + ioCtx.getStdIn().close(); + super.close(); + } + }; } catch (IOException ex) { err.write(ex.getLocalizedMessage()); in = new CharArrayReader(new char[0]) { diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbProcessConsoleTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbProcessConsoleTest.java new file mode 100644 index 0000000..7f7fd85 --- /dev/null +++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbProcessConsoleTest.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.lsp.server.debugging.launch; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.netbeans.modules.java.lsp.server.ui.AbstractLspInputOutputProvider; +import org.netbeans.modules.java.lsp.server.ui.LspIOAccessor; +import org.openide.util.Lookup; + +/** + * + * @author sdedic + */ +public class NbProcessConsoleTest { + NbProcessConsole console = new NbProcessConsole(new Consumer<NbProcessConsole.ConsoleMessage>() { + @Override + public void accept(NbProcessConsole.ConsoleMessage t) { + } + }); + + @Test + public void testConsoleClose() throws Exception { + InputStream sin = console.getStdIn(); + BufferedReader rdr = new BufferedReader(new InputStreamReader(sin, "UTF-8")); + assertReaderClosed(rdr); + } + + void assertReaderClosed(BufferedReader rdr) throws IOException { + InputStream sin = console.getStdIn(); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + AtomicBoolean read1 = new AtomicBoolean(false); + + // do not wait > 5 secs, abort + scheduler.schedule(new Callable<Void>() { + @Override + public Void call() throws Exception { + if (!read1.get()) { + System.err.println("CLOSED!"); + sin.close(); + } + return null; + } + }, 5, TimeUnit.SECONDS); + + scheduler.schedule(new Callable<Void>() { + @Override + public Void call() throws Exception { + console.stdIn("Hello, world!"); + return null; + } + }, 100, TimeUnit.MILLISECONDS); + + assertEquals("Hello, world!", rdr.readLine()); + read1.set(true); + + console.stdIn("Still there"); + assertEquals("Still there", rdr.readLine()); + + // do not wait > 5 secs, abort + scheduler.schedule(new Callable<Void>() { + @Override + public Void call() throws Exception { + // close asynchronously + sin.close(); + return null; + } + }, 300, TimeUnit.MILLISECONDS); + + long millis = System.currentTimeMillis(); + assertNull(rdr.readLine()); + long millis2 = System.currentTimeMillis(); + + assertTrue("Close should be delayed.", millis2 - millis >= 300); + } + + @Test + public void testCloseLspIOContextInput() throws Exception { + AbstractLspInputOutputProvider.LspIO io = LspIOAccessor.createIO("test", console, Lookup.EMPTY); + + Reader r = LspIOAccessor.reader(io); + BufferedReader rdr = new BufferedReader(r); + assertReaderClosed(rdr); + } +} diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ui/LspIOAccessor.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ui/LspIOAccessor.java new file mode 100644 index 0000000..b74e62c --- /dev/null +++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ui/LspIOAccessor.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.lsp.server.ui; + +import java.io.Reader; +import org.openide.util.Lookup; + +/** + * + * @author sdedic + */ +public class LspIOAccessor { + public static Reader reader(AbstractLspInputOutputProvider.LspIO io) { + return io.in; + } + + public static AbstractLspInputOutputProvider.LspIO createIO(String name, IOContext ioCtx, Lookup lookup) { + return new AbstractLspInputOutputProvider.LspIO(name, ioCtx, lookup); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists