/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;
import hudson.util.AdaptedIterator;
import java.util.Set;
import java.util.Collection;
import java.util.AbstractCollection;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.Nonnull;
/**
* Controls mutual exclusion of {@link ResourceList}.
* @author Kohsuke Kawaguchi
*/
public class ResourceController {
/**
* {@link ResourceList}s that are used by activities that are in progress.
*/
private final Set<ResourceActivity> inProgress = new CopyOnWriteArraySet<ResourceActivity>();
/**
* View of {@link #inProgress} that exposes its {@link ResourceList}.
*/
private final Collection<ResourceList> resourceView = new AbstractCollection<ResourceList>() {
public Iterator<ResourceList> iterator() {
return new AdaptedIterator<ResourceActivity,ResourceList>(inProgress.iterator()) {
protected ResourceList adapt(ResourceActivity item) {
return item.getResourceList();
}
};
}
public int size() {
return inProgress.size();
}
};
/**
* Union of all {@link Resource}s that are currently in use.
* Updated as a task starts/completes executing.
*/
private ResourceList inUse = ResourceList.EMPTY;
/**
* Performs the task that requires the given list of resources.
*
* <p>
* The execution is blocked until the resource is available.
*
* @throws InterruptedException
* the thread can be interrupted while waiting for the available resources.
*/
public void execute(@Nonnull Runnable task, final ResourceActivity activity ) throws InterruptedException {
final ResourceList resources = activity.getResourceList();
_withLock(new Runnable() {
@Override
public void run() {
while(inUse.isCollidingWith(resources))
try {
// TODO revalidate the resource list after re-acquiring lock, for now we just let the build fail
_await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// we have a go
inProgress.add(activity);
inUse = ResourceList.union(inUse,resources);
}
});
try {
task.run();
} finally {
// TODO if AsynchronousExecution, do that later
_withLock(new Runnable() {
@Override
public void run() {
inProgress.remove(activity);
inUse = ResourceList.union(resourceView);
_signalAll();
}
});
}
}
/**
* Checks if an activity that requires the given resource list
* can run immediately.
*
* <p>
* This method is really only useful as a hint, since
* another activity might acquire resources before the caller
* gets to call {@link #execute(Runnable, ResourceActivity)}.
*/
public boolean canRun(final ResourceList resources) {
try {
return _withLock(new Callable<Boolean>() {
@Override
public Boolean call() {
return !inUse.isCollidingWith(resources);
}
});
} catch (Exception e) {
throw new IllegalStateException("Inner callable does not throw exception");
}
}
/**
* Of the resource in the given resource list, return the one that's
* currently in use.
*
* <p>
* If more than one such resource exists, one is chosen and returned.
* This method is used for reporting what's causing the blockage.
*/
public Resource getMissingResource(final ResourceList resources) {
try {
return _withLock(new Callable<Resource>() {
@Override
public Resource call() {
return resources.getConflict(inUse);
}
});
} catch (Exception e) {
throw new IllegalStateException("Inner callable does not throw exception");
}
}
/**
* Of the activities that are in progress, return one that's blocking
* the given activity, or null if it's not blocked (and thus the
* given activity can be executed immediately.)
*/
public ResourceActivity getBlockingActivity(ResourceActivity activity) {
ResourceList res = activity.getResourceList();
for (ResourceActivity a : inProgress)
if(res.isCollidingWith(a.getResourceList()))
return a;
return null;
}
protected void _await() throws InterruptedException {
wait();
}
protected void _signalAll() {
notifyAll();
}
protected void _withLock(Runnable runnable) {
synchronized (this) {
runnable.run();
}
}
protected <V> V _withLock(java.util.concurrent.Callable<V> callable) throws Exception {
synchronized (this) {
return callable.call();
}
}
}