/******************************************************************************* * Copyright (c) 2006, 2016 Wind River Systems 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.dsf.concurrent; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.eclipse.cdt.dsf.internal.DsfPlugin; import org.eclipse.cdt.dsf.internal.LoggingUtils; import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; /** * Default implementation of a DSF executor interfaces, based on the * standard java.util.concurrent.ThreadPoolExecutor. * * @since 1.0 */ public class DefaultDsfExecutor extends ScheduledThreadPoolExecutor implements DsfExecutor { /** * Instance counter for DSF executors. Used in the executor's thread name. */ private static int fgInstanceCounter = 0; /** * Name of the executor, used in the executor's thread name. */ private String fName; /** Thread factory that creates the single thread to be used for this executor */ static class DsfThreadFactory implements ThreadFactory { private String fThreadName; DsfThreadFactory(String name) { fThreadName = name; } Thread fThread; @Override public Thread newThread(Runnable r) { assert fThread == null; // Should be called only once. fThread = new Thread(new ThreadGroup(fThreadName), r, fThreadName, 0); return fThread; } } public DefaultDsfExecutor() { this("DSF Executor"); //$NON-NLS-1$ } /** * Creates a new DSF Executor with the given name. * @param name Name used to create executor's thread. */ public DefaultDsfExecutor(String name) { super(1, new DsfThreadFactory(name + " - " + fgInstanceCounter++)); //$NON-NLS-1$ fName = name; if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { // If tracing, pre-start the dispatch thread, and add it to the map. prestartAllCoreThreads(); fThreadToExecutorMap.put(((DsfThreadFactory)getThreadFactory()).fThread, DefaultDsfExecutor.this); } } @Override public boolean isInExecutorThread() { return Thread.currentThread().equals( ((DsfThreadFactory)getThreadFactory()).fThread ); } /** * @since 2.1 */ public int getCurrentExecutionDepth() { if (fCurrentlyExecuting != null) { return fCurrentlyExecuting.fDepth; } return -1; } protected String getName() { return fName; } static void logException(Throwable t) { DsfPlugin plugin = DsfPlugin.getDefault(); if (plugin == null) return; ILog log = plugin.getLog(); if (log != null) { log.log(new Status( IStatus.ERROR, DsfPlugin.PLUGIN_ID, -1, "Uncaught exception in DSF executor thread", t)); //$NON-NLS-1$ } // Print out the stack trace to console if assertions are enabled. if(ASSERTIONS_ENABLED) { ByteArrayOutputStream outStream = new ByteArrayOutputStream(512); PrintStream printStream = new PrintStream(outStream); try { printStream.write("Uncaught exception in session executor thread: ".getBytes()); //$NON-NLS-1$ } catch (IOException e2) {} t.printStackTrace(new PrintStream(outStream)); System.err.println(outStream.toString()); } } // // Utilities used for tracing. // protected static boolean DEBUG_EXECUTOR = false; protected static String DEBUG_EXECUTOR_NAME = ""; //$NON-NLS-1$ protected static boolean ASSERTIONS_ENABLED = false; static { DEBUG_EXECUTOR = DsfPlugin.DEBUG && Boolean.parseBoolean( Platform.getDebugOption("org.eclipse.cdt.dsf/debug/executor")); //$NON-NLS-1$ DEBUG_EXECUTOR_NAME = DsfPlugin.DEBUG ? Platform.getDebugOption("org.eclipse.cdt.dsf/debug/executorName") : ""; //$NON-NLS-1$ //$NON-NLS-2$ assert (ASSERTIONS_ENABLED = true) == true; } /** * This map is used by DsfRunnable/Query/DsfCallable to track by which executor * an executable object was created. * <br>Note: Only used when tracing. */ static Map<Thread, DefaultDsfExecutor> fThreadToExecutorMap = new HashMap<Thread, DefaultDsfExecutor>(); /** * Currently executing runnable/callable. * <br>Note: Only used when tracing. */ TracingWrapper fCurrentlyExecuting; /** * Counter number saved by each tracing runnable when executed * <br>Note: Only used when tracing. */ int fSequenceCounter; /** * Wrapper for runnables/callables, is used to store tracing information * <br>Note: Only used when tracing. */ abstract class TracingWrapper { /** Sequence number of this runnable/callable */ int fSequenceNumber = -1; int fDepth = 0; /** Trace of where the runnable/callable was submitted to the executor */ StackTraceWrapper fSubmittedAt = null; /** Reference to the runnable/callable that submitted this runnable/callable to the executor */ TracingWrapper fSubmittedBy = null; /** * The names of the executor submitter methods we support, ordered by * popularity so as to optimize the tracing logic. (For the curious, * 'execute' is by far the most commonly called--ten times more often * than 'submit', in fact). */ private final String[] SUBMITTER_METHOD_NAMES = { "execute", "submit", "schedule", "scheduleAtFixedRate", "scheduleWithFixedDelay" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ /** */ TracingWrapper() { // Get the this thread's stack trace and then search for the call // into the executor's submitter method. We'll want to ignore // everything up to and including that call. StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); int frameIgnoreCount = 1; String executorClassName = this.getClass().getEnclosingClass().getSimpleName(); // e.g., "DefaultDsfExecutor" outer: for (StackTraceElement frame : stackTrace) { final String framestr = frame.toString(); for (String methodName : SUBMITTER_METHOD_NAMES) { if (framestr.contains(executorClassName + "." + methodName + "(")) { //$NON-NLS-1$ //$NON-NLS-2$ break outer; // exit both loops } } frameIgnoreCount++; } if (frameIgnoreCount == stackTrace.length) { // Internal error, really. We were unable to identify the // executor's submission function. Our check above must be // overlooking a possibility frameIgnoreCount = 0; } // guard against the offset being greater than the stack trace frameIgnoreCount = Math.min(frameIgnoreCount, stackTrace.length); fSubmittedAt = new StackTraceWrapper(new StackTraceElement[stackTrace.length - frameIgnoreCount]); if (fSubmittedAt.fStackTraceElements.length > 0) { System.arraycopy(stackTrace, frameIgnoreCount, fSubmittedAt.fStackTraceElements, 0, fSubmittedAt.fStackTraceElements.length); } if (isInExecutorThread() && fCurrentlyExecuting != null) { fSubmittedBy = fCurrentlyExecuting; } } void traceExecution() { fSequenceNumber = fSequenceCounter++; fDepth = fSubmittedBy == null ? 0 : fSubmittedBy.fDepth + 1; fCurrentlyExecuting = this; // Write to console only if tracing is enabled (as opposed to tracing or assertions). if (DEBUG_EXECUTOR && ("".equals(DEBUG_EXECUTOR_NAME) || fName.equals(DEBUG_EXECUTOR_NAME))) { //$NON-NLS-1$ StringBuilder traceBuilder = new StringBuilder(); // Record the time traceBuilder.append(DsfPlugin.getDebugTime()); traceBuilder.append(' '); // Record the executor # traceBuilder.append("DSF execution #"); //$NON-NLS-1$ traceBuilder.append(fSequenceNumber); // Record the executor name traceBuilder.append(". Executor is ("); //$NON-NLS-1$ traceBuilder.append(((DsfThreadFactory)getThreadFactory()).fThreadName); traceBuilder.append(')'); // This will be a Runnable or a Callable. Hopefully it will also // be a DsfExecutable and thus be instrumented with trace/debug // information. In nearly every case, it will be an anonymous // inner class. final Object executable = getExecutable(); // Append executable class name. The anonymous inner class name // name won't be very interesting; use the parent class instead. traceBuilder.append("\n\tExecutable detail: \n\t\ttype = "); //$NON-NLS-1$ Class<? extends Object> execClass = executable.getClass(); traceBuilder.append(execClass.isAnonymousClass() ? execClass.getSuperclass().getName() : execClass.getName()); // Append the executable reference final String refstr = LoggingUtils.toString(executable, false); String tostr = LoggingUtils.trimTrailingNewlines(executable.toString()); traceBuilder.append("\n\t\t"); //$NON-NLS-1$ traceBuilder.append("instance = ").append(refstr); //$NON-NLS-1$ if (!tostr.equals(refstr)) { traceBuilder.append(" ["); //$NON-NLS-1$ traceBuilder.append(tostr); traceBuilder.append(']'); } // Determine if the created-at and submitted-at information is // the same. If so, consolidate. StackTraceElement[] createdAtStack = null; StackTraceElement[] submittedAtStack = (fSubmittedAt == null) ? null : fSubmittedAt.fStackTraceElements; int createdBySeqNum = Integer.MIN_VALUE; int submittedBySeqNum = (fSubmittedBy == null) ? Integer.MIN_VALUE : fSubmittedBy.fSequenceNumber; if (executable instanceof DsfExecutable) { DsfExecutable dsfExecutable = (DsfExecutable)executable; createdAtStack = (dsfExecutable.fCreatedAt == null) ? null : dsfExecutable.fCreatedAt.fStackTraceElements; createdBySeqNum = (dsfExecutable.fCreatedBy == null) ? Integer.MIN_VALUE : dsfExecutable.fCreatedBy.fSequenceNumber; } boolean canConsolidate = false; if ((createdBySeqNum == submittedBySeqNum) && (createdAtStack != null) && (submittedAtStack != null)) { if ((createdAtStack.length == submittedAtStack.length) || (createdAtStack.length >=3 && submittedAtStack.length >= 3)) { canConsolidate = true; int count = Math.min(createdAtStack.length, 3); for (int i = 0; i < count; i++) { if (createdAtStack[i].toString().compareTo(submittedAtStack[i].toString()) != 0) { canConsolidate = false; break; } } } } if (canConsolidate) { traceBuilder.append("\n\t\tcreated and submitted"); //$NON-NLS-1$ if (createdBySeqNum != Integer.MIN_VALUE) { traceBuilder.append(" by #"); //$NON-NLS-1$ traceBuilder.append(createdBySeqNum); } if (createdAtStack != null) { traceBuilder.append(" at:"); //$NON-NLS-1$ for (int i = 0; i < createdAtStack.length && i < 3; i++) { traceBuilder.append("\n\t\t\t"); //$NON-NLS-1$ traceBuilder.append(createdAtStack[i].toString()); } } } else { // Append "create by" info. if (createdAtStack != null || createdBySeqNum != Integer.MIN_VALUE) { traceBuilder.append("\n\t\tcreated "); //$NON-NLS-1$ if (createdBySeqNum != Integer.MIN_VALUE) { traceBuilder.append(" by #"); //$NON-NLS-1$ traceBuilder.append(createdBySeqNum); } if (createdAtStack != null) { traceBuilder.append(" at:"); //$NON-NLS-1$ for (int i = 0; i < createdAtStack.length && i < 3; i++) { traceBuilder.append("\n\t\t\t"); //$NON-NLS-1$ traceBuilder.append(createdAtStack[i].toString()); } } } // Submitted info traceBuilder.append("\n\t\tsubmitted"); //$NON-NLS-1$ if (fSubmittedBy != null) { traceBuilder.append(" by #"); //$NON-NLS-1$ traceBuilder.append(fSubmittedBy.fSequenceNumber); } traceBuilder.append(" at:"); //$NON-NLS-1$ for (int i = 0; i < fSubmittedAt.fStackTraceElements.length && i < 3; i++) { traceBuilder.append("\n\t\t\t"); //$NON-NLS-1$ traceBuilder.append(fSubmittedAt.fStackTraceElements[i].toString()); } } // Finally write out to console DsfPlugin.debug(traceBuilder.toString()); } } abstract protected Object getExecutable(); } class TracingWrapperRunnable extends TracingWrapper implements Runnable { final Runnable fRunnable; public TracingWrapperRunnable(Runnable runnable) { if (runnable == null) throw new NullPointerException(); fRunnable = runnable; // Check if executable wasn't executed already. if (DEBUG_EXECUTOR && fRunnable instanceof DsfExecutable) { assert !((DsfExecutable)fRunnable).getSubmitted() : "Executable was previously executed."; //$NON-NLS-1$ ((DsfExecutable)fRunnable).setSubmitted(); } } @Override protected Object getExecutable() { return fRunnable; } @Override public void run() { traceExecution(); // Finally invoke the runnable code. try { fRunnable.run(); } catch (RuntimeException e) { // If an exception was thrown in the Runnable, trace it. // Because there is no one else to catch it, it is a // programming error. logException(e); throw e; } catch (Error e) { logException(e); throw e; } } } public class TracingWrapperCallable<T> extends TracingWrapper implements Callable<T> { final Callable<T> fCallable; /** * @deprecated use constructor that takes just the Callable parameter */ @Deprecated public TracingWrapperCallable(Callable<T> callable, int frameIgnoreCount) { if (callable == null) throw new NullPointerException(); fCallable = callable; } /** * @since 2.1 */ public TracingWrapperCallable(Callable<T> callable) { if (callable == null) throw new NullPointerException(); fCallable = callable; } @Override protected Object getExecutable() { return fCallable; } @Override public T call() throws Exception { traceExecution(); // Finally invoke the runnable code. // Note that callables can throw exceptions that can be caught // by clients that invoked them using ExecutionException. return fCallable.call(); } } @Override public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { if ( !(callable instanceof TracingWrapper) ) { callable = new TracingWrapperCallable<V>(callable); } } return super.schedule(callable, delay, unit); } @Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { if ( !(command instanceof TracingWrapper) ) { command = new TracingWrapperRunnable(command); } } return super.schedule(command, delay, unit); } @Override public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { command = new TracingWrapperRunnable(command); } return super.scheduleAtFixedRate(command, initialDelay, period, unit); } @Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { command = new TracingWrapperRunnable(command); } return super.scheduleWithFixedDelay(command, initialDelay, delay, unit); } @Override public void execute(Runnable command) { if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { command = new TracingWrapperRunnable(command); } super.execute(command); } @Override public Future<?> submit(Runnable command) { if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { command = new TracingWrapperRunnable(command); } return super.submit(command); } @Override public <T> Future<T> submit(Callable<T> callable) { if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { callable = new TracingWrapperCallable<T>(callable); } return super.submit(callable); } @Override public <T> Future<T> submit(Runnable command, T result) { if(DEBUG_EXECUTOR || ASSERTIONS_ENABLED) { command = new TracingWrapperRunnable(command); } return super.submit(command, result); } @Override public void shutdown() { if (DEBUG_EXECUTOR && ("".equals(DEBUG_EXECUTOR_NAME) || fName.equals(DEBUG_EXECUTOR_NAME))) { //$NON-NLS-1$ DsfPlugin.debug(DsfPlugin.getDebugTime() + " Executor (" + ((DsfThreadFactory)getThreadFactory()).fThreadName + ") is being shut down. Already submitted tasks will be executed, new ones will not."); //$NON-NLS-1$ //$NON-NLS-2$ } super.shutdown(); } @Override public List<Runnable> shutdownNow() { if (DEBUG_EXECUTOR && ("".equals(DEBUG_EXECUTOR_NAME) || fName.equals(DEBUG_EXECUTOR_NAME))) { //$NON-NLS-1$ DsfPlugin.debug(DsfPlugin.getDebugTime() + " Executor (" + ((DsfThreadFactory)getThreadFactory()).fThreadName + ") is being shut down. No queued or new tasks will be executed, and will attempt to cancel active ones."); //$NON-NLS-1$ //$NON-NLS-2$ } return super.shutdownNow(); } @Override protected void terminated() { fThreadToExecutorMap.remove(((DsfThreadFactory)getThreadFactory()).fThread); super.terminated(); } }