blob: e07e1de19e3e09c40f7ad1fefeacc37e8dee7223 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 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
* James Blackburn (Broadcom Corp.) - ongoing development
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427
*******************************************************************************/
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 {
@Override
public boolean contains(ISchedulingRule rule) {
return (rule instanceof IResource) || rule.getClass().equals(NotifyRule.class);
}
@Override
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<Boolean> 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)
hasBuildChanges = false;
//don't let cancelation of this operation affect other operations
operationCanceled = 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) {
if (hasChanges && Policy.DEBUG_BUILD_NEEDED) {
Policy.debug("Set build hasChanges: " + hasChanges + " hasBuildChanges: " + hasBuildChanges); //$NON-NLS-1$ //$NON-NLS-2$
if (!hasBuildChanges && Policy.DEBUG_BUILD_NEEDED_STACK) {
Policy.debug(new RuntimeException("Set build hasChanges!")); //$NON-NLS-1$
}
}
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;
}
@Override
public void shutdown(IProgressMonitor monitor) {
// do nothing
}
@Override
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());
}
}
}