This is an automated email from the ASF dual-hosted git repository.
entl 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 1b0f60d47a Output from pty is fully read after debugger exit.
1b0f60d47a is described below
commit 1b0f60d47a0a5b8b208aec55a76aa5f93cb154fe
Author: Martin Entlicher <[email protected]>
AuthorDate: Tue Jun 28 23:13:30 2022 +0200
Output from pty is fully read after debugger exit.
---
.../modules/cpplite/debugger/CPPLiteDebugger.java | 106 +++++++++++------
.../utils/InputStreamWithCloseDetection.java | 105 +++++++++++++++++
.../modules/cpplite/debugger/OutputTest.java | 126 +++++++++++++++++++++
.../extexecution/base/ProcessInputStream.java | 11 +-
.../base/input/DefaultInputReader.java | 2 +-
5 files changed, 308 insertions(+), 42 deletions(-)
diff --git
a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
index bb41a6fec1..2f143c6a14 100644
---
a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
+++
b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
@@ -60,6 +60,7 @@ import org.netbeans.modules.cnd.debugger.gdb2.mi.MITList;
import org.netbeans.modules.cnd.debugger.gdb2.mi.MITListItem;
import org.netbeans.modules.cnd.debugger.gdb2.mi.MIValue;
import org.netbeans.modules.cpplite.debugger.breakpoints.CPPLiteBreakpoint;
+import
org.netbeans.modules.cpplite.debugger.utils.InputStreamWithCloseDetection;
import org.netbeans.modules.nativeexecution.api.ExecutionEnvironmentFactory;
import org.netbeans.modules.nativeexecution.api.pty.Pty;
import org.netbeans.modules.nativeexecution.api.pty.PtySupport;
@@ -77,6 +78,7 @@ import org.openide.text.Annotatable;
import org.openide.text.Line;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
import org.openide.util.Pair;
import org.openide.util.RequestProcessor;
@@ -89,10 +91,8 @@ public final class CPPLiteDebugger {
private static final Logger LOGGER =
Logger.getLogger(CPPLiteDebugger.class.getName());
- private CPPLiteDebuggerConfig configuration;
- private CPPLiteDebuggerEngineProvider engineProvider;
- private ContextProvider contextProvider;
- private Process debuggee;
+ private final CPPLiteDebuggerEngineProvider engineProvider;
+ private final ContextProvider contextProvider;
private LiteMIProxy proxy;
private volatile Object currentLine;
private volatile boolean suspended = false;
@@ -102,18 +102,15 @@ public final class CPPLiteDebugger {
private final ThreadsCollector threadsCollector = new
ThreadsCollector(this);
private volatile CPPThread currentThread;
private volatile CPPFrame currentFrame;
- private AtomicInteger exitCode = new AtomicInteger();
+ private final AtomicInteger exitCode = new AtomicInteger();
public CPPLiteDebugger(ContextProvider contextProvider) {
this.contextProvider = contextProvider;
- configuration = contextProvider.lookupFirst(null,
CPPLiteDebuggerConfig.class);
// init engineProvider
engineProvider = (CPPLiteDebuggerEngineProvider)
contextProvider.lookupFirst(null, DebuggerEngineProvider.class);
}
void setDebuggee(Process debuggee, boolean printObjects) {
- this.debuggee = debuggee;
-
CPPLiteInjector injector = new
CPPLiteInjector(debuggee.getOutputStream());
this.proxy = new LiteMIProxy(injector, "(gdb)", "UTF-8");
@@ -129,7 +126,7 @@ public final class CPPLiteDebugger {
Exceptions.printStackTrace(ex);
}
// Debug I/O has finished.
- finish(false);
+ proxy.close();
}).start();
proxy.waitStarted();
@@ -148,6 +145,7 @@ public final class CPPLiteDebugger {
proxy.send(new Command("-exec-run"));
}
+ @NbBundle.Messages("MSG_DebuggerDisconnected=Debugger is disconnected")
private static class CPPLiteInjector implements MICommandInjector {
private final OutputStream out;
@@ -163,10 +161,16 @@ public final class CPPLiteDebugger {
out.write(data.getBytes());
out.flush();
} catch (IOException ex) {
- throw new IllegalStateException(ex);
+
Exceptions.printStackTrace(Exceptions.attachLocalizedMessage(ex,
Bundle.MSG_DebuggerDisconnected()));
}
}
+ void close() {
+ try {
+ out.close();
+ } catch (IOException ex) {}
+ }
+
@Override
public void log(String data) {
LOGGER.log(Level.FINE, "CPPLiteInjector.log({0})", data);
@@ -394,6 +398,29 @@ public final class CPPLiteDebugger {
LOGGER.fine("finish() done, build finished.");
}
+ private void programExited(int exitCode) {
+ this.exitCode.set(exitCode);
+ proxy.close(); // We close the communication with GDB when the program
finishes.
+ }
+
+ private void spawnFinishWhenClosed(Pty pty,
InputStreamWithCloseDetection... ins) {
+ new RequestProcessor("GDB finish and pty deallocator").post(() -> {
+ try {
+ for (InputStreamWithCloseDetection in : ins) {
+ if (in != null) {
+ in.waitForClose();
+ }
+ }
+ } catch (InterruptedException ex) {}
+ try {
+ PtySupport.deallocate(pty);
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ finish(false);
+ });
+ }
+
public String readMemory(String address, long offset, int length) {
MIRecord memory;
String offsetArg;
@@ -576,14 +603,16 @@ public final class CPPLiteDebugger {
private class LiteMIProxy extends MIProxy {
+ private final CPPLiteInjector injector;
private final CountDownLatch startedLatch = new CountDownLatch(1);
private final CountDownLatch runningLatch = new CountDownLatch(1);
private final CountDownLatch runningCommandLatch = new
CountDownLatch(0);
private final Semaphore runningCommandSemaphore = new Semaphore(1);
private final Object sendLock = new Object();
- LiteMIProxy(MICommandInjector injector, String prompt, String
encoding) {
+ LiteMIProxy(CPPLiteInjector injector, String prompt, String encoding) {
super(injector, prompt, encoding);
+ this.injector = injector;
}
@Override
@@ -635,7 +664,7 @@ public final class CPPLiteDebugger {
exitCode = 0;
}
}
- finish(true, exitCode);
+ programExited(exitCode);
} else {
threadsCollector.remove(threadId);
}
@@ -765,6 +794,10 @@ public final class CPPLiteDebugger {
Exceptions.printStackTrace(ex);
}
}
+
+ void close() {
+ injector.close();
+ }
}
public interface StateListener extends EventListener {
@@ -828,40 +861,33 @@ public final class CPPLiteDebugger {
ProcessBuilder processBuilder = new ProcessBuilder(executable);
setParameters(processBuilder, configuration);
Process debuggee = processBuilder.start();
- new RequestProcessor(configuration.getDisplayName() + " (pty
deallocator)").post(() -> { // NOI18N
- try {
- while (debuggee.isAlive()) {
- try {
- debuggee.waitFor();
- } catch (InterruptedException ex) {
- //ignore...
- }
- }
- } finally {
- try {
- PtySupport.deallocate(pty);
- } catch (IOException ex) {
- Exceptions.printStackTrace(ex);
- }
- }
- });
debugger.setDebuggee(debuggee, configuration.isPrintObjects());
AtomicInteger exitCode = debugger.exitCode;
return new Process() {
+
+ private InputStreamWithCloseDetection std;
+ private InputStreamWithCloseDetection err;
+
@Override
public OutputStream getOutputStream() {
return pty.getOutputStream();
}
@Override
- public InputStream getInputStream() {
- return pty.getInputStream();
+ public synchronized InputStream getInputStream() {
+ if (std == null) {
+ std = new
InputStreamWithCloseDetection(pty.getInputStream());
+ }
+ return std;
}
@Override
- public InputStream getErrorStream() {
- return pty.getErrorStream();
+ public synchronized InputStream getErrorStream() {
+ if (err == null) {
+ err = new
InputStreamWithCloseDetection(pty.getErrorStream());
+ }
+ return err;
}
@Override
@@ -872,12 +898,24 @@ public final class CPPLiteDebugger {
@Override
public int waitFor() throws InterruptedException {
debuggee.waitFor();
+ // We do not plan to write to PTY any more, close its input,
+ // PTY will close its output then.
+ try {
+ pty.getOutputStream().close();
+ } catch (IOException ex) {}
+ debugger.spawnFinishWhenClosed(pty, std, err);
return exitCode.get();
}
@Override
public int exitValue() {
- return debuggee.exitValue();
+ int debugExit = debuggee.exitValue();
+ int programExit = exitCode.get();
+ if (programExit != 0) {
+ return programExit;
+ } else {
+ return debugExit;
+ }
}
@Override
diff --git
a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/utils/InputStreamWithCloseDetection.java
b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/utils/InputStreamWithCloseDetection.java
new file mode 100644
index 0000000000..4c29b389f2
--- /dev/null
+++
b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/utils/InputStreamWithCloseDetection.java
@@ -0,0 +1,105 @@
+/*
+ * 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.cpplite.debugger.utils;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * InputStream wrapper, which detects end of stream.
+ * Use {@link InputStreamWithCloseDetection#waitForClose()} to wait for either
an
+ * explicit {@link InputStream#close()}, or EOF during read operations.
+ *
+ * @author Martin Entlicher
+ */
+public final class InputStreamWithCloseDetection extends FilterInputStream {
+
+ private final CountDownLatch closed = new CountDownLatch(1);
+
+ public InputStreamWithCloseDetection(InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public int read() throws IOException {
+ int r;
+ try {
+ r = super.read();
+ } catch (IOException ex) {
+ notifyClosed();
+ throw ex;
+ }
+ if (r == -1) {
+ notifyClosed();
+ }
+ return r;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ int l;
+ try {
+ l = super.read(b);
+ } catch (IOException ex) {
+ notifyClosed();
+ throw ex;
+ }
+ if (l == -1) {
+ notifyClosed();
+ }
+ return l;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int l;
+ try {
+ l = super.read(b, off, len);
+ } catch (IOException ex) {
+ notifyClosed();
+ throw ex;
+ }
+ if (l == -1) {
+ notifyClosed();
+ }
+ return l;
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ super.close();
+ } finally {
+ notifyClosed();
+ }
+ }
+
+ private void notifyClosed() {
+ closed.countDown();
+ }
+
+ /**
+ * Wait till this stream is closed, or at EOF.
+ */
+ public void waitForClose() throws InterruptedException {
+ closed.await();
+ }
+}
diff --git
a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/OutputTest.java
b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/OutputTest.java
new file mode 100644
index 0000000000..e5b0413ccb
--- /dev/null
+++
b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/OutputTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.cpplite.debugger;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import junit.framework.Test;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import org.netbeans.api.debugger.ActionsManager;
+import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.junit.NbModuleSuite;
+import static org.netbeans.modules.cpplite.debugger.AbstractDebugTest.compileC;
+import org.netbeans.modules.cpplite.debugger.breakpoints.CPPLiteBreakpoint;
+import org.openide.cookies.LineCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.loaders.DataObject;
+
+/**
+ * Test of application output.
+ */
+public class OutputTest extends AbstractDebugTest {
+
+ public OutputTest (String s) {
+ super (s);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ clearWorkDir();
+ }
+
+ private static String repeatText(String text, int count) {
+ StringBuilder sb = new StringBuilder(text);
+ while (--count > 0) {
+ sb.append(text);
+ }
+ return sb.toString();
+ }
+
+ public void testOutputNoSuspend() throws Exception {
+ String textToStdOut = repeatText("Text to STD OUT\\n", 100);
+ File wd = getWorkDir();
+ FileObject source = FileUtil.createData(FileUtil.toFileObject(wd),
"output1.c");
+ try (OutputStream os = source.getOutputStream();
+ Writer w = new OutputStreamWriter(os)) {
+ w.append("#include<stdio.h>\n" +
+ "\n" +
+ "int main(int argc, char** args) {\n" +
+ " printf(\""+textToStdOut+"\");\n" +
+ "}");
+ }
+ compileC("output1", wd);
+ LineCookie lc =
DataObject.find(source).getLookup().lookup(LineCookie.class);
+ assertNotNull(lc);
+ startDebugging("output1", wd);
+
+ assertEquals(0, waitAppProcessExit());
+
+ assertEquals(textToStdOut.replace("\\n", "\n"), stdOut.toString());
+ }
+
+ public void testOutputSteps() throws Exception {
+ final int N = 10;
+ File wd = getWorkDir();
+ FileObject source = FileUtil.createData(FileUtil.toFileObject(wd),
"output2.c");
+ try (OutputStream os = source.getOutputStream();
+ Writer w = new OutputStreamWriter(os)) {
+ w.append("#include<stdio.h>\n" +
+ "\n" +
+ "int main(int argc, char** args) {\n" +
+ " int N = "+N+";\n" +
+ " for (int i = 0; i < N; i++) {\n" +
+ " printf(\"Test output %d\\n\", i);\n" +
+ " }\n" +
+ "}");
+ }
+ compileC("output2", wd);
+ LineCookie lc =
DataObject.find(source).getLookup().lookup(LineCookie.class);
+ assertNotNull(lc);
+ CPPLiteBreakpoint bp6 =
CPPLiteBreakpoint.create(lc.getLineSet().getCurrent(5));
+ DebuggerManager.getDebuggerManager().addBreakpoint(bp6);
+ startDebugging("output2", wd);
+
+ StringBuilder referenceOutput = new StringBuilder();
+ for (int i = 0; i < N; i++) {
+ waitSuspended(i+1);
+ assertStoppedAt(source.toURI(), 6);
+ assertEquals(referenceOutput.toString(), stdOut.toString());
+
engine.getActionsManager().doAction(ActionsManager.ACTION_CONTINUE);
+ waitResumed(i+1);
+ referenceOutput.append("Test output ");
+ referenceOutput.append(i);
+ referenceOutput.append('\n');
+ }
+ assertEquals(0, waitAppProcessExit());
+ assertEquals(referenceOutput.toString(), stdOut.toString());
+ }
+
+ public static Test suite() {
+ return NbModuleSuite.emptyConfiguration()
+ .addTest(OutputTest.class)
+ .enableModules(".*", ".*")
+ .gui(false)
+ .suite();
+ }
+}
diff --git
a/ide/extexecution.base/src/org/netbeans/modules/extexecution/base/ProcessInputStream.java
b/ide/extexecution.base/src/org/netbeans/modules/extexecution/base/ProcessInputStream.java
index 38470b6964..df2ca47bab 100644
---
a/ide/extexecution.base/src/org/netbeans/modules/extexecution/base/ProcessInputStream.java
+++
b/ide/extexecution.base/src/org/netbeans/modules/extexecution/base/ProcessInputStream.java
@@ -105,6 +105,9 @@ public final class ProcessInputStream extends
FilterInputStream {
public synchronized int read(byte[] b, int off, int len) throws
IOException {
if (buffer != null) {
int available = buffer.length - position;
+ if (available == 0) {
+ return -1;
+ }
int size = Math.min(len, available);
System.arraycopy(buffer, position, b, off, size);
position += size;
@@ -136,13 +139,7 @@ public final class ProcessInputStream extends
FilterInputStream {
if (drain) {
LOGGER.log(Level.FINE, "Draining process stream");
- boolean running = false;
- try {
- process.exitValue();
- } catch (IllegalThreadStateException ex) {
- running = true;
- }
-
+ boolean running = process.isAlive();
if (running) {
LOGGER.log(Level.FINE, "Process is still running");
}
diff --git
a/ide/extexecution.base/src/org/netbeans/modules/extexecution/base/input/DefaultInputReader.java
b/ide/extexecution.base/src/org/netbeans/modules/extexecution/base/input/DefaultInputReader.java
index 36ed8c1dc5..62539dd4fd 100644
---
a/ide/extexecution.base/src/org/netbeans/modules/extexecution/base/input/DefaultInputReader.java
+++
b/ide/extexecution.base/src/org/netbeans/modules/extexecution/base/input/DefaultInputReader.java
@@ -49,7 +49,7 @@ public class DefaultInputReader implements InputReader {
public DefaultInputReader(Reader reader, boolean greedy) {
assert reader != null;
- this.reader = new BufferedReader(reader);
+ this.reader = reader;
this.greedy = greedy;
this.buffer = new char[greedy ? BUFFER_SIZE * 2 : BUFFER_SIZE];
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists