void (@NotNull ThreadPoolExecutor executor, long timeout, @NotNull TimeUnit unit) { executor.setKeepAliveTime(1, TimeUnit.NANOSECONDS); // no need for zombies in tests executor.setCorePoolSize(0); // interrupt idle workers ReentrantLock mainLock = ReflectionUtil.getField(executor.getClass(), executor, ReentrantLock.class, "mainLock"); Set<Object> workers; mainLock.lock(); try { @SuppressWarnings("unchecked") Set<Object> workersField = ReflectionUtil.getField(executor.getClass(), executor, HashSet.class, "workers"); // to be able to iterate thread-safely outside the lock workers = new HashSet<>(workersField); } finally { mainLock.unlock(); } for (Object worker : workers) { Thread thread = ReflectionUtil.getField(worker.getClass(), worker, Thread.class, "thread"); try { thread.join(unit.toMillis(timeout)); } catch (InterruptedException e) { @NonNls 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"; @NonNls String message = "Executor " + executor + " is still active after " + unit.toSeconds(timeout) + " seconds://///\n" + "Thread " + thread + " dump:\n" + trace + "all thread dump:\n" + ThreadDumper.dumpThreadsToString() + "\n/////"; System.err.println(message); break; } } }