/******************************************************************************* * Copyright (c) 2008, 2014 Ericsson and others. * 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: * Ericsson - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.dsf.gdb.internal.ui.actions; import java.util.HashSet; import java.util.Set; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DsfExecutor; import org.eclipse.cdt.dsf.concurrent.DsfRunnable; import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.debug.service.IMultiDetach; import org.eclipse.cdt.dsf.debug.service.IProcesses; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin; import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch; import org.eclipse.cdt.dsf.service.DsfServicesTracker; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.cdt.dsf.service.DsfSession.SessionEndedListener; import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.commands.IDebugCommandRequest; import org.eclipse.debug.core.commands.IDisconnectHandler; import org.eclipse.debug.core.commands.IEnabledStateRequest; public class GdbDisconnectCommand implements IDisconnectHandler { private final DsfSession fSession; private final DsfExecutor fExecutor; private final DsfServicesTracker fTracker; public GdbDisconnectCommand(DsfSession session) { super(); fSession = session; fExecutor = session.getExecutor(); fTracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), session.getId()); } public void dispose() { fTracker.dispose(); } @Override public void canExecute(final IEnabledStateRequest request) { if (request.getElements().length == 0) { request.setEnabled(false); request.done(); return; } getContainerDMContexts(request.getElements(), new DataRequestMonitor<IContainerDMContext[]>(fExecutor, null) { @Override protected void handleCompleted() { if (!isSuccess()) { request.setEnabled(false); request.done(); } else { canDisconnect(getData(), new ImmediateDataRequestMonitor<Boolean>() { @Override protected void handleCompleted() { if (!isSuccess()) { request.setEnabled(false); } else { request.setEnabled(getData());; } request.done(); } }); } } }); } @Override public boolean execute(final IDebugCommandRequest request) { if (request.getElements().length == 0) { request.done(); return false; } getContainerDMContexts(request.getElements(), new DataRequestMonitor<IContainerDMContext[]>(fExecutor, null) { @Override protected void handleCompleted() { if (!isSuccess()) { request.setStatus(getStatus()); request.done(); } else { disconnect(getData(), new ImmediateRequestMonitor() { @Override protected void handleCompleted() { if (!isSuccess()) { request.setStatus(getStatus()); request.done(); } else { waitForTermination(request); } } }); } } }); return false; } /** * Wait for the debug session to be fully shutdown before reporting * that the terminate was completed. This is important for the * 'Terminate and remove' operation. * The wait time is limited with a timeout so as to eventually complete the * request in the case of termination error, or when terminating * a single process in a multi-process session. * See bug 377447 */ private void waitForTermination(final IDebugCommandRequest request) { // It is possible that the session already had time to terminate if (!DsfSession.isSessionActive(fSession.getId())) { request.done(); return; } // Listener that will indicate when the shutdown is complete final SessionEndedListener endedListener = new SessionEndedListener () { @Override public void sessionEnded(DsfSession session) { if (fSession.equals(session)) { DsfSession.removeSessionEndedListener(this); request.done(); } } }; DsfSession.addSessionEndedListener(endedListener); // Create the timeout // For a multi-process session, if a single process is // terminated, this timeout will always hit (unless the // session is also terminated before the timeout). // We haven't found a problem with delaying the completion // of the request that way. // Note that this timeout is not removed even if we don't // need it anymore, once the session has terminated; // instead, we let it timeout and ignore it if the session // is already terminated. fExecutor.schedule(new Runnable() { @Override public void run() { // Check that the session is still active when the timeout hits. // If it is not, then everything has been cleaned up already. if (DsfSession.isSessionActive(fSession.getId())) { DsfSession.removeSessionEndedListener(endedListener); // Marking the request as cancelled will prevent the removal of // the launch from the Debug view in case of "Terminate and Remove". // This is important for multi-process sessions when "Terminate and Remove" // is applied to one of the running processes. In this case the selected // process will be terminated but the associated launch will not be removed // from the Debug view. request.setStatus(Status.CANCEL_STATUS); request.done(); } }}, 1, TimeUnit.MINUTES); } private void getContainerDMContexts(Object[] elements, final DataRequestMonitor<IContainerDMContext[]> rm) { GdbLaunch launch = null; final Set<IContainerDMContext> contDmcs = new HashSet<IContainerDMContext>(); for (Object obj : elements) { if (obj instanceof GdbLaunch) { launch = (GdbLaunch)obj; break; } if (obj instanceof IDMVMContext) { IContainerDMContext contDmc = DMContexts.getAncestorOfType(((IDMVMContext)obj).getDMContext(), IContainerDMContext.class); if (contDmc != null) { contDmcs.add(contDmc); } } } if (launch == null) { rm.setData(contDmcs.toArray(new IContainerDMContext[contDmcs.size()])); rm.done(); } else { try { fExecutor.execute(new DsfRunnable() { @Override public void run() { ICommandControlService commandControl = fTracker.getService(ICommandControlService.class); final IProcesses procService = fTracker.getService(IProcesses.class); if (commandControl != null && procService != null) { procService.getProcessesBeingDebugged( commandControl.getContext(), new ImmediateDataRequestMonitor<IDMContext[]>() { @Override protected void handleCompleted() { if (!isSuccess()) { rm.setStatus(getStatus()); } else { for (IDMContext ctx : getData()) { IContainerDMContext contDmc = DMContexts.getAncestorOfType(ctx, IContainerDMContext.class); if (contDmc != null) { contDmcs.add(contDmc); } } rm.setData(contDmcs.toArray(new IContainerDMContext[contDmcs.size()])); } rm.done(); }; }); } else { rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, "Service is not available.")); //$NON-NLS-1$ rm.done(); } } }); } catch (RejectedExecutionException e) { rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, e.getLocalizedMessage())); rm.done(); } } } private void canDisconnect(IContainerDMContext[] contDmcs, DataRequestMonitor<Boolean> rm) { if (contDmcs.length == 0) { rm.setData(false); rm.done(); return; } IMultiDetach multiDetach = fTracker.getService(IMultiDetach.class); if (multiDetach != null) { multiDetach.canDetachDebuggerFromSomeProcesses(contDmcs, rm); } else { IProcesses procService = fTracker.getService(IProcesses.class); if (procService != null && contDmcs.length == 1) { procService.canDetachDebuggerFromProcess(contDmcs[0], rm); } else { rm.setData(false); rm.done(); } } } private void disconnect(IContainerDMContext[] contDmcs, RequestMonitor rm) { if (contDmcs.length == 0) { rm.done(); return; } IMultiDetach multiDetach = fTracker.getService(IMultiDetach.class); if (multiDetach != null) { multiDetach.detachDebuggerFromProcesses(contDmcs, rm); } else { IProcesses procService = fTracker.getService(IProcesses.class); if (procService != null && contDmcs.length == 1) { procService.detachDebuggerFromProcess(contDmcs[0], rm); } else { rm.done(); } } } }