package org.jfrog.hudson.util; import hudson.model.AbstractBuild; import hudson.model.Result; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * A helper to manage multi thread jobs such as Multi-Configuration projects. * * @author Lior Hasson */ public class ConcurrentJobsHelper { private static ConcurrentHashMap<String, ConcurrentBuild> concurrentBuildHandler = new ConcurrentHashMap<String, ConcurrentBuild>(); /** * Get an 'identifier' for the build which is composed of {@link BuildUniqueIdentifierHelper#getBuildName(AbstractBuild)} * -{@link AbstractBuild#getNumber()}. This supports even concurrent builds of the same buildjob on a slave. * * @param build The build * @return The identifier for the given build. */ private static String getConcurrentBuildJobId(AbstractBuild build) { return BuildUniqueIdentifierHelper.getBuildName(build) + "." + build.getNumber(); } /** * Get the information for the given build. * * @param build The build * @return The information about the build */ public static ConcurrentBuild getConcurrentBuild(AbstractBuild build) { return concurrentBuildHandler.get(getConcurrentBuildJobId(build)); } /** * Removes the information for the given build. * * @param build The build */ public static void removeConcurrentBuildJob(AbstractBuild build) { concurrentBuildHandler.remove(getConcurrentBuildJobId(build)); } /** * The class is used to synchronize the setup stage of jobs which are part of the same multi-configuration project (matrix projects). * This is important when we want to make sure that only one of the matrix jobs handles the build initialization * and that all other jobs wait till the initialization ends. * The project type that chooses to use this utility class should create an instance of this class * and implement the setUp() method with the initialization steps. * The setUp() method will be invoked by only one job * and in addtion, all jobs will wait till the initialization is finished. */ public static abstract class ConcurrentBuildSetupSync { public ConcurrentBuildSetupSync(AbstractBuild build, int totalBuilds) { // Add the build to the concurrent map ConcurrentBuild newBuild = new ConcurrentBuild(new AtomicInteger(totalBuilds)); ConcurrentBuild existingBuild = concurrentBuildHandler.putIfAbsent(getConcurrentBuildJobId(build), newBuild); existingBuild = existingBuild == null ? newBuild : existingBuild; if (totalBuilds == 1) { setUp(); } else { setupMultiBuild(existingBuild); } } /** * Invokes the setUp() method of only one of the matrix jobs of the build. * In addtion, all jobs will wait till the initialization is finished. * * @param build The build object. */ private void setupMultiBuild(ConcurrentBuild build) { // Only one of the matrix jobs should initialize the build: if (!build.isInitialized()) { synchronized (build) { if (!build.isInitialized()) { setUp(); build.setAsInitialized(); } } } } /** * This method should be implemented with the initialization code which we would like * to be executed by only one of the matrix jobs. */ public abstract void setUp(); } /** * The class is used to synchronize the tearDown (end) stage of jobs which are part of the same multi-configuration project (matrix projects). * This is important when we want to make sure that only one of the matrix jobs handles the build tear down and that this job is * the last job running. * The project type that chooses to use this utility class should create an instance of this class * and implement the tearDown() method with the finalization steps. * The tearDown() method will be invoked by only the last job running. */ public static abstract class ConcurrentBuildTearDownSync { public ConcurrentBuildTearDownSync(AbstractBuild build, Result buildResult) { ConcurrentBuild concurrentBuild = concurrentBuildHandler.get(getConcurrentBuildJobId(build)); if (concurrentBuild.getThreadsCounter().decrementAndGet() == 0 || Result.ABORTED.equals(buildResult)) { tearDown(); removeConcurrentBuildJob(build); } } public abstract void tearDown(); } /** * Represents a build that its jobs need to be synchronized. */ public static class ConcurrentBuild { private AtomicInteger threadsCounter; private boolean initialized = false; private ConcurrentHashMap<String, String> params = new ConcurrentHashMap<String, String>(); public ConcurrentBuild(AtomicInteger threadsCounter) { this.threadsCounter = threadsCounter; } public void putParam(String name, String value) { params.put(name, value); } public String getParam(String name) { return params.get(name); } public AtomicInteger getThreadsCounter() { return threadsCounter; } public boolean isInitialized() { return initialized; } public void setAsInitialized() { this.initialized = true; } } }