Bug 521211 - Docker launch fails debugging some C/C++ programs

- using normal logging doesn't always work when debugging via gdbserver
  (e.g. a program that printfs without a newline and then uses
  an fflush of stdout)
- add a new DockerConsoleOutputStream class to core that will
  accept listeners that will be notified whenever writes are made
- change openTerminal method in DockerConnection to accept a
  DockerConsoleOutputStream as input parameter and to use this
  to echo stdout and stderr output of the terminal
- change RunConsole.attachTerminal method to accept a
  DockerConsoleOutputStream argument
- change ContainerLauncher launch method to determine when
  called to do a gdbserver launch from CDT (look at
  ContainerListener class name)
- when it is determined that we are doing a gdbserver launch,
  set the TTY option on and pass a DockerConsoleOutput stream
  to RunConsole.attachTerminal which will pass this on to
  openTerminal
- set up the DockerConsoleOutputStream to echo output to the
  normal RunConsole used for running and debugging
- when the session is complete, remove any Terminal that was
  created on behalf of the session
- change all callers of RunConsole.attachTerminal appropriately
  to pass null as last argument
- remove ConsoleOutputStream from docker ui
  
  
Change-Id: I17758f1ed9f0af6df754af88570cc4b7767c9638
Reviewed-on: https://git.eclipse.org/r/103419
Tested-by: Hudson CI
Reviewed-by: Jeff Johnston <jjohnstn@redhat.com>
diff --git a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java
index 4831fe9..e8eaaae 100644
--- a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java
+++ b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java
@@ -89,6 +89,7 @@
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.tm.terminal.view.core.TerminalServiceFactory;
 import org.eclipse.tm.terminal.view.core.interfaces.ITerminalService;
+import org.eclipse.tm.terminal.view.core.interfaces.ITerminalServiceOutputStreamMonitorListener;
 import org.eclipse.tm.terminal.view.core.interfaces.constants.ITerminalsConnectorConstants;
 
 import com.google.common.collect.ImmutableMap;
@@ -581,6 +582,7 @@
 				getContainers(force);
 			} catch (DockerException e) {
 				Activator.log(e);
+				return Collections.emptyList();
 			}
 
 		} else if (!isContainersLoaded() || force) {
@@ -984,6 +986,7 @@
 				latestImages = getImages(force);
 			} catch (DockerException e) {
 				Activator.log(e);
+				return Collections.emptyList();
 			}
 		} else if (!isImagesLoaded() || force) {
 			try {
@@ -1933,7 +1936,6 @@
 				Activator.log(e);
 			}
 		}
-
 	}
 
 	@Override
@@ -2007,7 +2009,7 @@
 	}
 
 	public void attachCommand(final String id, final InputStream in,
-			@SuppressWarnings("unused") final OutputStream out)
+			@SuppressWarnings("unused") final DockerConsoleOutputStream out)
 					throws DockerException {
 
 		final byte[] prevCmd = new byte[1024];
@@ -2021,7 +2023,7 @@
 			final boolean isOpenStdin = info.config().openStdin();
 
 			if (isTtyEnabled) {
-				openTerminal(pty_stream, info.name());
+				openTerminal(pty_stream, info.name(), out);
 			}
 
 			// Data from the given input stream
@@ -2130,16 +2132,43 @@
 			final LogStream pty_stream = client.execStart(execId,
 					DockerClient.ExecStartParameter.TTY);
 			final IDockerContainerInfo info = getContainerInfo(id);
-			openTerminal(pty_stream, info.name() + " [shell]"); //$NON-NLS-1$
+			openTerminal(pty_stream, info.name() + " [shell]", null); //$NON-NLS-1$
 		} catch (Exception e) {
 			throw new DockerException(e.getMessage(), e.getCause());
 		}
 	}
 
-	private void openTerminal(LogStream pty_stream, String name) throws DockerException {
+	private class TerminalOutputMonitorListener
+			implements ITerminalServiceOutputStreamMonitorListener {
+
+		private DockerConsoleOutputStream consoleOutputStream;
+
+		public TerminalOutputMonitorListener(DockerConsoleOutputStream out) {
+			this.consoleOutputStream = out;
+		}
+
+		@Override
+		public void onContentReadFromStream(byte[] byteBuffer, int bytesRead) {
+			try {
+				if (consoleOutputStream != null) {
+					consoleOutputStream.write(byteBuffer, 0, bytesRead);
+				}
+			} catch (IOException e) {
+				Activator.log(e);
+			}
+		}
+
+	}
+
+	private void openTerminal(LogStream pty_stream, String name,
+			DockerConsoleOutputStream out) throws DockerException {
 		try {
-			OutputStream tout = noBlockingOutputStream(HttpHijackWorkaround.getOutputStream(pty_stream, getUri()));
+			OutputStream tout = noBlockingOutputStream(
+					HttpHijackWorkaround.getOutputStream(pty_stream, getUri()));
 			InputStream tin = HttpHijackWorkaround.getInputStream(pty_stream);
+			
+			TerminalOutputMonitorListener monitor = new TerminalOutputMonitorListener(out);
+			
 			// org.eclipse.tm.terminal.connector.ssh.controls.SshWizardConfigurationPanel
 			Map<String, Object> properties = new HashMap<>();
 			properties.put(ITerminalsConnectorConstants.PROP_DELEGATE_ID,
@@ -2152,6 +2181,10 @@
 			properties.put(ITerminalsConnectorConstants.PROP_FORCE_NEW, true);
 			properties.put(ITerminalsConnectorConstants.PROP_STREAMS_STDIN, tout);
 			properties.put(ITerminalsConnectorConstants.PROP_STREAMS_STDOUT, tin);
+			properties.put(ITerminalsConnectorConstants.PROP_STDERR_LISTENERS, new ITerminalServiceOutputStreamMonitorListener[] {monitor});
+			properties.put(ITerminalsConnectorConstants.PROP_STDOUT_LISTENERS,
+					new ITerminalServiceOutputStreamMonitorListener[] {
+							monitor });
 			properties.put(ITerminalsConnectorConstants.PROP_DATA, pty_stream);
 			/*
 			 * The JVM will call finalize() on 'pty_stream' (LogStream)
@@ -2161,6 +2194,10 @@
 			 * used so we must preserve a reference to it.
 			 */
 			properties.put("PREVENT_JVM_GC_FINALIZE", pty_stream); //$NON-NLS-1$
+			// save properties to remove terminal later
+			if (out != null) {
+				out.setTerminalProperties(properties);
+			}
 			ITerminalService service = TerminalServiceFactory.getService();
 			service.openConsole(properties, null);
 		} catch (Exception e) {
diff --git a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConsoleOutputStream.java b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConsoleOutputStream.java
new file mode 100644
index 0000000..e2c1d27
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConsoleOutputStream.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Red Hat.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.core;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+
+import org.eclipse.core.runtime.ListenerList;
+
+// Special Console OutputStream which supports listeners.
+
+public class DockerConsoleOutputStream extends OutputStream {
+
+	private OutputStream stream;
+	private Map<String, Object> properties;
+
+	ListenerList<IConsoleListener> consoleListeners;
+
+	public DockerConsoleOutputStream(OutputStream stream) {
+		this.stream = stream;
+	}
+
+	public DockerConsoleOutputStream setOutputStream(OutputStream stream) {
+		this.stream = stream;
+		return this;
+	}
+
+	public void setTerminalProperties(Map<String, Object> properties) {
+		this.properties = properties;
+	}
+
+	public Map<String, Object> getTerminalProperties() {
+		return properties;
+	}
+
+	@Override
+	public void write(byte[] b) throws IOException {
+		if (stream != null) {
+			stream.write(b);
+		}
+		notifyConsoleListeners(b, 0, b.length);
+	}
+
+	@Override
+	public void write(byte[] b, int off, int len) throws IOException {
+		if (stream != null) {
+			stream.write(b, off, len);
+		}
+		notifyConsoleListeners(b, off, len);
+	}
+
+	@Override
+	public void write(int arg0) throws IOException {
+		byte[] b = new byte[1];
+		b[0] = (byte) arg0;
+		write(b);
+	}
+
+	@Override
+	public void close() throws IOException {
+		if (stream != null) {
+			stream.close();
+		}
+	}
+
+	@Override
+	public void flush() throws IOException {
+		if (stream != null) {
+			stream.flush();
+		}
+	}
+
+	public void addConsoleListener(IConsoleListener listener) {
+		if (consoleListeners == null)
+			consoleListeners = new ListenerList<>(ListenerList.IDENTITY);
+		consoleListeners.add(listener);
+	}
+
+	public void removeConsoleListener(IConsoleListener listener) {
+		if (consoleListeners != null)
+			consoleListeners.remove(listener);
+	}
+
+	public void notifyConsoleListeners(byte[] b, int off, int len) {
+		if (consoleListeners != null) {
+			String output = new String(b, off, len);
+			Object[] listeners = consoleListeners.getListeners();
+			for (int i = 0; i < listeners.length; ++i) {
+				((IConsoleListener) listeners[i]).newOutput(output);
+			}
+		}
+	}
+
+}
diff --git a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/IConsoleListener.java b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/IConsoleListener.java
new file mode 100644
index 0000000..2b3cff9
--- /dev/null
+++ b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/IConsoleListener.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Red Hat.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.linuxtools.internal.docker.core;
+
+public interface IConsoleListener {
+
+	/**
+	 * Listener receiver method called after output is written to Console.
+	 * 
+	 * @param output
+	 *            string written to console
+	 */
+	void newOutput(String output);
+
+}
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/docker/ui/launch/ContainerLauncher.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/docker/ui/launch/ContainerLauncher.java
index c5ea7e0..cc745da 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/docker/ui/launch/ContainerLauncher.java
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/docker/ui/launch/ContainerLauncher.java
@@ -48,7 +48,6 @@
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.linuxtools.docker.core.DockerConnectionManager;
 import org.eclipse.linuxtools.docker.core.DockerException;
-import org.eclipse.linuxtools.docker.core.EnumDockerLoggingStatus;
 import org.eclipse.linuxtools.docker.core.IDockerConnection;
 import org.eclipse.linuxtools.docker.core.IDockerContainerConfig;
 import org.eclipse.linuxtools.docker.core.IDockerContainerExit;
@@ -59,16 +58,22 @@
 import org.eclipse.linuxtools.docker.core.IDockerPortBinding;
 import org.eclipse.linuxtools.docker.ui.Activator;
 import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
+import org.eclipse.linuxtools.internal.docker.core.DockerConsoleOutputStream;
 import org.eclipse.linuxtools.internal.docker.core.DockerContainerConfig;
 import org.eclipse.linuxtools.internal.docker.core.DockerHostConfig;
 import org.eclipse.linuxtools.internal.docker.core.DockerPortBinding;
+import org.eclipse.linuxtools.internal.docker.core.IConsoleListener;
 import org.eclipse.linuxtools.internal.docker.ui.consoles.ConsoleOutputStream;
 import org.eclipse.linuxtools.internal.docker.ui.consoles.RunConsole;
 import org.eclipse.linuxtools.internal.docker.ui.launch.ContainerCommandProcess;
 import org.eclipse.linuxtools.internal.docker.ui.launch.LaunchConfigurationUtils;
 import org.eclipse.linuxtools.internal.docker.ui.views.DVMessages;
 import org.eclipse.linuxtools.internal.docker.ui.wizards.DataVolumeModel;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabItem;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.IWorkbenchPage;
 import org.eclipse.ui.PlatformUI;
 
 public class ContainerLauncher {
@@ -545,6 +550,25 @@
 
 	}
 
+	// The following class allows us to use internal IConsoleListeners in
+	// docker.core
+	// but still use the public IRunConsoleListeners API here without requiring
+	// a minor release.
+	private class RunConsoleListenerBridge implements IConsoleListener {
+
+		private IRunConsoleListener listener;
+
+		public RunConsoleListenerBridge(IRunConsoleListener listener) {
+			this.listener = listener;
+		}
+
+		@Override
+		public void newOutput(String output) {
+			listener.newOutput(output);
+		}
+
+	}
+
 	/**
 	 * Perform a launch of a command in a container and output stdout/stderr to
 	 * console.
@@ -652,6 +676,17 @@
 				.cmd(cmdList)
 				.image(image)
 				.workingDir(workingDir);
+
+		// Ugly hack...we want CDT gdbserver to run in the terminal so we look
+		// for its
+		// ContainerListener class and set tty=true in that case...this avoids a
+		// minor release and we can later add a new launch method with the tty
+		// option
+		if (listener != null && listener.getClass().getName().equals(
+				"org.eclipse.cdt.internal.docker.launcher.ContainerLaunchConfigurationDelegate$StartGdbServerJob")) {
+			builder = builder.tty(true);
+		}
+
 		// add any exposed ports as needed
 		if (exposedPorts.size() > 0)
 			builder = builder.exposedPorts(exposedPorts);
@@ -776,77 +811,163 @@
 							return;
 					}
 				}
-				OutputStream stream = null;
-				RunConsole oldConsole = getConsole();
-				final RunConsole rc = RunConsole.findConsole(containerId,
-						consoleId);
-				setConsole(rc);
-				rc.clearConsole();
-				if (oldConsole != null)
-					RunConsole.removeConsole(oldConsole);
-				Display.getDefault()
-						.syncExec(() -> rc.setTitle(Messages.getFormattedString(
-								LAUNCH_TITLE, new String[] { cmdList.get(0),
-										imageName })));
-				// if (!rc.isAttached()) {
-				rc.attachToConsole(connection, containerId);
-				// }
-				if (rc != null) {
-					stream = rc.getOutputStream();
-					if (containerListener != null) {
-						((ConsoleOutputStream) stream)
-								.addConsoleListener(containerListener);
+				if (config.tty()) {
+					// We need tty support to handle issue with Docker daemon
+					// not always outputting in time (e.g. we might get an
+					// output line after the process has exited which can be
+					// too late to show or it might get displayed in a wrong
+					// order in relation to other output. We also want the
+					// output to ultimately show up in the Console View.
+					OutputStream stream = null;
+					RunConsole oldConsole = getConsole();
+					final RunConsole rc = RunConsole.findConsole(containerId,
+							consoleId);
+					setConsole(rc);
+					rc.clearConsole();
+					if (oldConsole != null)
+						RunConsole.removeConsole(oldConsole);
+					Display.getDefault().syncExec(() -> rc.setTitle(Messages
+							.getFormattedString(LAUNCH_TITLE, new String[] {
+									cmdList.get(0), imageName })));
+					if (rc != null) {
+						stream = rc.getOutputStream();
 					}
-				}
-				// Create a unique logging thread id which has container id
-				// and console id
-				String loggingId = containerId + "." + consoleId;
-				((DockerConnection) connection).startContainer(containerId,
-						loggingId, stream);
-				if (rc != null)
-					rc.showConsole();
-				if (containerListener != null) {
+
+					// We want terminal support, but we want to output to the
+					// RunConsole.
+					// To do this, we create a DockerConsoleOutputStream which
+					// we
+					// hook into the TM Terminal via stdout and stderr output
+					// listeners.
+					// These listeners will output to the
+					// DockerConsoleOutputStream which
+					// will in turn output to the RunConsole. See
+					// DockerConnection.openTerminal().
+					DockerConsoleOutputStream out = new DockerConsoleOutputStream(
+							stream);
+					RunConsole.attachToTerminal(connection, containerId, out);
+					if (containerListener != null) {
+						out.addConsoleListener(new RunConsoleListenerBridge(
+								containerListener));
+					}
+					((DockerConnection) connection).startContainer(containerId,
+							null, null);
 					IDockerContainerInfo info = ((DockerConnection) connection)
 							.getContainerInfo(containerId);
-					containerListener.containerInfo(info);
-				}
-
-				// Wait for the container to finish
-				final IDockerContainerExit status = ((DockerConnection) connection)
-						.waitForContainer(containerId);
-				Display.getDefault().syncExec(() -> {
-					rc.setTitle(
-							Messages.getFormattedString(LAUNCH_EXITED_TITLE,
-									new String[] {
-											status.statusCode().toString(),
-											cmdList.get(0), imageName }));
-					rc.showConsole();
-				});
-
-				// Let any container listener know that the container is
-				// finished
-				if (containerListener != null)
-					containerListener.done();
-
-				if (!keepContainer) {
-					// Drain the logging thread before we remove the
-					// container (we need to use the logging id)
-					((DockerConnection) connection)
-							.stopLoggingThread(loggingId);
-					while (((DockerConnection) connection).loggingStatus(
-							loggingId) == EnumDockerLoggingStatus.LOGGING_ACTIVE) {
-						Thread.sleep(1000);
+					if (containerListener != null) {
+						containerListener.containerInfo(info);
 					}
-					// Look for any Display Log console that the user may
-					// have opened which would be
-					// separate and make sure it is removed as well
-					RunConsole rc2 = RunConsole
-							.findConsole(((DockerConnection) connection)
-									.getContainer(containerId));
-					if (rc2 != null)
-						RunConsole.removeConsole(rc2);
-					((DockerConnection) connection)
-							.removeContainer(containerId);
+					// Wait for the container to finish
+					final IDockerContainerExit status = ((DockerConnection) connection)
+							.waitForContainer(containerId);
+					Display.getDefault().syncExec(() -> {
+						rc.setTitle(
+								Messages.getFormattedString(LAUNCH_EXITED_TITLE,
+										new String[] {
+												status.statusCode().toString(),
+												cmdList.get(0), imageName }));
+						rc.showConsole();
+						// We used a TM Terminal to receive the output of the
+						// session and
+						// then sent the output to the RunConsole. Remove the
+						// terminal
+						// tab that got created now that we are finished and all
+						// data is shown
+						// in Console View.
+						IWorkbenchPage page = PlatformUI.getWorkbench()
+								.getActiveWorkbenchWindow().getActivePage();
+						IViewPart terminalView = page.findView(
+								"org.eclipse.tm.terminal.view.ui.TerminalsView");
+						CTabFolder ctabfolder = terminalView
+								.getAdapter(CTabFolder.class);
+						if (ctabfolder != null) {
+							CTabItem[] items = ctabfolder.getItems();
+							for (CTabItem item : items) {
+								if (item.getText().endsWith(info.name())) {
+									item.dispose();
+									break;
+								}
+							}
+						 }
+					});
+					// Let any container listener know that the container is
+					// finished
+					if (containerListener != null)
+						containerListener.done();
+
+					if (!keepContainer) {
+						((DockerConnection) connection)
+								.removeContainer(containerId);
+					}
+				} else {
+					OutputStream stream = null;
+					RunConsole oldConsole = getConsole();
+					final RunConsole rc = RunConsole.findConsole(containerId,
+							consoleId);
+					setConsole(rc);
+					rc.clearConsole();
+					if (oldConsole != null)
+						RunConsole.removeConsole(oldConsole);
+					Display.getDefault().syncExec(() -> rc.setTitle(Messages
+							.getFormattedString(LAUNCH_TITLE, new String[] {
+									cmdList.get(0), imageName })));
+					// if (!rc.isAttached()) {
+					rc.attachToConsole(connection, containerId);
+					// }
+					if (rc != null) {
+						stream = rc.getOutputStream();
+						if (containerListener != null) {
+							((ConsoleOutputStream) stream)
+									.addConsoleListener(containerListener);
+						}
+					}
+					// Create a unique logging thread id which has container id
+					// and console id
+					String loggingId = containerId + "." + consoleId;
+					((DockerConnection) connection).startContainer(containerId,
+							loggingId, stream);
+					if (rc != null)
+						rc.showConsole();
+					if (containerListener != null) {
+						IDockerContainerInfo info = ((DockerConnection) connection)
+								.getContainerInfo(containerId);
+						containerListener.containerInfo(info);
+					}
+
+					// Wait for the container to finish
+					final IDockerContainerExit status = ((DockerConnection) connection)
+							.waitForContainer(containerId);
+					Display.getDefault().syncExec(() -> {
+						rc.setTitle(
+								Messages.getFormattedString(LAUNCH_EXITED_TITLE,
+										new String[] {
+												status.statusCode().toString(),
+												cmdList.get(0), imageName }));
+						rc.showConsole();
+					});
+
+					// Let any container listener know that the container is
+					// finished
+					if (containerListener != null)
+						containerListener.done();
+
+					if (!keepContainer) {
+						// Drain the logging thread before we remove the
+						// container (we need to use the logging id)
+						Thread.sleep(1000);
+						((DockerConnection) connection)
+								.stopLoggingThread(loggingId);
+						// Look for any Display Log console that the user may
+						// have opened which would be
+						// separate and make sure it is removed as well
+						RunConsole rc2 = RunConsole
+								.findConsole(((DockerConnection) connection)
+										.getContainer(containerId));
+						if (rc2 != null)
+							RunConsole.removeConsole(rc2);
+						((DockerConnection) connection)
+								.removeContainer(containerId);
+					}
 				}
 
 			} catch (final DockerException e2) {
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandUtils.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandUtils.java
index d1e0f3a..d1fcdc2 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandUtils.java
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandUtils.java
@@ -268,7 +268,7 @@
 				&& connection.getContainerInfo(container.id()) != null
 				&& connection.getContainerInfo(container.id()).config() != null
 				&& connection.getContainerInfo(container.id()).config().tty()) {
-			RunConsole.attachToTerminal(connection, container.id());
+			RunConsole.attachToTerminal(connection, container.id(), null);
 			return null;
 		}
 		final boolean autoLogOnStart = Activator.getDefault().getPreferenceStore()
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/DisplayContainerLogCommandHandler.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/DisplayContainerLogCommandHandler.java
index 0192e32..a8f9b24 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/DisplayContainerLogCommandHandler.java
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/DisplayContainerLogCommandHandler.java
@@ -49,7 +49,7 @@
 		final String name = container.name();
 
 		if (connection.getContainerInfo(id).config().tty()) {
-			RunConsole.attachToTerminal(connection, id);
+			RunConsole.attachToTerminal(connection, id, null);
 			return null;
 		}
 		try {
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/consoles/RunConsole.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/consoles/RunConsole.java
index 4a24048..116acaa 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/consoles/RunConsole.java
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/consoles/RunConsole.java
@@ -19,6 +19,7 @@
 import org.eclipse.linuxtools.docker.core.IDockerContainer;
 import org.eclipse.linuxtools.docker.core.IDockerContainerState;
 import org.eclipse.linuxtools.internal.docker.core.DockerConnection;
+import org.eclipse.linuxtools.internal.docker.core.DockerConsoleOutputStream;
 import org.eclipse.linuxtools.internal.docker.ui.views.DVMessages;
 import org.eclipse.ui.console.ConsolePlugin;
 import org.eclipse.ui.console.IConsole;
@@ -142,7 +143,6 @@
 	 */
 	public void attachToConsole(final IDockerConnection connection) {
 		final InputStream in = getInputStream();
-		final OutputStream out = newOutputStream();
 		Thread t = new Thread(() -> {
 			try {
 				DockerConnection conn = (DockerConnection) connection;
@@ -155,7 +155,7 @@
 						}
 						state = conn.getContainerInfo(containerId).state();
 					} while (!state.running() && state.finishDate() == null);
-					conn.attachCommand(containerId, in, out);
+					conn.attachCommand(containerId, in, null);
 				}
 			} catch (Exception e) {
 			}
@@ -164,7 +164,8 @@
 		attached = true;
 	}
 
-	public static void attachToTerminal (final IDockerConnection connection, final String containerId) {
+	public static void attachToTerminal(final IDockerConnection connection,
+			final String containerId, final DockerConsoleOutputStream out) {
 		Thread t = new Thread(() -> {
 			try {
 				DockerConnection conn = (DockerConnection) connection;
@@ -176,7 +177,7 @@
 					}
 					state = conn.getContainerInfo(containerId).state();
 				} while (!state.running() && state.finishDate() == null);
-				conn.attachCommand(containerId, null, null);
+				conn.attachCommand(containerId, null, out);
 			} catch (Exception e) {
 			}
 		});
diff --git a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/launch/ContainerCommandProcess.java b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/launch/ContainerCommandProcess.java
index 4797187..f58778e 100644
--- a/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/launch/ContainerCommandProcess.java
+++ b/containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/launch/ContainerCommandProcess.java
@@ -65,14 +65,32 @@
 		this.keepContainer = keepContainer;
 		// Lambda Runnable
 		Runnable logContainer = () -> {
+			PipedOutputStream pipedOut = null;
+			PipedOutputStream pipedErr = null;
 			try (PipedOutputStream pipedStdout = new PipedOutputStream(stdout);
 					PipedOutputStream pipedStderr = new PipedOutputStream(
 							stderr)) {
+				pipedOut = pipedStdout;
+				pipedErr = pipedStderr;
 				connection.attachLog(containerId, pipedStdout, pipedStderr);
 				pipedStdout.flush();
 				pipedStderr.flush();
 			} catch (DockerException | InterruptedException | IOException e) {
-				// do nothing but close output streams
+				// do nothing but flush/close output streams
+				if (pipedOut != null) {
+					try {
+						pipedOut.flush();
+					} catch (IOException e1) {
+						// ignore
+					}
+				}
+				if (pipedErr != null) {
+					try {
+						pipedErr.flush();
+					} catch (IOException e1) {
+						// ignore
+					}
+				}
 			}
 		};
 
@@ -93,12 +111,19 @@
 			} catch (InterruptedException e1) {
 				// ignore
 			}
+			thread.interrupt();
+			while (thread.isAlive()) {
+				try {
+					Thread.sleep(500);
+				} catch (InterruptedException e) {
+					// ignore
+				}
+			}
 			this.stdout.close();
 			this.stderr.close();
 		} catch (IOException e) {
 			// ignore
 		}
-		thread.interrupt();
 	}
 
 	@Override
@@ -262,6 +287,14 @@
 		try {
 			IDockerContainerExit exit = connection
 					.waitForContainer(containerId);
+			thread.interrupt();
+			while (thread.isAlive()) {
+				try {
+					Thread.sleep(500);
+				} catch (InterruptedException e) {
+					// ignore
+				}
+			}
 			connection.stopLoggingThread(containerId);
 			if (!keepContainer) {
 				connection.removeContainer(containerId);