From 56aba13759b22f5f5124725c9182c65def7ae0ce Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 16 Apr 2015 17:46:22 +0300 Subject: [PATCH 1/3] [WiP] - TCB plugin performance (extra caching) The approach is not ready. --- .../ThrottleCategoriesCountersCache.java | 230 ++++++++++++++++++ .../ThrottleJobProperty.java | 1 + .../ThrottleQueueTaskDispatcher.java | 89 ++++++- .../ThrottleRunListener.java | 53 ++++ .../util/ThrottleHelper.java | 83 +++++++ 5 files changed, 449 insertions(+), 7 deletions(-) create mode 100644 src/main/java/hudson/plugins/throttleconcurrents/ThrottleCategoriesCountersCache.java create mode 100644 src/main/java/hudson/plugins/throttleconcurrents/ThrottleRunListener.java create mode 100644 src/main/java/hudson/plugins/throttleconcurrents/util/ThrottleHelper.java diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleCategoriesCountersCache.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleCategoriesCountersCache.java new file mode 100644 index 00000000..a981670c --- /dev/null +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleCategoriesCountersCache.java @@ -0,0 +1,230 @@ +/* + * The MIT License + * + * Copyright 2015 CloudBees Inc. + * + * 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.throttleconcurrents; + +import hudson.model.AbstractBuild; +import hudson.model.JobProperty; +import hudson.model.queue.CauseOfBlockage; +import hudson.plugins.throttleconcurrents.util.ThrottleHelper; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; + +/** + * Stores additional cache for {@link ThrottleQueueTaskDispatcher}. + * The cache is supposed to be used from {@link RunListener}. + * @author Oleg Nenashev + * @since TODO + */ +public class ThrottleCategoriesCountersCache { + + private static final ThrottleCategoriesCountersCache INSTANCE = new ThrottleCategoriesCountersCache(); + + public static ThrottleCategoriesCountersCache getInstance() { + return INSTANCE; + } + + private final Map categoriesMap = new HashMap(); + + private @CheckForNull Map categoriesHash = null; + + private CategoryEntry getCategoryEntry(String categoryName) { + CategoryEntry res = categoriesMap.get(categoryName); + if (res == null) { + res = new CategoryEntry(categoryName); + categoriesMap.put(categoryName, res); + } + return res; + } + + private Map getCategoriesHash() { + if (categoriesHash == null) { + categoriesHash = new HashMap(); + refreshCategoriesCache(); + } + return categoriesHash; + } + + public synchronized @CheckForNull CauseOfBlockage canTake(Collection categoryNames, String nodeName) { + // Global check + for (String categoryName : categoryNames) { + int maxValue = getCategoriesHash().get(categoryName).getMaxConcurrentTotal(); + int totalBuildsCount = getTotalBuildsNumber(categoryName); + if (totalBuildsCount >= maxValue) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityTotal(totalBuildsCount)); + } + } + + // Per-node check + for (String categoryName : categoryNames) { + int maxValue = getCategoriesHash().get(categoryName).getMaxConcurrentPerNode(); + int totalBuildsCountOnNode = getNodeBuildsNumber(categoryName, nodeName); + if (totalBuildsCountOnNode >= maxValue) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityTotal(totalBuildsCountOnNode)); + } + } + return null; + } + + public synchronized @CheckForNull CauseOfBlockage canRun(Collection categoryNames) { + for (String categoryName : categoryNames) { + int maxValue = getCategoriesHash().get(categoryName).getMaxConcurrentTotal(); + int totalBuildsCount = getTotalBuildsNumber(categoryName); + if (totalBuildsCount >= maxValue) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityTotal(totalBuildsCount)); + } + } + return null; + } + + /** + * Updates global cache of category properties + */ + public synchronized void refreshCategoriesCache() { + Jenkins j = Jenkins.getInstance(); + if (j == null) { + throw new IllegalStateException("Jenkins must be started"); + } + + ThrottleJobProperty.DescriptorImpl d = j.getDescriptorByType(ThrottleJobProperty.DescriptorImpl.class); + if (d == null) { + throw new IllegalStateException("Cannot get the Throttle Property descriptor"); + } + + List categories = d.getCategories(); + getCategoriesHash().clear(); + for (ThrottleJobProperty.ThrottleCategory category : categories) { + categoriesHash.put(category.getCategoryName(), category); + } + } + + private int getTotalBuildsNumber(String categoryName) { + return getCategoryEntry(categoryName).globalCount.count; + } + + private int getNodeBuildsNumber(String categoryName, String nodeName) { + return getCategoryEntry(categoryName).getEntry(nodeName).count; + } + + //TODO: recalculate cache on jobs update + + public synchronized void fireStarted(AbstractBuild build) { + JobProperty jp = ThrottleHelper.getThrottleJobProperty(build.getProject()); + if (jp == null) { + return; + } + final ThrottleJobProperty tjp = (ThrottleJobProperty)jp; + if ( !ThrottleHelper.shouldBeThrottled(build.getParent(), tjp) ) { + return; + } + + // Update the categories cache + if (tjp.getThrottleOption().equals("category")) { + final List categories = tjp.getCategories(); + if (categories != null && !categories.isEmpty()) { + for (String categoryName : categories) { + getCategoryEntry(categoryName).fireStarted(build); + } + } + } + + } + + public synchronized void fireCompleted(AbstractBuild build) { + //TODO: remove code dup + JobProperty jp = ThrottleHelper.getThrottleJobProperty(build.getProject()); + if (jp == null) { + return; + } + final ThrottleJobProperty tjp = (ThrottleJobProperty)jp; + if ( !ThrottleHelper.shouldBeThrottled(build.getParent(), tjp) ) { + return; + } + + // Update the categories cache + if (tjp.getThrottleOption().equals("category")) { + final List categories = tjp.getCategories(); + if (categories != null && !categories.isEmpty()) { + for (String categoryName : categories) { + getCategoryEntry(categoryName).fireCompleted(build); + } + } + } + } + + private static class CategoryEntry { + private final String categoryName; + private final CounterEntry globalCount = new CounterEntry(); + private final Map nodeCounts = new HashMap(); + + public CategoryEntry(String categoryName) { + this.categoryName = null; + } + + public String getCategoryName() { + return categoryName; + } + + void fireStarted(AbstractBuild build) { + globalCount.inc(); + getEntry(build.getBuiltOnStr()).inc(); + } + + void fireCompleted(AbstractBuild build) { + globalCount.dec(); + getEntry(build.getBuiltOnStr()).dec(); + } + + private @Nonnull CounterEntry getEntry(String nodeName) { + String key = StringUtils.isEmpty(nodeName) ? "(master)" : nodeName; + CounterEntry res = nodeCounts.get(key); + if (res == null) { + res = new CounterEntry(); + nodeCounts.put(key, res); + } + return res; + } + } + + private static class CounterEntry { + int count=0; + + public int getCount() { + return count; + } + + public void inc() { + count++; + } + + public void dec() { + count--; + } + } +} diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java index f2cba356..db98b109 100644 --- a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java @@ -249,6 +249,7 @@ public boolean isMatrixProject(Job job) { @Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { req.bindJSON(this, formData); + ThrottleCategoriesCountersCache.getInstance().refreshCategoriesCache(); save(); return true; } diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java index eb1abece..10446e1e 100644 --- a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java @@ -24,6 +24,58 @@ @Extension public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher { + /** + * Fast and unreliable implementation of can take. + * It's designed to be a first-stage check in + * {@link ThrottleQueueTaskDispatcher#canTake(hudson.model.Node, hudson.model.Queue.Task)}. + * @param node + * @param task + * @param tjp Non-null job property of the task + * @return {@link CauseOfBlockage} if Jenkins cannot take the fob. + * null means that Jenkins may be able to take the job, but + * a full check is required. + */ + private @CheckForNull CauseOfBlockage fastCanTake(Node node, AbstractProject prj, ThrottleJobProperty tjp) { + // We presume that the throttling eligibility has been checked before + // The only supported type is AbstractProject + + if (tjp.getThrottleOption().equals("category")) { + final CauseOfBlockage categoriesCheckResult = + ThrottleCategoriesCountersCache.getInstance().canTake( + tjp.getCategories(), node.getNodeName()); + if (categoriesCheckResult != null) { + return categoriesCheckResult; + } + } + + return null; + } + + /** + * Fast and unreliable implementation of canRun. + * It's designed to be a first-stage check in + * {@link ThrottleQueueTaskDispatcher#canTake(hudson.model.Node, hudson.model.Queue.Task)}. + * @param node + * @param task + * @param tjp Non-null job property of the task + * @return {@link CauseOfBlockage} if Jenkins cannot take the fob. + * null means that Jenkins may be able to take the job, but + * a full check is required. + */ + private @CheckForNull CauseOfBlockage fastCanRun(AbstractProject prj, ThrottleJobProperty tjp) { + // We presume that the throttling eligibility has been checked before + // The only supported type is AbstractProject + if (tjp.getThrottleOption().equals("category")) { + final CauseOfBlockage categoriesCheckResult = + ThrottleCategoriesCountersCache.getInstance().canRun(tjp.getCategories()); + if (categoriesCheckResult != null) { + return categoriesCheckResult; + } + } + + return null; + } + @Override public CauseOfBlockage canTake(Node node, Task task) { @@ -34,8 +86,14 @@ public CauseOfBlockage canTake(Node node, Task task) { return null; } + // Perform fast inaccurate check and exit if it vetoes the task + final CauseOfBlockage fastCheckResult = fastCanTake(node, (AbstractProject) task, tjp); + if (fastCheckResult != null) { + return fastCheckResult; + } + if (tjp!=null && tjp.getThrottleEnabled()) { - CauseOfBlockage cause = canRun(task, tjp); + CauseOfBlockage cause = canRun(task, tjp, false); // isPending() is not required for canTake() if (cause != null) return cause; if (tjp.getThrottleOption().equals("project")) { @@ -91,9 +149,23 @@ else if (tjp.getThrottleOption().equals("category")) { // @Override on jenkins 4.127+ , but still compatible with 1.399 public CauseOfBlockage canRun(Queue.Item item) { ThrottleJobProperty tjp = getThrottleJobProperty(item.task); - if (tjp!=null && tjp.getThrottleEnabled()) { - return canRun(item.task, tjp); + if (!shouldBeThrottled(item.task, tjp)) { + return null; + } + + // Perform fast inaccurate check and exit if it vetoes the task + final CauseOfBlockage fastCheckResult = fastCanRun((AbstractProject)item.task, tjp); + if (fastCheckResult != null) { + return fastCheckResult; } + + // TODO: fast check of nodes + + // We do not check everything else and rely on CanTake + // It has been done to make canRun as a spot-check only + /**if (tjp!=null && tjp.getThrottleEnabled()) { + return canRun(item.task, tjp); + } */ return null; } @@ -123,10 +195,13 @@ private boolean shouldBeThrottled(@Nonnull Task task, @CheckForNull ThrottleJobP return true; } + @Deprecated public CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp) { - if (!shouldBeThrottled(task, tjp)) { - return null; - } + return canRun(task, tjp, true); + } + + public CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp, boolean checkPending) { + if (Hudson.getInstance().getQueue().isPending(task)) { return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); } @@ -158,7 +233,7 @@ else if (tjp.getThrottleOption().equals("category")) { int totalRunCount = 0; for (AbstractProject catProj : categoryProjects) { - if (Hudson.getInstance().getQueue().isPending(catProj)) { + if (checkPending && Hudson.getInstance().getQueue().isPending(catProj)) { return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); } totalRunCount += buildsOfProjectOnAllNodes(catProj); diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleRunListener.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleRunListener.java new file mode 100644 index 00000000..369d1e31 --- /dev/null +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleRunListener.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * + * Copyright 2015 CloudBees Inc. + * + * 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.throttleconcurrents; + +import hudson.Extension; +import hudson.model.AbstractBuild; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.model.listeners.RunListener; + +/** + * Performs monitoring of {@link Run}s to fill {@link ThrottleCategoriesCountersCache}. + * @author Oleg Nenashev + * @since TODO + */ +@Extension +public class ThrottleRunListener extends RunListener { + + @Override + public void onStarted(Run r, TaskListener listener) { + if (r instanceof AbstractBuild) { + ThrottleCategoriesCountersCache.getInstance().fireStarted((AbstractBuild)r); + } + } + + @Override + public void onCompleted(Run r, TaskListener listener) { + if (r instanceof AbstractBuild) { + ThrottleCategoriesCountersCache.getInstance().fireCompleted((AbstractBuild)r); + } + } +} diff --git a/src/main/java/hudson/plugins/throttleconcurrents/util/ThrottleHelper.java b/src/main/java/hudson/plugins/throttleconcurrents/util/ThrottleHelper.java new file mode 100644 index 00000000..b02066d1 --- /dev/null +++ b/src/main/java/hudson/plugins/throttleconcurrents/util/ThrottleHelper.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * + * Copyright 2015 CloudBees Inc. + * + * 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.throttleconcurrents.util; + +import hudson.matrix.MatrixConfiguration; +import hudson.matrix.MatrixProject; +import hudson.model.AbstractProject; +import hudson.model.Queue; +import hudson.plugins.throttleconcurrents.ThrottleJobProperty; +import hudson.plugins.throttleconcurrents.ThrottleMatrixProjectOptions; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * A set of helper methods. + * @author Oleg Nenashev + * @since TODO + */ +public class ThrottleHelper { + + @CheckForNull + public static ThrottleJobProperty getThrottleJobProperty(Queue.Task task) { + if (task instanceof AbstractProject) { + return getThrottleJobProperty((AbstractProject) task); + } + return null; + } + + @CheckForNull + public static ThrottleJobProperty getThrottleJobProperty(AbstractProject prj) { + if (prj instanceof MatrixConfiguration) { + prj = (AbstractProject)((MatrixConfiguration)prj).getParent(); + } + ThrottleJobProperty tjp = prj.getProperty(ThrottleJobProperty.class); + return tjp; + } + + /** + * Checks if the task should be throttled + * @param Type of task to be checked. Designed for {@link Queue.Task} and {@link AbstractProject}. + * @param task Task to be checked + * @param tjp Property of the task being evaluated + * @return + */ + public static boolean shouldBeThrottled(@Nonnull TTask task, @CheckForNull ThrottleJobProperty tjp) { + if (tjp == null) return false; + if (!tjp.getThrottleEnabled()) return false; + + // Handle matrix options + ThrottleMatrixProjectOptions matrixOptions = tjp.getMatrixOptions(); + if (matrixOptions == null) matrixOptions = ThrottleMatrixProjectOptions.DEFAULT; + if (!matrixOptions.isThrottleMatrixConfigurations() && task instanceof MatrixConfiguration) { + return false; + } + if (!matrixOptions.isThrottleMatrixBuilds()&& task instanceof MatrixProject) { + return false; + } + + // Allow throttling by default + return true; + } +} From e6656673345da06560520e861ab963aaa07b0d17 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 16 Apr 2015 19:34:32 +0300 Subject: [PATCH 2/3] Bump to 1.532 in order to get an advantage of Queue::isPending() --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 84f54aa3..d55aa63f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ THE SOFTWARE. org.jenkins-ci.plugins plugin - 1.424 + 1.532.2 throttle-concurrents @@ -113,6 +113,12 @@ THE SOFTWARE. 2.0.1 jar + + org.jenkins-ci.plugins + matrix-project + 1.0 + jar + From 008ea007dff8fa20d096dbf82b76e7f6ab98c260 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 16 Apr 2015 19:35:08 +0300 Subject: [PATCH 3/3] Replace one queue check by a fast implementation --- .../ThrottleQueueTaskDispatcher.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java index 10446e1e..3c94d853 100644 --- a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java @@ -77,7 +77,8 @@ public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher { } @Override - public CauseOfBlockage canTake(Node node, Task task) { + public CauseOfBlockage canTake(Node node, Queue.BuildableItem item) { + Task task =item.task; ThrottleJobProperty tjp = getThrottleJobProperty(task); @@ -93,7 +94,7 @@ public CauseOfBlockage canTake(Node node, Task task) { } if (tjp!=null && tjp.getThrottleEnabled()) { - CauseOfBlockage cause = canRun(task, tjp, false); // isPending() is not required for canTake() + CauseOfBlockage cause = canRun(task, tjp); // TODO: ? isPending() is not required for canTake() if (cause != null) return cause; if (tjp.getThrottleOption().equals("project")) { @@ -163,9 +164,9 @@ public CauseOfBlockage canRun(Queue.Item item) { // We do not check everything else and rely on CanTake // It has been done to make canRun as a spot-check only - /**if (tjp!=null && tjp.getThrottleEnabled()) { - return canRun(item.task, tjp); - } */ + if (tjp!=null && tjp.getThrottleEnabled()) { + return canRun(item, tjp); + } return null; } @@ -197,18 +198,23 @@ private boolean shouldBeThrottled(@Nonnull Task task, @CheckForNull ThrottleJobP @Deprecated public CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp) { - return canRun(task, tjp, true); + return canRun(Hudson.getInstance().getQueue().getItem(task), tjp); + } + + private boolean isPending(Queue.Item item) { + return (item instanceof Queue.BuildableItem) + ? ((Queue.BuildableItem)item).isPending() : false; } - public CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp, boolean checkPending) { + public CauseOfBlockage canRun(Queue.Item item, ThrottleJobProperty tjp) { - if (Hudson.getInstance().getQueue().isPending(task)) { + if (isPending(item)) { return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); } if (tjp.getThrottleOption().equals("project")) { if (tjp.getMaxConcurrentTotal().intValue() > 0) { int maxConcurrentTotal = tjp.getMaxConcurrentTotal().intValue(); - int totalRunCount = buildsOfProjectOnAllNodes(task); + int totalRunCount = buildsOfProjectOnAllNodes(item.task); if (totalRunCount >= maxConcurrentTotal) { return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityTotal(totalRunCount)); @@ -233,7 +239,7 @@ else if (tjp.getThrottleOption().equals("category")) { int totalRunCount = 0; for (AbstractProject catProj : categoryProjects) { - if (checkPending && Hudson.getInstance().getQueue().isPending(catProj)) { + if (Hudson.getInstance().getQueue().isPending(catProj)) { return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); } totalRunCount += buildsOfProjectOnAllNodes(catProj);