platform35/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkManager.java
--- /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());
+ }
+ }
+}