/******************************************************************************* * * Copyright (c) 2004-2010 Oracle Corporation. * * 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: * * Kohsuke Kawaguchi * * *******************************************************************************/ package hudson.model; import hudson.model.Queue.Task; import hudson.model.queue.MappingWorksheet; import hudson.model.queue.MappingWorksheet.ExecutorChunk; import hudson.model.queue.MappingWorksheet.Mapping; import hudson.util.ConsistentHash; import hudson.util.ConsistentHash.Hash; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; /** * Strategy that decides which {@link Task} gets run on which {@link Executor}. * * @author Kohsuke Kawaguchi * @since 1.301 */ public abstract class LoadBalancer /*implements ExtensionPoint*/ { /** * Chooses the executor(s) to carry out the build for the given task. * * <p> This method is invoked from different threads, but the execution is * serialized by the caller. The thread that invokes this method always * holds a lock to {@link Queue}, so queue contents can be safely * introspected from this method, if that information is necessary to make * decisions. * * @param task The task whose execution is being considered. Never null. * @param worksheet The work sheet that represents the matching that needs * to be made. The job of this method is to determine which work units on * this worksheet are executed on which executors (also on this worksheet.) * * @return Build up the mapping by using the given worksheet and return it. * Return null if you don't want the task to be executed right now, in which * case this method will be called some time later with the same task. */ public abstract Mapping map(Task task, MappingWorksheet worksheet); /** * Uses a consistent hash for scheduling. */ public static final LoadBalancer CONSISTENT_HASH = new LoadBalancer() { @Override public Mapping map(Task task, MappingWorksheet ws) { // build consistent hash for each work chunk List<ConsistentHash<ExecutorChunk>> hashes = new ArrayList<ConsistentHash<ExecutorChunk>>(ws.works.size()); for (int i = 0; i < ws.works.size(); i++) { ConsistentHash<ExecutorChunk> hash = new ConsistentHash<ExecutorChunk>(new Hash<ExecutorChunk>() { public String hash(ExecutorChunk node) { return node.getName(); } }); for (ExecutorChunk ec : ws.works(i).applicableExecutorChunks()) { hash.add(ec, ec.size() * 100); } hashes.add(hash); } // do a greedy assignment Mapping m = ws.new Mapping(); assert m.size() == ws.works.size(); // just so that you the reader of the source code don't get confused with the for loop index if (assignGreedily(m, task, hashes, 0)) { assert m.isCompletelyValid(); return m; } else { return null; } } private boolean assignGreedily(Mapping m, Task task, List<ConsistentHash<ExecutorChunk>> hashes, int i) { if (i == hashes.size()) { return true; // fully assigned } String key = task.getFullDisplayName() + (i > 0 ? String.valueOf(i) : ""); for (ExecutorChunk ec : hashes.get(i).list(key)) { // let's attempt this assignment m.assign(i, ec); if (m.isPartiallyValid() && assignGreedily(m, task, hashes, i + 1)) { return true; // successful greedily allocation } // otherwise 'ec' wasn't a good fit for us. try next. } // every attempt failed m.assign(i, null); return false; } }; /** * Traditional implementation of this. * * @deprecated as of 1.377 The only implementation in the core now is the * one based on consistent hash. */ public static final LoadBalancer DEFAULT = CONSISTENT_HASH; /** * Wraps this {@link LoadBalancer} into a decorator that tests the basic * sanity of the implementation. Only override this if you find some of the * checks excessive, but beware that it's like driving without a seat belt. */ protected LoadBalancer sanitize() { final LoadBalancer base = this; return new LoadBalancer() { @Override public Mapping map(Task task, MappingWorksheet worksheet) { if (Queue.ifBlockedByHudsonShutdown(task)) { // if we are quieting down, don't start anything new so that // all executors will be eventually free. return null; } return base.map(task, worksheet); } /** * Double-sanitization is pointless. */ @Override protected LoadBalancer sanitize() { return this; } private final Logger LOGGER = Logger.getLogger(LoadBalancer.class.getName()); }; } }