| /******************************************************************************* |
| * Copyright (c) 2004, 2017 IBM Corporation 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: |
| * IBM - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.tests.internal.resources; |
| |
| import junit.framework.Test; |
| import junit.framework.TestSuite; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.tests.harness.CancelingProgressMonitor; |
| import org.eclipse.core.tests.harness.TestBarrier; |
| import org.eclipse.core.tests.resources.ResourceTest; |
| |
| /** |
| * Tests concurrency issues when dealing with operations on the workspace |
| */ |
| public class WorkspaceConcurrencyTest extends ResourceTest { |
| |
| public static Test suite() { |
| return new TestSuite(WorkspaceConcurrencyTest.class); |
| } |
| |
| public WorkspaceConcurrencyTest() { |
| super(""); |
| } |
| |
| public WorkspaceConcurrencyTest(String name) { |
| super(name); |
| } |
| |
| private void sleep(long duration) { |
| try { |
| Thread.sleep(duration); |
| } catch (InterruptedException e) { |
| //ignore |
| } |
| } |
| |
| public void testEndRuleInWorkspaceOperation() { |
| try { |
| final IProject project = getWorkspace().getRoot().getProject("testEndRuleInWorkspaceOperation"); |
| getWorkspace().run((IWorkspaceRunnable) monitor -> Job.getJobManager().endRule(project), project, IResource.NONE, getMonitor()); |
| //should have failed |
| fail("1.0"); |
| } catch (CoreException e) { |
| fail("1.99", e); |
| } catch (RuntimeException e) { |
| //expected |
| } |
| } |
| |
| /** |
| * Tests that it is possible to cancel a workspace operation when it is blocked |
| * by activity in another thread. This is a regression test for bug 56118. |
| */ |
| public void testCancelOnBlocked() { |
| //create a dummy project |
| ensureExistsInWorkspace(getWorkspace().getRoot().getProject("P1"), true); |
| //add a resource change listener that blocks forever, thus |
| //simulating a scenario where workspace lock is held indefinitely |
| final int[] barrier = new int[1]; |
| final Throwable[] error = new Throwable[1]; |
| IResourceChangeListener listener = event -> { |
| //block until we are told to do otherwise |
| barrier[0] = TestBarrier.STATUS_START; |
| try { |
| TestBarrier.waitForStatus(barrier, TestBarrier.STATUS_DONE); |
| } catch (Throwable e) { |
| error[0] = e; |
| } |
| }; |
| getWorkspace().addResourceChangeListener(listener); |
| try { |
| //create a thread that modifies the workspace. This should |
| //hang indefinitely due to the misbehaving listener |
| TestWorkspaceJob testJob = new TestWorkspaceJob(10); |
| testJob.setTouch(true); |
| testJob.setRule(getWorkspace().getRoot()); |
| testJob.schedule(); |
| |
| //wait until blocked on the listener |
| TestBarrier.waitForStatus(barrier, TestBarrier.STATUS_START); |
| |
| //create a second thread that attempts to modify the workspace, but immediately |
| //cancels itself. This thread should terminate immediately with a cancelation exception |
| final boolean[] canceled = new boolean[] {false}; |
| Thread t2 = new Thread(() -> { |
| try { |
| getWorkspace().run((IWorkspaceRunnable) monitor -> { |
| //no-op |
| }, new CancelingProgressMonitor()); |
| } catch (CoreException e1) { |
| fail("1.99", e1); |
| } catch (OperationCanceledException e2) { |
| canceled[0] = true; |
| } |
| }); |
| t2.start(); |
| try { |
| t2.join(); |
| } catch (InterruptedException e) { |
| fail("1.88", e); |
| } |
| //should have canceled |
| assertTrue("2.0", canceled[0]); |
| |
| //finally release the listener and ensure the first thread completes |
| barrier[0] = TestBarrier.STATUS_DONE; |
| try { |
| testJob.join(); |
| } catch (InterruptedException e1) { |
| //ignore |
| } |
| if (error[0] != null) { |
| fail("3.0", error[0]); |
| } |
| } finally { |
| getWorkspace().removeResourceChangeListener(listener); |
| } |
| } |
| |
| /** |
| * Tests calling IWorkspace.run with a non-workspace rule. This should be |
| * allowed. This is a regression test for bug 60114. |
| */ |
| public void testRunnableWithOtherRule() { |
| ISchedulingRule rule = new ISchedulingRule() { |
| @Override |
| public boolean contains(ISchedulingRule rule) { |
| return rule == this; |
| } |
| |
| @Override |
| public boolean isConflicting(ISchedulingRule rule) { |
| return rule == this; |
| } |
| }; |
| try { |
| getWorkspace().run((IWorkspaceRunnable) monitor -> { |
| //noop |
| }, rule, IResource.NONE, getMonitor()); |
| } catch (CoreException e) { |
| fail("1.99", e); |
| } |
| } |
| |
| /** |
| * Tests three overlapping jobs |
| * - Job 1 (root rule) does a build. |
| * - Job 2 (null rule) overlaps Job 1 and has null scheduling rule |
| * - Job 3 (project rule) overlaps Job 2 |
| * |
| * The POST_BUILD event should occur at the end of Job 1. If it |
| * is delayed until the end of Job 3, an appropriate scheduling rule |
| * will not be available and it will fail. |
| * This is a regression test for bug 62927. |
| */ |
| public void testRunWhileBuilding() { |
| final IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| //create a POST_BUILD listener that will touch a project |
| final IProject touch = workspace.getRoot().getProject("ToTouch"); |
| final IProject rule = workspace.getRoot().getProject("jobThree"); |
| final IFile ruleFile = rule.getFile("somefile.txt"); |
| ensureExistsInWorkspace(rule, true); |
| ensureExistsInWorkspace(touch, true); |
| ensureExistsInWorkspace(ruleFile, true); |
| final Throwable[] failure = new Throwable[1]; |
| IResourceChangeListener listener = event -> { |
| try { |
| touch.touch(null); |
| } catch (CoreException e1) { |
| failure[0] = e1; |
| } catch (RuntimeException e2) { |
| failure[0] = e2; |
| } |
| }; |
| workspace.addResourceChangeListener(listener, IResourceChangeEvent.POST_BUILD); |
| try { |
| //create one job that does a build, and then waits |
| final int[] status = new int[3]; |
| Job jobOne = new Job("jobOne") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| workspace.run((IWorkspaceRunnable) monitor1 -> { |
| //do a build |
| workspace.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor1); |
| //signal that the job has done the build |
| status[0] = TestBarrier.STATUS_RUNNING; |
| //wait for job two to start |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_WAIT_FOR_DONE); |
| }, null); |
| } catch (CoreException e) { |
| return e.getStatus(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| //schedule and wait for job one to start |
| jobOne.schedule(); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_RUNNING); |
| |
| //create job two that does an empty workspace operation |
| Job jobTwo = new Job("jobTwo") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| workspace.run((IWorkspaceRunnable) monitor1 -> { |
| //signal that this job has started |
| status[1] = TestBarrier.STATUS_RUNNING; |
| //let job one finish |
| status[0] = TestBarrier.STATUS_WAIT_FOR_DONE; |
| //wait for job three to start |
| TestBarrier.waitForStatus(status, 1, TestBarrier.STATUS_WAIT_FOR_DONE); |
| }, null, IResource.NONE, null); |
| } catch (CoreException e) { |
| return e.getStatus(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| jobTwo.schedule(); |
| //create job three that has a non-null rule |
| Job jobThree = new Job("jobThree") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| workspace.run((IWorkspaceRunnable) monitor1 -> { |
| //signal that this job has started |
| status[2] = TestBarrier.STATUS_RUNNING; |
| //let job two finish |
| status[1] = TestBarrier.STATUS_WAIT_FOR_DONE; |
| //ensure this job does something so the build listener runs |
| ruleFile.touch(null); |
| //wait for the ok to complete |
| TestBarrier.waitForStatus(status, 2, TestBarrier.STATUS_WAIT_FOR_DONE); |
| }, workspace.getRuleFactory().modifyRule(ruleFile), IResource.NONE, null); |
| } catch (CoreException e) { |
| return e.getStatus(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| jobThree.schedule(); |
| //wait for job two to complete |
| waitForCompletion(jobTwo); |
| |
| //let job three complete |
| status[2] = TestBarrier.STATUS_WAIT_FOR_DONE; |
| |
| //wait for job three to complete |
| waitForCompletion(jobThree); |
| |
| //ensure no jobs failed |
| IStatus result = jobOne.getResult(); |
| if (!result.isOK()) { |
| fail("1.0", new CoreException(result)); |
| } |
| result = jobTwo.getResult(); |
| if (!result.isOK()) { |
| fail("1.1", new CoreException(result)); |
| } |
| result = jobThree.getResult(); |
| if (!result.isOK()) { |
| fail("1.2", new CoreException(result)); |
| } |
| |
| if (failure[0] != null) { |
| fail("1.3", failure[0]); |
| } |
| } finally { |
| //ensure listener is removed |
| workspace.removeResourceChangeListener(listener); |
| } |
| } |
| |
| private void waitForCompletion(Job job) { |
| int i = 0; |
| while (job.getState() != Job.NONE) { |
| sleep(100); |
| //sanity test to avoid hanging tests |
| assertTrue("Timeout waiting for job to complete", i++ < 1000); |
| } |
| } |
| } |