| package org.eclipse.jdt.core.tests.model; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jdt.core.IClassFile; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IOrdinaryClassFile; |
| import org.eclipse.jdt.core.IPackageFragment; |
| import org.eclipse.jdt.core.IPackageFragmentRoot; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.internal.core.JavaElement; |
| import org.eclipse.jdt.internal.core.nd.indexer.Indexer; |
| |
| import junit.framework.Test; |
| |
| public class JavaModelManagerTests extends AbstractJavaModelTests { |
| |
| private static final IProgressMonitor NULL_MONITOR = new NullProgressMonitor(); |
| private static final String PROJECT_NAME = JavaModelManagerTests.class.getSimpleName(); |
| |
| public static Test suite() { |
| return buildModelTestSuite(JavaModelManagerTests.class); |
| } |
| |
| public JavaModelManagerTests(String name) { |
| super(name); |
| } |
| |
| /** |
| * Test for Bug 548456 - Concurrency problem with JavaModelManager and JavaElement.openWhenClosed() |
| * |
| * We create a project with a jar on its class path and ask after {@link JavaElement#exists()} |
| * for a class in the jar. We do so from 2 different threads in parallel; with sufficient repetitions |
| * we hope to run into the race condition described in the bug. |
| */ |
| public void testBug548456_concurrentElementInfoAccess() throws Exception { |
| int iterations = 20; |
| int numberOfThreads = 10; |
| for (int i = 0; i < iterations; ++i) { |
| doTestBug548456_concurrentCallBinaryTypeExists(numberOfThreads); |
| } |
| } |
| |
| private void doTestBug548456_concurrentCallBinaryTypeExists(int numberOfThreads) throws Exception { |
| final IJavaProject project = setUpJavaProject(PROJECT_NAME); |
| try { |
| buildProject(project); |
| assertHasNoBuildProblems(project); |
| |
| CountDownLatch latch = new CountDownLatch(numberOfThreads); |
| List<CheckIfTypeExists> runnables = new ArrayList<>(numberOfThreads); |
| for (int i = 0; i < numberOfThreads; ++i) { |
| CheckIfTypeExists runnable = new CheckIfTypeExists(project, latch); |
| runnables.add(runnable); |
| } |
| |
| List<Thread> threads = startThreads(runnables); |
| |
| boolean interrupted = false; |
| for (Thread thread : threads) { |
| try { |
| thread.join(); |
| } catch (InterruptedException e) { |
| // we join all threads regardless of interruption, to be sure we don't leave running threads after this method done |
| interrupted = true; |
| } |
| } |
| assertFalse("waiting on test threads to finish must not be interrupted", interrupted); |
| |
| for (CheckIfTypeExists runnable : runnables) { |
| assertTrue("expected type for test source to exist, despite concurrent access to JavaModelManager", runnable.typeExists); |
| } |
| } finally { |
| deleteProject(PROJECT_NAME); |
| } |
| } |
| |
| private static List<Thread> startThreads(List<CheckIfTypeExists> runnables) { |
| List<Thread> threads = new ArrayList<>(runnables.size()); |
| for (CheckIfTypeExists runnable : runnables) { |
| Thread thread = new Thread(runnable); |
| threads.add(thread); |
| thread.start(); |
| } |
| return threads; |
| } |
| |
| class CheckIfTypeExists implements Runnable { |
| |
| private final IJavaProject project; |
| private final CountDownLatch latch; |
| |
| boolean typeExists = false; |
| |
| CheckIfTypeExists(IJavaProject project, CountDownLatch latch) { |
| this.project = project; |
| this.latch = latch; |
| } |
| |
| @Override |
| public void run() { |
| IType type = retrieveTestType(this.project); |
| // we use a CountDownLatch to ensure the thread calls to IJavaElement.exists() are running as close together as possible |
| this.latch.countDown(); |
| this.typeExists = type.exists(); |
| } |
| } |
| |
| static IType retrieveTestType(IJavaProject project) { |
| IFile jarResource = project.getProject().getFile("lib.jar"); |
| IPackageFragmentRoot root = project.getPackageFragmentRoot(jarResource); |
| IPackageFragment packageFragment = root.getPackageFragment("p"); |
| IClassFile classFile = packageFragment.getClassFile("X.class"); |
| IOrdinaryClassFile ordinaryClassFile = (IOrdinaryClassFile) classFile; |
| IType type = ordinaryClassFile.getType(); |
| return type; |
| } |
| |
| private static void buildProject(IJavaProject javaProject) throws Exception { |
| IProject project = javaProject.getProject(); |
| refreshProject(project); |
| project.build(IncrementalProjectBuilder.CLEAN_BUILD, NULL_MONITOR); |
| refreshProject(project); |
| project.build(IncrementalProjectBuilder.FULL_BUILD, NULL_MONITOR); |
| Job.getJobManager().join(ResourcesPlugin.FAMILY_MANUAL_BUILD, null); |
| Indexer.getInstance().waitForIndex(null); |
| refreshProject(project); |
| } |
| |
| private static void refreshProject(IProject project) throws CoreException { |
| project.refreshLocal(IResource.DEPTH_INFINITE, NULL_MONITOR); |
| waitForManualRefresh(); |
| } |
| |
| private static void assertHasNoBuildProblems(IJavaProject javaProject) throws Exception { |
| IProject project = javaProject.getProject(); |
| List<IMarker> modelProblems = getTestProjectErrorMarkers(project); |
| assertTrue("expected no problems when building, but got:" + System.lineSeparator() + toString(modelProblems), |
| modelProblems.isEmpty()); |
| } |
| |
| private static List<IMarker> getTestProjectErrorMarkers(IProject project) throws Exception { |
| IMarker[] problemMarkers = project.findMarkers(null, true, IResource.DEPTH_ZERO); |
| List<IMarker> errorMarkers = new ArrayList<>(); |
| for (IMarker problemMarker : problemMarkers) { |
| int severity = problemMarker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO); |
| if (severity == IMarker.SEVERITY_ERROR) { |
| errorMarkers.add(problemMarker); |
| } |
| } |
| return errorMarkers; |
| } |
| |
| private static String toString(List<IMarker> markers) throws Exception { |
| StringBuilder sb = new StringBuilder(); |
| for (IMarker marker : markers) { |
| sb.append("Message: "); |
| sb.append((marker.getAttribute(IMarker.MESSAGE))); |
| sb.append(System.lineSeparator()); |
| sb.append("Location: "); |
| sb.append(marker.getAttribute(IMarker.LOCATION)); |
| sb.append(System.lineSeparator()); |
| sb.append("Line: "); |
| sb.append(marker.getAttribute(IMarker.LINE_NUMBER)); |
| sb.append(System.lineSeparator()); |
| } |
| String fatalProblemsAsText = sb.toString(); |
| return fatalProblemsAsText; |
| } |
| } |