platform35/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java
changeset 40 eb3c938c7fef
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform35/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java	Thu Jul 30 11:56:23 2009 -0500
@@ -0,0 +1,317 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 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.internal.resources;
+
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.jobs.*;
+
+/**
+ * The work manager governs concurrent access to the workspace tree.  The {@link #lock}
+ * field is used to protect the workspace tree data structure from concurrent
+ * write attempts.  This is an internal lock that is generally not held while 
+ * client code is running.  Scheduling rules are used by client code to obtain
+ * exclusive write access to a portion of the workspace. 
+ * 
+ * This class also tracks operation state for each thread that is involved in an
+ * operation. This includes prepared and running operation depth, auto-build
+ * strategy and cancel state.
+ */
+public class WorkManager implements IManager {
+	/**
+	 * Scheduling rule for use during resource change notification. This rule
+	 * must always be allowed to nest within a resource rule of any granularity
+	 * since it is used from within the scope of all resource changing
+	 * operations. The purpose of this rule is two-fold: 1. To prevent other
+	 * resource changing jobs from being scheduled while the notification is
+	 * running 2. To cause an exception if a resource change listener tries to
+	 * begin a resource rule during a notification. This also prevents
+	 * deadlock, because the notification thread owns the workspace lock, and
+	 * threads that own the workspace lock must never block trying to acquire a
+	 * resource rule.
+	 */
+	class NotifyRule implements ISchedulingRule {
+		public boolean contains(ISchedulingRule rule) {
+			return (rule instanceof IResource) || rule.getClass().equals(NotifyRule.class);
+		}
+
+		public boolean isConflicting(ISchedulingRule rule) {
+			return contains(rule);
+		}
+	}
+
+	/**
+	 * Indicates that the last checkIn failed, either due to cancelation or due to the
+	 * workspace tree being locked for modifications (during resource change events).
+	 */
+	private final ThreadLocal checkInFailed = new ThreadLocal();
+	/**
+	 * Indicates whether any operations have run that may require a build. 
+	 */
+	private boolean hasBuildChanges = false;
+	private IJobManager jobManager;
+	/**
+	 * The primary workspace lock. This lock must be held by any thread
+	 * modifying the workspace tree.
+	 */
+	private final ILock lock;
+	
+	/**
+	 * The current depth of running nested operations.
+	 */
+	private int nestedOperations = 0;
+	
+	private NotifyRule notifyRule = new NotifyRule();
+	
+	private boolean operationCanceled = false;
+	
+	/**
+	 * The current depth of prepared operations.
+	 */
+	private int preparedOperations = 0;
+	private Workspace workspace;
+
+	public WorkManager(Workspace workspace) {
+		this.workspace = workspace;
+		this.jobManager = Job.getJobManager();
+		this.lock = jobManager.newLock();
+	}
+	
+	/**
+	 * Releases the workspace lock without changing the nested operation depth.
+	 * Must be followed eventually by endUnprotected. Any
+	 * beginUnprotected/endUnprotected pair must be done entirely within the
+	 * scope of a checkIn/checkOut pair. Returns the old lock depth.
+	 * @see #endUnprotected(int)
+	 */
+	public int beginUnprotected() {
+		int depth = lock.getDepth();
+		for (int i = 0; i < depth; i++)
+			lock.release();
+		return depth;
+	}
+
+	/**
+	 * An operation calls this method and it only returns when the operation is
+	 * free to run.
+	 */
+	public void checkIn(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException {
+		boolean success = false;
+		try {
+			if (workspace.isTreeLocked()) {
+				String msg = Messages.resources_cannotModify;
+				throw new ResourceException(IResourceStatus.WORKSPACE_LOCKED, null, msg, null);
+			}
+			jobManager.beginRule(rule, monitor);
+			lock.acquire();
+			incrementPreparedOperations();
+			success = true;
+		} finally {
+			//remember if we failed to check in, so we can avoid check out
+			if (!success)
+				checkInFailed.set(Boolean.TRUE);
+		}
+	}
+
+	/**
+	 * Returns true if the check in for this thread failed, in which case the
+	 * check out and other end of operation code should not run.
+	 * <p>
+	 * The failure flag is reset immediately after calling this method. Subsequent
+	 * calls to this method will indicate no failure (unless a new failure has occurred).
+	 * @return <code>true</code> if the checkIn failed, and <code>false</code> otherwise.
+	 */
+	public boolean checkInFailed(ISchedulingRule rule) {
+		if (checkInFailed.get() != null) {
+			//clear the failure flag for this thread
+			checkInFailed.set(null);
+			//must still end the rule even in the case of failure
+			if (!workspace.isTreeLocked())
+				jobManager.endRule(rule);
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Inform that an operation has finished. 
+	 */
+	public synchronized void checkOut(ISchedulingRule rule) {
+		decrementPreparedOperations();
+		rebalanceNestedOperations();
+		//reset state if this is the end of a top level operation
+		if (preparedOperations == 0)
+			operationCanceled = hasBuildChanges = false;
+		try {
+			lock.release();
+		} finally {
+			//end rule in finally in case lock.release throws an exception
+			jobManager.endRule(rule);
+		}
+	}
+
+	/**
+	 * This method can only be safely called from inside a workspace
+	 * operation. Should NOT be called from outside a
+	 * prepareOperation/endOperation block.
+	 */
+	private void decrementPreparedOperations() {
+		preparedOperations--;
+	}
+
+	/**
+	 * Re-acquires the workspace lock that was temporarily released during an
+	 * operation, and restores the old lock depth.
+	 * @see #beginUnprotected()
+	 */
+	public void endUnprotected(int depth) {
+		for (int i = 0; i < depth; i++)
+			lock.acquire();
+	}
+
+	/**
+	 * Returns the work manager's lock
+	 */
+	ILock getLock() {
+		return lock;
+	}
+	
+	/**
+	 * Returns the scheduling rule used during resource change notifications.
+	 */
+	public ISchedulingRule getNotifyRule() {
+		return notifyRule;
+	}
+
+	/**
+	 * This method can only be safely called from inside a workspace
+	 * operation. Should NOT be called from outside a
+	 * prepareOperation/endOperation block.
+	 */
+	public synchronized int getPreparedOperationDepth() {
+		return preparedOperations;
+	}
+
+	/**
+	 * This method can only be safely called from inside a workspace
+	 * operation. Should NOT be called from outside a
+	 * prepareOperation/endOperation block.
+	 */
+	void incrementNestedOperations() {
+		nestedOperations++;
+	}
+
+	/**
+	 * This method can only be safely called from inside a workspace
+	 * operation. Should NOT be called from outside a
+	 * prepareOperation/endOperation block.
+	 */
+	private void incrementPreparedOperations() {
+		preparedOperations++;
+	}
+
+	/**
+	 * Returns true if the nested operation depth is the same as the prepared
+	 * operation depth, and false otherwise. This method can only be safely
+	 * called from inside a workspace operation. Should NOT be called from
+	 * outside a prepareOperation/endOperation block.
+	 */
+	boolean isBalanced() {
+		return nestedOperations == preparedOperations;
+	}
+
+	/**
+	 * Returns true if the workspace lock has already been acquired by this
+	 * thread, and false otherwise.
+	 */
+	public boolean isLockAlreadyAcquired() {
+		boolean result = false;
+		try {
+			boolean success = lock.acquire(0L);
+			if (success) {
+				//if lock depth is greater than one, then we already owned it
+				// before
+				result = lock.getDepth() > 1;
+				lock.release();
+			}
+		} catch (InterruptedException e) {
+			// ignore
+		}
+		return result;
+	}
+
+	/**
+	 * This method can only be safely called from inside a workspace
+	 * operation. Should NOT be called from outside a
+	 * prepareOperation/endOperation block.
+	 */
+	public void operationCanceled() {
+		operationCanceled = true;
+	}
+
+	/**
+	 * Used to make things stable again after an operation has failed between a
+	 * workspace.prepareOperation() and workspace.beginOperation(). This method
+	 * can only be safely called from inside a workspace operation. Should NOT
+	 * be called from outside a prepareOperation/endOperation block.
+	 */
+	public void rebalanceNestedOperations() {
+		nestedOperations = preparedOperations;
+	}
+
+	/**
+	 * Indicates if the operation that has just completed may potentially
+	 * require a build.
+	 */
+	public void setBuild(boolean hasChanges) {
+		hasBuildChanges = hasBuildChanges || hasChanges;
+	}
+
+	/**
+	 * This method can only be safely called from inside a workspace operation.
+	 * Should NOT be called from outside a prepareOperation/endOperation block.
+	 */
+	public boolean shouldBuild() {
+		if (hasBuildChanges) {
+			if (operationCanceled)
+				return Policy.buildOnCancel;
+			return true;
+		}
+		return false;
+	}
+
+	public void shutdown(IProgressMonitor monitor) {
+		// do nothing
+	}
+
+	public void startup(IProgressMonitor monitor) {
+		jobManager.beginRule(workspace.getRoot(), monitor);
+		lock.acquire();
+	}
+
+	/**
+	* This method should be called at the end of the workspace startup, even if the startup failed. 
+	* It must be preceded by a call to <code>startup</code>. It releases the primary workspace lock
+	* and ends applying the workspace rule to this thread.
+	*/
+	void postWorkspaceStartup() {
+		try {
+			lock.release();
+		} finally {
+			//end rule in finally in case lock.release throws an exception
+			jobManager.endRule(workspace.getRoot());
+		}
+	}
+}