Skip to content

Commit

Permalink
[NETBEANS-5744] Avoid java.io deadlock of close vs. pending read.
Browse files Browse the repository at this point in the history
  • Loading branch information
sdedic committed Jun 3, 2021
1 parent a15d4b6 commit ba5bb8a
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 3 deletions.
Expand Up @@ -71,7 +71,18 @@ public void stop() {
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;
Expand All @@ -85,6 +96,7 @@ public void stdIn(String line) throws IOException {
}
inputBuffer.write(line);
inputBuffer.newLine();
inputBuffer.flush();
}

@Override
Expand Down
Expand Up @@ -30,7 +30,6 @@
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> {
Expand Down Expand Up @@ -145,7 +144,14 @@ public static final class LspIO {
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]) {
Expand Down
@@ -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);
}
}
@@ -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);
}
}

0 comments on commit ba5bb8a

Please sign in to comment.