diff --git a/platform/platform-impl/platform-impl.iml b/platform/platform-impl/platform-impl.iml index ff57cb26abd2..ee9bf613b86a 100644 --- a/platform/platform-impl/platform-impl.iml +++ b/platform/platform-impl/platform-impl.iml @@ -29,7 +29,7 @@ - + diff --git a/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java b/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java index 911c1e0509a4..2926b4e2c683 100644 --- a/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java +++ b/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java @@ -33,8 +33,10 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import io.netty.handler.codec.http.cors.CorsConfig; import io.netty.handler.codec.http.cors.CorsHandler; import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.util.concurrent.GlobalEventExecutor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import org.jetbrains.concurrency.AsyncPromise; import org.jetbrains.concurrency.Promise; import org.jetbrains.ide.PooledThreadExecutor; @@ -45,6 +47,7 @@ import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Random; +import java.util.concurrent.TimeUnit; public final class NettyUtil { public static final int MAX_CONTENT_LENGTH = 100 * 1024 * 1024; @@ -226,4 +229,18 @@ public final class NettyUtil { context.fireExceptionCaught(cause); } } + + @TestOnly + public static void awaitQuiescenceOfGlobalEventExecutor(long timeout, @NotNull TimeUnit unit) { + try { + @NotNull GlobalEventExecutor executor = GlobalEventExecutor.INSTANCE; + executor.awaitInactivity(timeout, unit); + } + catch (InterruptedException ignored) { + + } + catch (IllegalStateException ignored) { + // thread did not start + } + } } diff --git a/platform/testFramework/src/com/intellij/testFramework/ThreadTracker.java b/platform/testFramework/src/com/intellij/testFramework/ThreadTracker.java index 1a44743d6611..284367106e76 100644 --- a/platform/testFramework/src/com/intellij/testFramework/ThreadTracker.java +++ b/platform/testFramework/src/com/intellij/testFramework/ThreadTracker.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,24 @@ */ package com.intellij.testFramework; +import com.intellij.execution.process.BaseOSProcessHandler; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.impl.ProjectManagerImpl; +import com.intellij.openapi.util.Condition; +import com.intellij.util.ReflectionUtil; +import com.intellij.util.TimeoutUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.ContainerUtilRt; import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; +import org.jetbrains.io.NettyUtil; import org.junit.Assert; +import java.lang.reflect.Method; import java.util.Collection; -import java.util.Collections; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * @author cdr @@ -31,50 +41,69 @@ public class ThreadTracker { private final Collection before; private final boolean myDefaultProjectInitialized; + @TestOnly public ThreadTracker() { before = getThreads(); myDefaultProjectInitialized = ((ProjectManagerImpl)ProjectManager.getInstance()).isDefaultProjectInitialized(); } + private static final Method getThreads = ReflectionUtil.getDeclaredMethod(Thread.class, "getThreads"); + + @NotNull private static Collection getThreads() { - //todo enable - return Collections.emptyList(); //Thread.getAllStackTraces().keySet(); + Thread[] threads; + try { + // faster than Thread.getAllStackTraces().keySet() + threads = (Thread[])getThreads.invoke(null); + } + catch (Exception e) { + throw new RuntimeException(e); + } + return ContainerUtilRt.newArrayList(threads); } - private static final Set wellKnownOffenders = new THashSet(){{ - add("Alarm pool(own)"); - add("Alarm pool(shared)"); - add("ApplicationImpl pooled thread"); - add("AWT-Shutdown"); - add("AWT-Windows"); - add("CompilerThread0"); - add("Finalizer"); - add("FS Synchronizer"); - add("IDEA Test Case Thread"); - add("Image Fetcher 0"); - add("Java2D Disposer"); - add("Low Memory Detector"); - add("main"); - add("Monitor Ctrl-Break"); - add("Periodic tasks thread"); - add("Reference Handler"); - add("Signal Dispatcher"); - add("SimpleTimer"); - add("timed reference disposer"); - add("timer-int"); //serverImpl - add("timer-sys"); //clientimpl - add("TimerQueue"); - add("UserActivityMonitor thread"); - add("VM Periodic Task Thread"); - add("VM Thread"); - add("YJPAgent-Telemetry"); + private static final Set wellKnownOffenders = new THashSet(); + static { + wellKnownOffenders.add("Action Updater"); // todo remove + wellKnownOffenders.add("Alarm pool(own)"); + wellKnownOffenders.add("Alarm pool(shared)"); + wellKnownOffenders.add("ApplicationImpl pooled thread"); + wellKnownOffenders.add("AWT-EventQueue-"); + wellKnownOffenders.add("AWT-Shutdown"); + wellKnownOffenders.add("AWT-Windows"); + wellKnownOffenders.add("Change List Updater"); //todo remove + wellKnownOffenders.add("CompilerThread0"); + wellKnownOffenders.add("Document commit thread"); + wellKnownOffenders.add("Finalizer"); + wellKnownOffenders.add("FS Synchronizer"); //todo remove + wellKnownOffenders.add("IDEA Test Case Thread"); + wellKnownOffenders.add("Image Fetcher "); + wellKnownOffenders.add("Java2D Disposer"); + wellKnownOffenders.add("JobScheduler FJ pool "); + wellKnownOffenders.add("JPS thread pool"); + wellKnownOffenders.add("Keep-Alive-Timer"); + wellKnownOffenders.add("Low Memory Detector"); + wellKnownOffenders.add("main"); + wellKnownOffenders.add("Monitor Ctrl-Break"); + wellKnownOffenders.add("Periodic tasks thread"); + wellKnownOffenders.add("Reference Handler"); + wellKnownOffenders.add("RMI TCP Connection"); + wellKnownOffenders.add("Signal Dispatcher"); + wellKnownOffenders.add("Netty "); + wellKnownOffenders.add("timed reference disposer"); + wellKnownOffenders.add("timer-int"); //serverImpl + wellKnownOffenders.add("timer-sys"); //clientimpl + wellKnownOffenders.add("TimerQueue"); + wellKnownOffenders.add("UserActivityMonitor thread"); + wellKnownOffenders.add("VM Periodic Task Thread"); + wellKnownOffenders.add("VM Thread"); + wellKnownOffenders.add("YJPAgent-Telemetry"); + } - - - - add("Change List Updater"); - }}; + @TestOnly public void checkLeak() throws AssertionError { + BaseOSProcessHandler.awaitQuiescence(100, TimeUnit.SECONDS); + NettyUtil.awaitQuiescenceOfGlobalEventExecutor(100, TimeUnit.SECONDS); try { if (myDefaultProjectInitialized != ((ProjectManagerImpl)ProjectManager.getInstance()).isDefaultProjectInitialized()) return; @@ -85,15 +114,27 @@ public class ThreadTracker { if (thread == Thread.currentThread()) continue; ThreadGroup group = thread.getThreadGroup(); if (group != null && "system".equals(group.getName()))continue; - String name = thread.getName(); - if (name.startsWith("AWT-EventQueue-0")) continue; - if (name.startsWith("JobScheduler pool ")) continue; - if (wellKnownOffenders.contains(name)) continue; + final String name = thread.getName(); + if (ContainerUtil.exists(wellKnownOffenders, new Condition() { + @Override + public boolean value(String pattern) { + return name.contains(pattern); + } + })) { + continue; + } - String trace = "Thread leaked: " + thread+": "+ name +";\n "; + if (!thread.isAlive()) continue; + if (thread.getStackTrace().length == 0) { + TimeoutUtil.sleep(10000); + if (!thread.isAlive()) continue; + } + + String trace = "Thread leaked: " + thread+"; " + thread.getState()+" ("+ thread.isAlive()+")\n--- its stacktrace:\n"; for (final StackTraceElement stackTraceElement : thread.getStackTrace()) { trace += " at "+stackTraceElement +"\n"; } + trace += "---\n"; Assert.fail(trace); } }