Bug 478634 - Remove threadJob from waiting queue after rule transfer

In case the threadJob was waiting on the rule blocked by other job, the
blocked rule was transferred to the thread of the waiting threadJob and
both rules were compatible, remove threadJob from the waiting jobs
queue.

Change-Id: Id1fb1e0a567ae5a9bfaede00591a83bc3aa2d3f8
Signed-off-by: Rastislav Wagner <rawagner@redhat.com>
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java
index cdf9f90..cb694b2 100644
--- a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java
+++ b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/ThreadJob.java
@@ -245,6 +245,7 @@
 		ThreadJob result = threadJob;
 		boolean interrupted = false;
 		boolean waiting = false;
+		boolean ruleCompatibleAndTransferred = false;
 		try {
 			waitStart(threadJob, monitor, blockingJob);
 			manager.implicitJobs.addWaiting(threadJob);
@@ -288,6 +289,8 @@
 					result = (ThreadJob) blockingJob;
 					// push expects a compatible rule, otherwise an exception will be thrown
 					result.push(threadJob.getRule());
+					// rule was either accepted or both jobs have null rules
+					ruleCompatibleAndTransferred = true;
 					result.isBlocked = threadJob.isBlocked;
 					// Condition #3.
 					return result;
@@ -326,9 +329,23 @@
 				manager.getLockManager().removeLockWaitThread(currentThread, threadJob.getRule());
 			}
 		} finally {
-			//only update the lock state if we ended up using the thread job that was given to us
-			waitEnd(threadJob, threadJob == result, monitor);
-			if (threadJob == result) {
+			boolean canStopWaiting;
+			boolean updateLockState;
+			if (threadJob != result) {
+				// The rule which was blocking given threadJob could have been transferred to
+				// this thread while we were waiting, and if our rule was contained in the
+				// blocking rule, we can remove this job from the waiting queue
+				canStopWaiting = ruleCompatibleAndTransferred;
+				// lock sate should be unchanged, the thread is same as before
+				updateLockState = false;
+			} else {
+				// job acquired blocked rule, so it can be removed from the waiting queue
+				canStopWaiting = true;
+				// update the lock state because our thread acquired the rule now
+				updateLockState = true;
+			}
+			waitEnd(threadJob, updateLockState, monitor);
+			if (canStopWaiting) {
 				if (waiting)
 					manager.implicitJobs.removeWaiting(threadJob);
 			}
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/AllTests.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/AllTests.java
index 86010a3..1fbf4ed 100644
--- a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/AllTests.java
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/AllTests.java
@@ -44,6 +44,7 @@
 		suite.addTestSuite(Bug_311863.class);
 		suite.addTestSuite(Bug_316839.class);
 		suite.addTestSuite(Bug_320329.class);
+		suite.addTestSuite(Bug_478634.class);
 		suite.addTest(Bug_412138.suite());
 		suite.addTestSuite(WorkerPoolTest.class);
 		return suite;
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/Bug_478634.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/Bug_478634.java
new file mode 100644
index 0000000..996cc06
--- /dev/null
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/Bug_478634.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 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 Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.tests.runtime.jobs;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.*;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class Bug_478634 extends AbstractJobTest {
+
+	PathRule rootRule = new PathRule("/");
+	PathRule projectRule = new PathRule("/a");
+	IJobManager jobManager = Job.getJobManager();
+	ProjectJob projectJob = new ProjectJob();
+
+	@Test
+	public void testWaitingThreadJob() {
+		projectJob.schedule();
+		waitForCompletion(projectJob);
+		ShouldNotBeBlockedJob j = new ShouldNotBeBlockedJob();
+		j.setRule(rootRule);
+		j.schedule();
+		waitForCompletion(j);
+		assertFalse("Job was blocked", j.wasBlocked());
+
+	}
+
+	class ShouldNotBeBlockedJob extends Job {
+
+		private boolean blocked;
+
+		public ShouldNotBeBlockedJob() {
+			super("ShouldNotBeBlockedJob");
+		}
+
+		public boolean wasBlocked() {
+			return blocked;
+		}
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			blocked = isBlocking();
+			return Status.OK_STATUS;
+		}
+
+	}
+
+	class RootJob extends Job {
+		private ThreadJobListener listener;
+
+		public RootJob(ThreadJobListener listener) {
+			super("RootJob");
+			this.listener = listener;
+		}
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			jobManager.beginRule(rootRule, new NullProgressMonitor());
+			listener.notifyBeginRule();
+			try {
+				Thread.sleep(100);
+			} catch (InterruptedException e) {
+			}
+			jobManager.transferRule(rootRule, projectJob.getThread());
+			return Status.OK_STATUS;
+		}
+
+	}
+
+	class ProjectJob extends Job {
+
+		public ProjectJob() {
+			super("ProjectJob");
+		}
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			ThreadJobListener tListener = new ThreadJobListener();
+			RootJob rootJob = new RootJob(tListener);
+			rootJob.schedule();
+			while (!tListener.isBeginRule()) {
+				try {
+					Thread.sleep(50);
+				} catch (InterruptedException e) {
+				}
+			}
+			jobManager.beginRule(projectRule, new NullProgressMonitor());
+			jobManager.endRule(projectRule);
+			jobManager.endRule(rootRule);
+			return Status.OK_STATUS;
+
+		}
+
+	}
+
+	class ThreadJobListener extends JobChangeAdapter {
+
+		private boolean beginRule = false;
+
+		public void notifyBeginRule() {
+			beginRule = true;
+		}
+
+		public boolean isBeginRule() {
+			return beginRule;
+		}
+
+	}
+
+}