/******************************************************************************* | |
* Copyright (c) 2009, 2010 Nokia 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: | |
* Nokia - Initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.cdt.debug.edc.debugger.tests; | |
import java.util.concurrent.RejectedExecutionException; | |
import java.util.concurrent.atomic.AtomicBoolean; | |
import java.util.concurrent.atomic.AtomicInteger; | |
import junit.framework.Assert; | |
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; | |
import org.eclipse.cdt.debug.edc.internal.services.dsf.INoop; | |
import org.eclipse.cdt.debug.edc.internal.services.dsf.RunControl.ExecutionDMC; | |
import org.eclipse.cdt.debug.edc.launch.EDCLaunch; | |
import org.eclipse.cdt.debug.edc.services.Stack; | |
import org.eclipse.cdt.debug.edc.services.Stack.StackFrameDMC; | |
import org.eclipse.cdt.debug.edc.tests.TestUtils; | |
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; | |
import org.eclipse.cdt.dsf.concurrent.RequestMonitor; | |
import org.eclipse.cdt.dsf.debug.service.IStack; | |
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; | |
import org.eclipse.cdt.dsf.service.DsfSession; | |
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; | |
import org.junit.After; | |
import org.junit.Test; | |
public class Concurrent extends BaseLaunchTest { | |
private EDCLaunch launch; | |
private DsfSession session; | |
private boolean testDidShutdown; | |
/** | |
* This test validates that the EDC service threads are given a chance to | |
* complete before the DSF session proceeds with its shutdown. Otherwise, | |
* the logic running on those threads would likely encounter all sorts of | |
* problems as it tries to operate within a session that has been shut down. | |
*/ | |
@Test | |
public void testEDCThreadAtShutdown_1() throws Throwable { | |
TestUtils.showDebugPerspective(); | |
launch = createLaunch(); | |
assertNotNull(launch); | |
session = TestUtils.waitForSession(launch); | |
assertNotNull(session); | |
final ExecutionDMC threadDMC = TestUtils.waitForSuspendedThread(session); | |
assertNotNull(threadDMC); | |
TestUtils.waitForStackFrame(session, threadDMC); | |
final INoop service = TestUtils.getService(session, INoop.class); | |
final String semaphore = new String(); | |
final AtomicBoolean successful = new AtomicBoolean(false); | |
final Throwable[] exception = new Throwable[1]; | |
Runnable runnable = new Runnable() { | |
public void run() { | |
try { | |
RequestMonitor rm = new RequestMonitor(service.getExecutor(), null); | |
service.longNoopUsingServiceTracker(3, rm); | |
successful.set(true); | |
synchronized (semaphore) { | |
semaphore.notify(); | |
} | |
} | |
catch (Throwable exc) { | |
synchronized (exception) { | |
exception[0] = exc; | |
} | |
synchronized (semaphore) { | |
semaphore.notify(); | |
} | |
} | |
} | |
}; | |
// Kick off some work on an EDC service thread. Its logic will encounter | |
// an exception if the DSF session shuts down while they're running | |
EDCLaunch.getThreadPool(service.getSession().getId()).execute(runnable); | |
// Tell the DSF session to shut down | |
TestUtils.shutdownDebugSession(launch, session); | |
// Avoid a redundant shutdown by the @After method | |
testDidShutdown = true; | |
// Wait until the EDC service thread either completes or encounters an | |
// exception | |
synchronized (semaphore) { | |
semaphore.wait(8 * 1000); | |
} | |
// If it ran into an exception, fail the test | |
synchronized (exception) { | |
if (exception[0] != null) { | |
throw exception[0]; | |
} | |
} | |
// A sanity check that the thread completed successfully | |
assertTrue(successful.get()); | |
} | |
/** | |
* This test validates that the EDC service threads are given a chance to | |
* complete before the DSF session proceeds with its shutdown. Note the request | |
* monitor should be completed properly. | |
*/ | |
@Test | |
public void testEDCThreadAtShutdown_2() throws Throwable { | |
TestUtils.showDebugPerspective(); | |
launch = createLaunch(); | |
assertNotNull(launch); | |
session = TestUtils.waitForSession(launch); | |
assertNotNull(session); | |
final ExecutionDMC threadDMC = TestUtils.waitForSuspendedThread(session); | |
assertNotNull(threadDMC); | |
TestUtils.waitForStackFrame(session, threadDMC); | |
final INoop service = TestUtils.getService(session, INoop.class); | |
final String semaphore = new String(); | |
final AtomicBoolean successful = new AtomicBoolean(false); | |
final Throwable[] exception = new Throwable[1]; | |
Runnable runnable = new Runnable() { | |
public void run() { | |
try { | |
DataRequestMonitor<Boolean> drm = new DataRequestMonitor<Boolean>(service.getExecutor(), null) { | |
@Override | |
protected void handleCompleted() { | |
successful.set(getData()); | |
synchronized (semaphore) { | |
semaphore.notify(); | |
} | |
}}; | |
service.longNoop(3, drm); | |
} | |
catch (Throwable exc) { | |
synchronized (exception) { | |
exception[0] = exc; | |
} | |
synchronized (semaphore) { | |
semaphore.notify(); | |
} | |
} | |
} | |
}; | |
// Kick off some work on an EDC service thread. Its logic will encounter | |
// an exception if the DSF session shuts down while they're running | |
EDCLaunch.getThreadPool(service.getSession().getId()).execute(runnable); | |
// Tell the DSF session to shut down | |
TestUtils.shutdownDebugSession(launch, session); | |
// Avoid a redundant shutdown by the @After method | |
testDidShutdown = true; | |
// Wait until the EDC service thread either completes or encounters an | |
// exception | |
synchronized (semaphore) { | |
semaphore.wait(4 * 1000); | |
} | |
// If it ran into an exception, fail the test | |
synchronized (exception) { | |
if (exception[0] != null) { | |
throw exception[0]; | |
} | |
} | |
// A sanity check that the thread and request monitor completed successfully | |
assertTrue(successful.get()); | |
} | |
/** | |
* This test validates that a non EDC service thread is not given a chance to | |
* complete before the DSF session proceeds with its shutdown. | |
*/ | |
@Test | |
public void testNonEDCThreadAtShutdown() throws Throwable { | |
TestUtils.showDebugPerspective(); | |
launch = createLaunch(); | |
assertNotNull(launch); | |
session = TestUtils.waitForSession(launch); | |
assertNotNull(session); | |
final ExecutionDMC threadDMC = TestUtils.waitForSuspendedThread(session); | |
assertNotNull(threadDMC); | |
TestUtils.waitForStackFrame(session, threadDMC); | |
final INoop service = TestUtils.getService(session, INoop.class); | |
final String semaphore = new String(); | |
final AtomicBoolean successful = new AtomicBoolean(false); | |
final Throwable[] exception = new Throwable[1]; | |
final Runnable runnable = new Runnable() { | |
public void run() { | |
try { | |
DataRequestMonitor<Boolean> drm = new DataRequestMonitor<Boolean>(service.getExecutor(), null) { | |
@Override | |
protected void handleRejectedExecutionException() { | |
// Executor is shutting down | |
synchronized (exception) { | |
exception[0] = new Exception("Executor has been shut down."); | |
} | |
synchronized (semaphore) { | |
semaphore.notify(); | |
} | |
} | |
@Override | |
protected void handleCompleted() { | |
successful.set(getData()); | |
synchronized (semaphore) { | |
semaphore.notify(); | |
} | |
}}; | |
service.longNoop(3, drm); | |
} | |
catch (Throwable exc) { | |
synchronized (exception) { | |
exception[0] = exc; | |
} | |
synchronized (semaphore) { | |
semaphore.notify(); | |
} | |
} | |
} | |
}; | |
// Kick off some work in a regular thread (non EDC service thread). Its logic will encounter | |
// an exception if the DSF session shuts down while they're running | |
new Thread(runnable).start(); | |
// Tell the DSF session to shut down | |
TestUtils.shutdownDebugSession(launch, session); | |
// Avoid a redundant shutdown by the @After method | |
testDidShutdown = true; | |
// Wait until the thread either completes or encounters an | |
// exception | |
synchronized (semaphore) { | |
semaphore.wait(4 * 1000); | |
} | |
// it's expected to run into exception | |
synchronized (exception) { | |
assertNotNull(exception[0]); | |
} | |
// the task (request monitor) is not completed successfully | |
assertFalse(successful.get()); | |
} | |
/** | |
* Basic test. See {@link #testStackTraces()} | |
*/ | |
@Test | |
public void testStackTraces1() throws Throwable { | |
testStackTraces(); | |
} | |
/** | |
* Variation that uses a thread pool with only one thread. Will be slower, | |
* but should be able to handle the load. | |
*/ | |
/** | |
* @throws Throwable | |
*/ | |
@Test | |
public void testStackTraces2() throws Throwable { | |
System.setProperty("org.eclipse.cdt.edc.poolthread.coreThreadCount", "1"); | |
testStackTraces(); | |
} | |
/** | |
* Variation that uses a larger thread pool. Should be more effective in | |
* flushing out concurrenc issues since more threads are running the same | |
* code simultaneously | |
*/ | |
/** | |
* @throws Throwable | |
*/ | |
@Test | |
public void testStackTraces3() throws Throwable { | |
System.setProperty("org.eclipse.cdt.edc.poolthread.coreThreadCount", "10"); | |
testStackTraces(); | |
} | |
/** | |
* Test that an overwhelmed thread pool throws the expected exception. | |
* There's no way three threads can handle 10,000 requests with a maximum | |
* backlog of five requests. | |
*/ | |
/** | |
* @throws Throwable | |
*/ | |
@Test | |
public void testStackTraces4() throws Throwable { | |
String existingProp = System.getProperty("org.eclipse.cdt.edc.poolthread.queueLimit"); | |
if (existingProp == null) | |
existingProp = "10000"; // See value in EDC Launch | |
System.setProperty("org.eclipse.cdt.edc.poolthread.queueLimit", "5"); | |
try { | |
testStackTraces(); | |
Assert.fail(); // RejectedExecutionException should have been thrown | |
} | |
catch (RejectedExecutionException exc) {} | |
System.setProperty("org.eclipse.cdt.edc.poolthread.queueLimit", existingProp); | |
} | |
/** | |
* @throws Throwable | |
*/ | |
public void testStackTraces() throws Throwable { | |
TestUtils.showDebugPerspective(); | |
launch = createLaunch(); | |
assertNotNull(launch); | |
session = TestUtils.waitForSession(launch); | |
assertNotNull(session); | |
final ExecutionDMC threadDMC = TestUtils.waitForSuspendedThread(session); | |
assertNotNull(threadDMC); | |
TestUtils.waitForStackFrame(session, threadDMC); | |
final AtomicInteger completed = new AtomicInteger(); | |
final Stack stackService = TestUtils.getService(session, Stack.class); | |
final int testCount = 10000; | |
final IFrameDMContext[][] referenceStackCrawl = new IFrameDMContext[1][]; | |
final Throwable[] exception = new Throwable[1]; | |
Runnable getFrames = new Runnable() { | |
public void run() { | |
try { | |
// Don't bother if another thread has already encountered an | |
// exception | |
synchronized (exception) { | |
if (exception[0] != null) { | |
return; | |
} | |
} | |
IFrameDMContext[] frames = stackService.getFramesForDMC((ExecutionDMC)threadDMC, 0, IStack.ALL_FRAMES); | |
// The first stack crawl we get is the one we compare all | |
// subsequent ones to | |
synchronized (referenceStackCrawl) { | |
if (referenceStackCrawl[0] == null) { | |
referenceStackCrawl[0] = frames; | |
} | |
} | |
if (frames != referenceStackCrawl[0]) { | |
Assert.assertEquals(referenceStackCrawl[0].length, frames.length); | |
for (int i = 0; i < frames.length; i++) { | |
Assert.assertEquals(frames[i].getLevel(), referenceStackCrawl[0][i].getLevel()); | |
compareStackFrames((StackFrameDMC)frames[i], ((StackFrameDMC)referenceStackCrawl[0][i])); | |
} | |
} | |
if (completed.incrementAndGet() == testCount) { | |
synchronized (completed) { | |
completed.notify(); | |
} | |
} | |
} catch (Throwable e) { | |
synchronized (exception) { | |
if (exception[0] == null) { | |
exception[0] = e; | |
} | |
} | |
synchronized (completed) { | |
completed.notify(); | |
} | |
} | |
} | |
}; | |
for (int i = 0; i < testCount; i++) { | |
EDCLaunch.getThreadPool(stackService.getSession().getId()).execute(getFrames); | |
} | |
synchronized (completed) { | |
completed.wait(30*1000); | |
} | |
// See if the threads encountered an exception. If so, then throw it from this thread (the test thread) so the test fails accordingly | |
synchronized (exception) { | |
if (exception[0] != null) { | |
throw exception[0]; | |
} | |
} | |
Assert.assertEquals(testCount, completed.get()); | |
} | |
/** | |
* Validate two stack frames are equal | |
*/ | |
private static void compareStackFrames(StackFrameDMC f1, StackFrameDMC f2) throws Exception { | |
Assert.assertEquals(f1.getFunctionName(), f2.getFunctionName()); | |
Assert.assertEquals(f1.getLineNumber(), f2.getLineNumber()); | |
Assert.assertEquals(f1.getModuleName(), f2.getModuleName()); | |
Assert.assertEquals(f1.getName(), f2.getName()); | |
Assert.assertEquals(f1.getSourceFile(), f2.getSourceFile()); | |
Assert.assertEquals(f1.getBaseAddress(), f2.getBaseAddress()); | |
Assert.assertEquals(f1.getCalledFrame(), f2.getCalledFrame()); | |
} | |
@After | |
public void shutdown() { | |
if (!testDidShutdown) { | |
TestUtils.shutdownDebugSession(launch, session); | |
} | |
} | |
@Override | |
protected void configureLaunchConfiguration( | |
ILaunchConfigurationWorkingCopy configuration) { | |
// Make sure it stop at main | |
configuration.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN, true); | |
configuration.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN_SYMBOL, "main"); | |
} | |
} |