PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java

http://github.com/JetBrains/intellij-community
Java | 422 lines | 358 code | 45 blank | 19 comment | 61 complexity | 032178fac511378c548be233244c7c95 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
  1. // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
  2. package com.intellij.debugger.impl;
  3. import com.intellij.debugger.DebugEnvironment;
  4. import com.intellij.debugger.DebuggerManagerEx;
  5. import com.intellij.debugger.JavaDebuggerBundle;
  6. import com.intellij.debugger.NameMapper;
  7. import com.intellij.debugger.engine.*;
  8. import com.intellij.debugger.settings.DebuggerSettings;
  9. import com.intellij.debugger.ui.breakpoints.BreakpointManager;
  10. import com.intellij.debugger.ui.tree.render.BatchEvaluator;
  11. import com.intellij.execution.ExecutionException;
  12. import com.intellij.execution.ExecutionResult;
  13. import com.intellij.execution.Executor;
  14. import com.intellij.execution.configurations.JavaParameters;
  15. import com.intellij.execution.configurations.RemoteConnection;
  16. import com.intellij.execution.executors.DefaultDebugExecutor;
  17. import com.intellij.execution.process.KillableColoredProcessHandler;
  18. import com.intellij.execution.process.ProcessAdapter;
  19. import com.intellij.execution.process.ProcessEvent;
  20. import com.intellij.execution.process.ProcessHandler;
  21. import com.intellij.execution.ui.RunContentDescriptor;
  22. import com.intellij.execution.ui.RunContentWithExecutorListener;
  23. import com.intellij.openapi.application.ApplicationManager;
  24. import com.intellij.openapi.components.PersistentStateComponent;
  25. import com.intellij.openapi.components.State;
  26. import com.intellij.openapi.components.Storage;
  27. import com.intellij.openapi.components.StoragePathMacros;
  28. import com.intellij.openapi.diagnostic.Logger;
  29. import com.intellij.openapi.editor.colors.EditorColorsListener;
  30. import com.intellij.openapi.editor.colors.EditorColorsManager;
  31. import com.intellij.openapi.editor.colors.EditorColorsScheme;
  32. import com.intellij.openapi.progress.ProgressIndicator;
  33. import com.intellij.openapi.progress.ProgressManager;
  34. import com.intellij.openapi.project.Project;
  35. import com.intellij.openapi.util.Comparing;
  36. import com.intellij.openapi.util.WriteExternalException;
  37. import com.intellij.psi.PsiClass;
  38. import com.intellij.util.EventDispatcher;
  39. import com.intellij.util.containers.ContainerUtil;
  40. import com.intellij.util.messages.MessageBusConnection;
  41. import com.intellij.xdebugger.XDebuggerManager;
  42. import org.jdom.Element;
  43. import org.jetbrains.annotations.NotNull;
  44. import org.jetbrains.annotations.Nullable;
  45. import javax.swing.*;
  46. import java.util.*;
  47. @State(name = "DebuggerManager", storages = @Storage(StoragePathMacros.WORKSPACE_FILE))
  48. public class DebuggerManagerImpl extends DebuggerManagerEx implements PersistentStateComponent<Element> {
  49. private static final Logger LOG = Logger.getInstance(DebuggerManagerImpl.class);
  50. public static final String LOCALHOST_ADDRESS_FALLBACK = "127.0.0.1";
  51. private static final int WAIT_KILL_TIMEOUT = 10000;
  52. private final Project myProject;
  53. private final Map<ProcessHandler, DebuggerSession> mySessions = new HashMap<>();
  54. private final BreakpointManager myBreakpointManager;
  55. private final List<NameMapper> myNameMappers = ContainerUtil.createLockFreeCopyOnWriteList();
  56. private final EventDispatcher<DebuggerManagerListener> myDispatcher = EventDispatcher.create(DebuggerManagerListener.class);
  57. private final MyDebuggerStateManager myDebuggerStateManager = new MyDebuggerStateManager();
  58. private final DebuggerContextListener mySessionListener = new DebuggerContextListener() {
  59. @Override
  60. public void changeEvent(@NotNull DebuggerContextImpl newContext, DebuggerSession.Event event) {
  61. final DebuggerSession session = newContext.getDebuggerSession();
  62. if (event == DebuggerSession.Event.PAUSE && myDebuggerStateManager.myDebuggerSession != session) {
  63. // if paused in non-active session; switch current session
  64. myDebuggerStateManager.setState(newContext, session != null? session.getState() : DebuggerSession.State.DISPOSED, event, null);
  65. return;
  66. }
  67. if (myDebuggerStateManager.myDebuggerSession == session) {
  68. myDebuggerStateManager.fireStateChanged(newContext, event);
  69. }
  70. if (event == DebuggerSession.Event.ATTACHED) {
  71. getEventPublisher().sessionAttached(session);
  72. }
  73. else if (event == DebuggerSession.Event.DETACHED) {
  74. getEventPublisher().sessionDetached(session);
  75. }
  76. else if (event == DebuggerSession.Event.DISPOSE) {
  77. dispose(session);
  78. if (myDebuggerStateManager.myDebuggerSession == session) {
  79. myDebuggerStateManager
  80. .setState(DebuggerContextImpl.EMPTY_CONTEXT, DebuggerSession.State.DISPOSED, DebuggerSession.Event.DISPOSE, null);
  81. }
  82. }
  83. }
  84. };
  85. @NotNull
  86. private DebuggerManagerListener getEventPublisher() {
  87. return myProject.getMessageBus().syncPublisher(DebuggerManagerListener.TOPIC);
  88. }
  89. @Override
  90. public void addClassNameMapper(final NameMapper mapper) {
  91. myNameMappers.add(mapper);
  92. }
  93. @Override
  94. public void removeClassNameMapper(final NameMapper mapper) {
  95. myNameMappers.remove(mapper);
  96. }
  97. @Override
  98. public String getVMClassQualifiedName(@NotNull final PsiClass aClass) {
  99. for (NameMapper nameMapper : myNameMappers) {
  100. final String qName = nameMapper.getQualifiedName(aClass);
  101. if (qName != null) {
  102. return qName;
  103. }
  104. }
  105. return aClass.getQualifiedName();
  106. }
  107. @Override
  108. public void addDebuggerManagerListener(@NotNull DebuggerManagerListener listener) {
  109. myDispatcher.addListener(listener);
  110. }
  111. @Override
  112. public void removeDebuggerManagerListener(@NotNull DebuggerManagerListener listener) {
  113. myDispatcher.removeListener(listener);
  114. }
  115. public DebuggerManagerImpl(@NotNull Project project) {
  116. myProject = project;
  117. myBreakpointManager = new BreakpointManager(myProject, this);
  118. MessageBusConnection busConnection = project.getMessageBus().connect();
  119. if (!project.isDefault()) {
  120. busConnection.subscribe(EditorColorsManager.TOPIC, new EditorColorsListener() {
  121. @Override
  122. public void globalSchemeChange(EditorColorsScheme scheme) {
  123. getBreakpointManager().updateBreakpointsUI();
  124. }
  125. });
  126. busConnection.subscribe(DebuggerManagerListener.TOPIC, myDispatcher.getMulticaster());
  127. }
  128. myBreakpointManager.addListeners(busConnection);
  129. }
  130. @Nullable
  131. @Override
  132. public DebuggerSession getSession(DebugProcess process) {
  133. ApplicationManager.getApplication().assertIsDispatchThread();
  134. return ContainerUtil.find(getSessions(), debuggerSession -> process == debuggerSession.getProcess());
  135. }
  136. @NotNull
  137. @Override
  138. public Collection<DebuggerSession> getSessions() {
  139. synchronized (mySessions) {
  140. final Collection<DebuggerSession> values = mySessions.values();
  141. return values.isEmpty() ? Collections.emptyList() : new ArrayList<>(values);
  142. }
  143. }
  144. @Nullable
  145. @Override
  146. public Element getState() {
  147. Element state = new Element("state");
  148. myBreakpointManager.writeExternal(state);
  149. return state;
  150. }
  151. @Override
  152. public void loadState(@NotNull Element state) {
  153. myBreakpointManager.readExternal(state);
  154. }
  155. public void writeExternal(Element element) throws WriteExternalException {
  156. myBreakpointManager.writeExternal(element);
  157. }
  158. @Override
  159. @Nullable
  160. public DebuggerSession attachVirtualMachine(@NotNull DebugEnvironment environment) throws ExecutionException {
  161. ApplicationManager.getApplication().assertIsDispatchThread();
  162. DebugProcessEvents debugProcess = new DebugProcessEvents(myProject);
  163. DebuggerSession session = DebuggerSession.create(debugProcess, environment);
  164. ExecutionResult executionResult = session.getProcess().getExecutionResult();
  165. if (executionResult == null) {
  166. return null;
  167. }
  168. session.getContextManager().addListener(mySessionListener);
  169. getContextManager()
  170. .setState(DebuggerContextUtil.createDebuggerContext(session, session.getContextManager().getContext().getSuspendContext()),
  171. session.getState(), DebuggerSession.Event.CONTEXT, null);
  172. final ProcessHandler processHandler = executionResult.getProcessHandler();
  173. synchronized (mySessions) {
  174. mySessions.put(processHandler, session);
  175. }
  176. if (!(processHandler instanceof RemoteDebugProcessHandler)) {
  177. // add listener only to non-remote process handler:
  178. // on Unix systems destroying process does not cause VMDeathEvent to be generated,
  179. // so we need to call debugProcess.stop() explicitly for graceful termination.
  180. // RemoteProcessHandler on the other hand will call debugProcess.stop() as a part of destroyProcess() and detachProcess() implementation,
  181. // so we shouldn't add the listener to avoid calling stop() twice
  182. processHandler.addProcessListener(new ProcessAdapter() {
  183. @Override
  184. public void processWillTerminate(@NotNull ProcessEvent event, boolean willBeDestroyed) {
  185. ProcessHandler processHandler = event.getProcessHandler();
  186. final DebugProcessImpl debugProcess = getDebugProcess(processHandler);
  187. if (debugProcess != null) {
  188. // if current thread is a "debugger manager thread", stop will execute synchronously
  189. // it is KillableColoredProcessHandler responsibility to terminate VM
  190. debugProcess.stop(willBeDestroyed && !(processHandler instanceof KillableColoredProcessHandler && ((KillableColoredProcessHandler)processHandler).shouldKillProcessSoftly()));
  191. // wait at most 10 seconds: the problem is that debugProcess.stop() can hang if there are troubles in the debuggee
  192. // if processWillTerminate() is called from AWT thread debugProcess.waitFor() will block it and the whole app will hang
  193. if (!DebuggerManagerThreadImpl.isManagerThread()) {
  194. if (SwingUtilities.isEventDispatchThread()) {
  195. ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
  196. ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
  197. indicator.setIndeterminate(false);
  198. int wait = 0;
  199. while (wait < WAIT_KILL_TIMEOUT && !indicator.isCanceled()) {
  200. indicator.setFraction((double)wait/WAIT_KILL_TIMEOUT);
  201. debugProcess.waitFor(200);
  202. wait += 200;
  203. }
  204. }, JavaDebuggerBundle.message("waiting.for.debugger.response"), true, debugProcess.getProject());
  205. }
  206. else {
  207. debugProcess.waitFor(WAIT_KILL_TIMEOUT);
  208. }
  209. }
  210. }
  211. }
  212. });
  213. }
  214. getEventPublisher().sessionCreated(session);
  215. if (debugProcess.isDetached() || debugProcess.isDetaching()) {
  216. session.dispose();
  217. return null;
  218. }
  219. if (environment.isRemote()) {
  220. // optimization: that way BatchEvaluator will not try to lookup the class file in remote VM
  221. // which is an expensive operation when executed first time
  222. debugProcess.putUserData(BatchEvaluator.REMOTE_SESSION_KEY, Boolean.TRUE);
  223. }
  224. return session;
  225. }
  226. @Override
  227. public DebugProcessImpl getDebugProcess(final ProcessHandler processHandler) {
  228. synchronized (mySessions) {
  229. DebuggerSession session = mySessions.get(processHandler);
  230. return session != null ? session.getProcess() : null;
  231. }
  232. }
  233. @SuppressWarnings("UnusedDeclaration")
  234. @Nullable
  235. public DebuggerSession getDebugSession(final ProcessHandler processHandler) {
  236. synchronized (mySessions) {
  237. return mySessions.get(processHandler);
  238. }
  239. }
  240. @Override
  241. public void addDebugProcessListener(final ProcessHandler processHandler, final DebugProcessListener listener) {
  242. DebugProcessImpl debugProcess = getDebugProcess(processHandler);
  243. if (debugProcess != null) {
  244. debugProcess.addDebugProcessListener(listener);
  245. }
  246. else {
  247. processHandler.addProcessListener(new ProcessAdapter() {
  248. @Override
  249. public void startNotified(@NotNull ProcessEvent event) {
  250. DebugProcessImpl debugProcess = getDebugProcess(processHandler);
  251. if (debugProcess != null) {
  252. debugProcess.addDebugProcessListener(listener);
  253. }
  254. processHandler.removeProcessListener(this);
  255. }
  256. });
  257. }
  258. }
  259. @Override
  260. public void removeDebugProcessListener(final ProcessHandler processHandler, final DebugProcessListener listener) {
  261. DebugProcessImpl debugProcess = getDebugProcess(processHandler);
  262. if (debugProcess != null) {
  263. debugProcess.removeDebugProcessListener(listener);
  264. }
  265. else {
  266. processHandler.addProcessListener(new ProcessAdapter() {
  267. @Override
  268. public void startNotified(@NotNull ProcessEvent event) {
  269. DebugProcessImpl debugProcess = getDebugProcess(processHandler);
  270. if (debugProcess != null) {
  271. debugProcess.removeDebugProcessListener(listener);
  272. }
  273. processHandler.removeProcessListener(this);
  274. }
  275. });
  276. }
  277. }
  278. @Override
  279. public boolean isDebuggerManagerThread() {
  280. return DebuggerManagerThreadImpl.isManagerThread();
  281. }
  282. @NotNull
  283. @Override
  284. public BreakpointManager getBreakpointManager() {
  285. return myBreakpointManager;
  286. }
  287. @NotNull
  288. @Override
  289. public DebuggerContextImpl getContext() {
  290. return getContextManager().getContext();
  291. }
  292. @NotNull
  293. @Override
  294. public DebuggerStateManager getContextManager() {
  295. return myDebuggerStateManager;
  296. }
  297. /**
  298. * @deprecated use {@link RemoteConnectionBuilder}
  299. */
  300. @Deprecated
  301. public static RemoteConnection createDebugParameters(final JavaParameters parameters,
  302. final boolean debuggerInServerMode,
  303. int transport, final String debugPort,
  304. boolean checkValidity) throws ExecutionException {
  305. return new RemoteConnectionBuilder(debuggerInServerMode, transport, debugPort)
  306. .checkValidity(checkValidity)
  307. .asyncAgent(true)
  308. .create(parameters);
  309. }
  310. /**
  311. * @deprecated use {@link RemoteConnectionBuilder}
  312. */
  313. @Deprecated
  314. public static RemoteConnection createDebugParameters(final JavaParameters parameters,
  315. GenericDebuggerRunnerSettings settings,
  316. boolean checkValidity)
  317. throws ExecutionException {
  318. return new RemoteConnectionBuilder(settings.LOCAL, settings.getTransport(), settings.getDebugPort())
  319. .checkValidity(checkValidity)
  320. .asyncAgent(true)
  321. .create(parameters);
  322. }
  323. private static class MyDebuggerStateManager extends DebuggerStateManager {
  324. private DebuggerSession myDebuggerSession;
  325. @NotNull
  326. @Override
  327. public DebuggerContextImpl getContext() {
  328. return myDebuggerSession == null ? DebuggerContextImpl.EMPTY_CONTEXT : myDebuggerSession.getContextManager().getContext();
  329. }
  330. @Override
  331. public void setState(@NotNull final DebuggerContextImpl context, DebuggerSession.State state, DebuggerSession.Event event, String description) {
  332. ApplicationManager.getApplication().assertIsDispatchThread();
  333. myDebuggerSession = context.getDebuggerSession();
  334. if (myDebuggerSession != null) {
  335. myDebuggerSession.getContextManager().setState(context, state, event, description);
  336. }
  337. else {
  338. fireStateChanged(context, event);
  339. }
  340. }
  341. }
  342. private void dispose(DebuggerSession session) {
  343. ProcessHandler processHandler = session.getProcess().getProcessHandler();
  344. synchronized (mySessions) {
  345. DebuggerSession removed = mySessions.remove(processHandler);
  346. LOG.assertTrue(removed != null);
  347. getEventPublisher().sessionRemoved(session);
  348. }
  349. }
  350. public static class DebuggerRunContentWithExecutorListener implements RunContentWithExecutorListener {
  351. private final Project myProject;
  352. public DebuggerRunContentWithExecutorListener(Project project) {
  353. myProject = project;
  354. }
  355. @Override
  356. public void contentSelected(@Nullable RunContentDescriptor descriptor, @NotNull Executor executor) {
  357. if (executor == DefaultDebugExecutor.getDebugExecutorInstance()) {
  358. DebuggerSession session = descriptor == null ? null : getSession(descriptor);
  359. DebuggerStateManager manager = getInstanceEx(myProject).getContextManager();
  360. if (session != null) {
  361. manager.setState(session.getContextManager().getContext(), session.getState(), DebuggerSession.Event.CONTEXT, null);
  362. }
  363. else {
  364. manager.setState(DebuggerContextImpl.EMPTY_CONTEXT, DebuggerSession.State.DISPOSED, DebuggerSession.Event.CONTEXT, null);
  365. }
  366. }
  367. }
  368. private DebuggerSession getSession(RunContentDescriptor descriptor) {
  369. for (JavaDebugProcess process : XDebuggerManager.getInstance(myProject).getDebugProcesses(JavaDebugProcess.class)) {
  370. if (Comparing.equal(process.getProcessHandler(), descriptor.getProcessHandler())) {
  371. return process.getDebuggerSession();
  372. }
  373. }
  374. return null;
  375. }
  376. }
  377. }