hudson.plugins.locksandlatches.LockWrapper.java Source code

Java tutorial

Introduction

Here is the source code for hudson.plugins.locksandlatches.LockWrapper.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2007-2011, Stephen Connolly, Alan Harder, Romain Seguy
 *
 * 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.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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
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();

    /**
     * @see ResourceActivity#getResourceList()
     */
    public ResourceList getResourceList() {
        ResourceList resources = new ResourceList();
        for (LockWaitConfig lock : locks) {
            Resource resource = new Resource(null, "locks-and-latches/lock/" + lock.getName(),
                    DESCRIPTOR.getWriteLockCount());
            if (lock.isShared()) {
                resources.r(resource);
            } else {
                resources.w(resource);
            }
        }
        return resources;
    }

    @Override
    public Environment setUp(AbstractBuild abstractBuild, Launcher launcher, BuildListener buildListener)
            throws IOException, InterruptedException {
        final List<NamedReentrantLock> backups = new ArrayList<NamedReentrantLock>();
        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());
            }
        });

        // build the list of "real" locks
        final Map<String, Boolean> sharedLocks = new HashMap<String, Boolean>();
        for (LockWaitConfig lock : locks) {
            NamedReentrantLock backupLock;
            do {
                backupLock = DESCRIPTOR.backupLocks.get(lock.getName());
                if (backupLock == null) {
                    DESCRIPTOR.backupLocks.putIfAbsent(lock.getName(), new NamedReentrantLock(lock.getName()));
                }
            } while (backupLock == null);
            backups.add(backupLock);
            sharedLocks.put(lock.getName(), lock.isShared());
        }

        final StringBuilder locksToGet = new StringBuilder();
        CollectionUtils.forAllDo(backups, new Closure() {
            public void execute(Object input) {
                locksToGet.append(((NamedReentrantLock) input).getName()).append(", ");
            }
        });

        buildListener.getLogger()
                .println("[locks-and-latches] Locks to get: " + locksToGet.substring(0, locksToGet.length() - 2));

        boolean haveAll = false;
        while (!haveAll) {
            haveAll = true;
            List<NamedReentrantLock> locked = new ArrayList<NamedReentrantLock>();

            DESCRIPTOR.lockingLock.lock();
            try {
                for (NamedReentrantLock lock : backups) {
                    boolean shared = sharedLocks.get(lock.getName());
                    buildListener.getLogger().print("[locks-and-latches] Trying to get " + lock.getName() + " in "
                            + (shared ? "shared" : "exclusive") + " mode... ");
                    Lock actualLock;
                    if (shared) {
                        actualLock = lock.readLock();
                    } else {
                        actualLock = lock.writeLock();
                    }
                    if (actualLock.tryLock()) {
                        buildListener.getLogger().println(" Success");
                        locked.add(lock);
                    } else {
                        buildListener.getLogger().println(" Failed, releasing all locks");
                        haveAll = false;
                        break;
                    }
                }
                if (!haveAll) {
                    // release them all
                    for (NamedReentrantLock lock : locked) {
                        boolean shared = sharedLocks.get(lock.getName());
                        Lock actualLock;
                        if (shared) {
                            actualLock = lock.readLock();
                        } else {
                            actualLock = lock.writeLock();
                        }
                        actualLock.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 (NamedReentrantLock lock : backups) {
                    boolean shared = sharedLocks.get(lock.getName());
                    Lock actualLock;
                    if (shared) {
                        actualLock = lock.readLock();
                    } else {
                        actualLock = lock.writeLock();
                    }
                    actualLock.unlock();
                }
                buildListener.getLogger().println("[locks-and-latches] All the locks released");
                return super.tearDown(abstractBuild, buildListener);
            }
        };
    }

    public void makeBuildVariables(AbstractBuild build, Map<String, String> variables) {
        final StringBuilder names = new StringBuilder();
        for (LockWaitConfig lock : locks) {
            if (names.length() > 0) {
                names.append(',');
            }
            names.append(lock.getName());
        }
        variables.put("LOCKS", names.toString());
    }

    public String getDisplayName() {
        return DESCRIPTOR.getDisplayName();
    }

    public static final class DescriptorImpl extends Descriptor<BuildWrapper> {
        private List<LockConfig> locks;

        /**
         * Required to work around HUDSON-2450.
         */
        private transient ConcurrentMap<String, NamedReentrantLock> backupLocks = new ConcurrentHashMap<String, NamedReentrantLock>();

        /**
         * Used to guarantee exclusivity when a build tries to get all its locks.
         */
        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
            CollectionUtils.filter(getLocks(), new Predicate() {
                public boolean evaluate(Object object) {
                    return StringUtils.isNotBlank(((LockConfig) object).getName());
                }
            });

            // 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 boolean shared;
        private transient LockConfig lock;

        public LockWaitConfig() {
        }

        @DataBoundConstructor
        public LockWaitConfig(String name, boolean shared) {
            this.name = name;
            this.shared = shared;
        }

        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));
        }

        public boolean isShared() {
            return shared;
        }

        public void setShared(boolean shared) {
            this.shared = shared;
        }

    }

    /**
     * Extends {@code ReentrantLock} to add a {@link #name} attribute (mainly
     * for display purposes).
     */
    public static final class NamedReentrantLock extends ReentrantReadWriteLock {
        private String name;

        public NamedReentrantLock(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    private static final Logger LOGGER = Logger.getLogger(LockWrapper.class.getName());

}