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

Reply via email to