Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: c7965e9c9d57f33ce47830aa0ee4b8e544aa5344 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
/*******************************************************************************
 * Copyright (c) 2006, 2011 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 && "true".equals( //$NON-NLS-1$
            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 = " + 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();
    }
}

Back to the top