package hudson.plugins.locksandlatches;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.*;
import hudson.tasks.BuildWrapper;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.apache.commons.lang.StringUtils;
/**
* Created by IntelliJ IDEA.
*
* @author Stephen Connolly
* @since 26-Sep-2007 16:06:28
*/
public class LockWrapper extends BuildWrapper implements ResourceActivity {
private List<LockWaitConfig> locks;
public LockWrapper(List<LockWaitConfig> locks) {
this.locks = locks;
}
public List<LockWaitConfig> getLocks() {
return locks;
}
public void setLocks(List<LockWaitConfig> locks) {
this.locks = locks;
}
@Override
public Descriptor<BuildWrapper> getDescriptor() {
return DESCRIPTOR;
}
@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
public ResourceList getResourceList() {
ResourceList resources = new ResourceList();
for (LockWaitConfig lock : locks) {
resources.w(new Resource(null, "locks-and-latches/lock/" + lock.getName(), DESCRIPTOR.getWriteLockCount()));
}
return resources;
}
@Override
public Environment setUp(AbstractBuild abstractBuild, Launcher launcher, BuildListener buildListener) throws IOException, InterruptedException {
final List<ReentrantLock> backups = new ArrayList<ReentrantLock>();
List<LockWaitConfig> locks = new ArrayList<LockWaitConfig>(this.locks);
// sort this list of locks so that we _always_ ask for the locks in order
Collections.sort(locks, new Comparator<LockWaitConfig>() {
public int compare(LockWaitConfig o1, LockWaitConfig o2) {
return o1.getName().compareTo(o2.getName());
}
});
for (LockWaitConfig lock : locks) {
ReentrantLock backupLock;
do {
backupLock = DESCRIPTOR.backupLocks.get(lock.getName());
if (backupLock == null) {
DESCRIPTOR.backupLocks.putIfAbsent(lock.getName(), new ReentrantLock());
}
} while (backupLock == null);
backups.add(backupLock);
}
buildListener.getLogger().println("[locks-and-latches] Checking to see if we really have the locks");
boolean haveAll = false;
while (!haveAll) {
haveAll = true;
List<ReentrantLock> locked = new ArrayList<ReentrantLock>();
DESCRIPTOR.lockingLock.lock();
try {
for (ReentrantLock lock : backups) {
if (lock.tryLock()) {
locked.add(lock);
} else {
haveAll = false;
break;
}
}
if (!haveAll) {
// release them all
for (ReentrantLock lock : locked) {
lock.unlock();
}
}
} finally {
DESCRIPTOR.lockingLock.unlock();
}
if (!haveAll) {
buildListener.getLogger().println("[locks-and-latches] Could not get all the locks... sleeping for 1 minute");
TimeUnit.SECONDS.sleep(60);
}
}
buildListener.getLogger().println("[locks-and-latches] Have all the locks, build can start");
return new Environment() {
@Override
public boolean tearDown(AbstractBuild abstractBuild, BuildListener buildListener) throws IOException, InterruptedException {
buildListener.getLogger().println("[locks-and-latches] Releasing all the locks");
for (ReentrantLock lock : backups) {
lock.unlock();
}
buildListener.getLogger().println("[locks-and-latches] All the locks released");
return super.tearDown(abstractBuild, buildListener);
}
};
}
public String getDisplayName() {
return DESCRIPTOR.getDisplayName();
}
public static final class DescriptorImpl extends Descriptor<BuildWrapper> {
private List<LockConfig> locks;
/**
* required to work around https://hudson.dev.java.net/issues/show_bug.cgi?id=2450
*/
private transient ConcurrentMap<String, ReentrantLock> backupLocks =
new ConcurrentHashMap<String, ReentrantLock>();
private transient ReentrantLock lockingLock = new ReentrantLock();
DescriptorImpl() {
super(LockWrapper.class);
load();
}
public String getDisplayName() {
return "Locks";
}
@Override
public BuildWrapper newInstance(StaplerRequest req, JSONObject formData) throws FormException {
List<LockWaitConfig> locks = req.bindParametersToList(LockWaitConfig.class, "locks.locks.");
return new LockWrapper(locks);
}
@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
req.bindParameters(this, "locks.");
locks = req.bindParametersToList(LockConfig.class, "locks.lock.");
save();
return super.configure(req, formData);
}
@Override
public synchronized void save() {
// let's remove blank locks
List<LockConfig> blankLocks = new ArrayList<LockConfig>();
for(LockConfig lock: getLocks()) {
if(StringUtils.isBlank(lock.getName())) {
blankLocks.add(lock);
}
}
locks.removeAll(blankLocks);
// now, we can safely sort remaining locks
Collections.sort(this.locks, new Comparator<LockConfig>() {
public int compare(LockConfig lock1, LockConfig lock2) {
return lock1.getName().compareToIgnoreCase(lock2.getName());
}
});
super.save();
}
public List<LockConfig> getLocks() {
if (locks == null) {
locks = new ArrayList<LockConfig>();
// provide default if we have none
locks.add(new LockConfig("(default)"));
}
return locks;
}
public void setLocks(List<LockConfig> locks) {
this.locks = locks;
}
public LockConfig getLock(String name) {
for (LockConfig host : locks) {
if (name.equals(host.getName())) {
return host;
}
}
return null;
}
public String[] getLockNames() {
getLocks();
String[] result = new String[locks.size()];
for (int i = 0; i < result.length; i++) {
result[i] = locks.get(i).getName();
}
return result;
}
public void addLock(LockConfig hostConfig) {
locks.add(hostConfig);
save();
}
/**
* There wass a bug in the ResourceList.isCollidingWith,
* this method used to determine the hack workaround if the bug is not fixed, but now only needs to
* return 1.
*/
synchronized int getWriteLockCount() {
return 1;
}
}
public static final class LockConfig implements Serializable {
private String name;
private transient AbstractBuild owner = null;
public LockConfig() {
}
@DataBoundConstructor
public LockConfig(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LockConfig that = (LockConfig) o;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
return true;
}
@Override
public int hashCode() {
int result;
result = (name != null ? name.hashCode() : 0);
return result;
}
}
public static final class LockWaitConfig implements Serializable {
private String name;
private transient LockConfig lock;
public LockWaitConfig() {
}
@DataBoundConstructor
public LockWaitConfig(String name) {
this.name = name;
}
public LockConfig getLock() {
if (lock == null && name != null && !"".equals(name)) {
setLock(DESCRIPTOR.getLock(name));
}
return lock;
}
public void setLock(LockConfig lock) {
this.lock = lock;
}
public String getName() {
if (lock == null) {
return name;
}
return name = lock.getName();
}
public void setName(String name) {
setLock(DESCRIPTOR.getLock(this.name = name));
}
}
private static final Logger LOGGER = Logger.getLogger(LockWrapper.class.getName());
}