提交 7335916f 编写于 作者: S Stephen Connolly

Provide a future path to refactoring the Queue locking strategy

- Also tidy up some holes in locking strategy that can cause builds to be scheduled on
  executors that are about to be interrupted or nodes in the process of being removed
上级 7f990004
......@@ -43,9 +43,9 @@ import javax.annotation.CheckForNull;
import jenkins.model.Configuration;
public abstract class AbstractCIBase extends Node implements ItemGroup<TopLevelItem>, StaplerProxy, StaplerFallback, ViewGroup, AccessControlled, DescriptorByNameOwner {
public static boolean LOG_STARTUP_PERFORMANCE = Configuration.getBooleanConfigParameter("logStartupPerformance", false);
private static final Logger LOGGER = Logger.getLogger(AbstractCIBase.class.getName());
private final transient Object updateComputerLock = new Object();
......@@ -171,7 +171,7 @@ public abstract class AbstractCIBase extends Node implements ItemGroup<TopLevelI
byName.put(node.getNodeName(),c);
}
Set<Computer> old = new HashSet<Computer>(computers.values());
final Set<Computer> old = new HashSet<Computer>(computers.values());
Set<Computer> used = new HashSet<Computer>();
updateComputer(this, byName, used, automaticSlaveLaunch);
......
......@@ -135,7 +135,7 @@ import static javax.servlet.http.HttpServletResponse.*;
public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, ExecutorListener {
private final CopyOnWriteArrayList<Executor> executors = new CopyOnWriteArrayList<Executor>();
// TODO:
// TODO:
private final CopyOnWriteArrayList<OneOffExecutor> oneOffExecutors = new CopyOnWriteArrayList<OneOffExecutor>();
private int numExecutors;
......@@ -144,7 +144,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* Contains info about reason behind computer being offline.
*/
protected volatile OfflineCause offlineCause;
private long connectTime = 0;
/**
......@@ -180,7 +180,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
public List<ComputerPanelBox> getComputerPanelBoxs(){
return ComputerPanelBox.all(this);
}
/**
* Returns the transient {@link Action}s associated with the computer.
*/
......@@ -342,7 +342,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
connectTime = System.currentTimeMillis();
return _connect(forceReconnect);
}
/**
* Allows implementing-classes to provide an implementation for the connect method.
*
......@@ -375,13 +375,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* Gets the time (since epoch) when this computer connected.
*
*
* @return The time in ms since epoch when this computer last connected.
*/
public final long getConnectTime() {
return connectTime;
}
/**
* Disconnect this computer.
*
......@@ -591,7 +591,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
*
* @param cause
* If the first argument is true, specify the reason why the node is being put
* offline.
* offline.
*/
public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause) {
offlineCause = temporarilyOffline ? cause : null;
......@@ -713,14 +713,20 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
private synchronized void setNumExecutors(int n) {
this.numExecutors = n;
int diff = executors.size()-n;
final int diff = executors.size()-n;
if (diff>0) {
// we have too many executors
// send signal to all idle executors to potentially kill them off
for( Executor e : executors )
if(e.isIdle())
e.interrupt();
// need the Queue maintenance lock held to prevent concurrent job assignment on the idle executors
Queue.withLock(new Runnable() {
@Override
public void run() {
for( Executor e : executors )
if(e.isIdle())
e.interrupt();
}
});
}
if (diff<0) {
......@@ -1002,7 +1008,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* @since 1.300
* @return
* null if the host name cannot be computed (for example because this computer is offline,
* because the slave is behind the firewall, etc.)
* because the slave is behind the firewall, etc.)
*/
public String getHostName() throws IOException, InterruptedException {
if(hostNameCached)
......@@ -1197,7 +1203,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
String name = Util.fixEmptyAndTrim(req.getSubmittedForm().getString("name"));
Jenkins.checkGoodName(name);
Node node = getNode();
if (node == null) {
throw new ServletException("No such node " + nodeName);
......@@ -1382,7 +1388,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
File newLocation = new File(dir, "logs/slaves/" + m.group(1) + "/slave.log" + Util.fixNull(m.group(2)));
newLocation.getParentFile().mkdirs();
boolean relocationSuccessfull=f.renameTo(newLocation);
if (relocationSuccessfull) { // The operation will fail if mkdir fails
if (relocationSuccessfull) { // The operation will fail if mkdir fails
LOGGER.log(Level.INFO, "Relocated log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()});
} else {
LOGGER.log(Level.WARNING, "Cannot relocate log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()});
......
/*
* The MIT License
*
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Seiji Sogabe, Stephen Connolly
*
*
* 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
......@@ -77,10 +77,10 @@ import org.kohsuke.stapler.export.Exported;
* <p>
* Nodes are persisted objects that capture user configurations, and instances get thrown away and recreated whenever
* the configuration changes. Running state of nodes are captured by {@link Computer}s.
*
*
* <p>
* There is no URL binding for {@link Node}. {@link Computer} and {@link TransientComputerActionFactory} must
* be used to associate new {@link Action}s to slaves.
* be used to associate new {@link Action}s to slaves.
*
* @author Kohsuke Kawaguchi
* @see NodeMonitor
......@@ -145,7 +145,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
* Returns a {@link Launcher} for executing programs on this node.
*
* <p>
* The callee must call {@link Launcher#decorateFor(Node)} before returning to complete the decoration.
* The callee must call {@link Launcher#decorateFor(Node)} before returning to complete the decoration.
*/
public abstract Launcher createLauncher(TaskListener listener);
......@@ -181,7 +181,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
/**
* Gets the current channel, if the node is connected and online, or null.
*
* This is just a convenience method for {@link Computer#getChannel()} with null check.
* This is just a convenience method for {@link Computer#getChannel()} with null check.
*/
public final @CheckForNull VirtualChannel getChannel() {
Computer c = toComputer();
......@@ -291,10 +291,10 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
/**
* Returns the manually configured label for a node. The list of assigned
* and dynamically determined labels is available via
* and dynamically determined labels is available via
* {@link #getAssignedLabels()} and includes all labels that have been
* manually configured.
*
*
* Mainly for form binding.
*/
public abstract String getLabelString();
......@@ -426,11 +426,11 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
public List<NodePropertyDescriptor> getNodePropertyDescriptors() {
return NodeProperty.for_(this);
}
public ACL getACL() {
return Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
}
public final void checkPermission(Permission permission) {
getACL().checkPermission(permission);
}
......
/*
* The MIT License
*
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Stephen Connolly, Tom Huybrechts, InfraDNA, 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
......@@ -158,7 +158,7 @@ public class Queue extends ResourceController implements Saveable {
* @since 1.577
*/
private static int CACHE_REFRESH_PERIOD = Integer.getInteger(Queue.class.getName() + ".cacheRefreshPeriod", 1000);
/**
* Items that are waiting for its quiet period to pass.
*
......@@ -412,7 +412,7 @@ public class Queue extends ResourceController implements Saveable {
*/
public synchronized void save() {
if(BulkChange.contains(this)) return;
// write out the tasks on the queue
ArrayList<Queue.Item> items = new ArrayList<Queue.Item>();
for (Item item: getItems()) {
......@@ -607,7 +607,7 @@ public class Queue extends ResourceController implements Saveable {
/**
* @deprecated as of 1.311
* Use {@link #schedule(Task, int)}
* Use {@link #schedule(Task, int)}
*/
public synchronized boolean add(Task p, int quietPeriod) {
return schedule(p, quietPeriod)!=null;
......@@ -619,7 +619,7 @@ public class Queue extends ResourceController implements Saveable {
/**
* @deprecated as of 1.311
* Use {@link #schedule(Task, int, Action...)}
* Use {@link #schedule(Task, int, Action...)}
*/
public synchronized boolean add(Task p, int quietPeriod, Action... actions) {
return schedule(p, quietPeriod, actions)!=null;
......@@ -655,7 +655,7 @@ public class Queue extends ResourceController implements Saveable {
// use bitwise-OR to make sure that both branches get evaluated all the time
return blockedProjects.cancel(p)!=null | buildables.cancel(p)!=null;
}
public synchronized boolean cancel(Item item) {
LOGGER.log(Level.FINE, "Cancelling {0} item#{1}", new Object[] {item.task, item.id});
return item.cancel(this);
......@@ -721,7 +721,7 @@ public class Queue extends ResourceController implements Saveable {
public List<Item> getApproximateItemsQuickly() {
return itemsView.get();
}
public synchronized Item getItem(int id) {
for (Item item: waitingList) if (item.id == id) return item;
for (Item item: blockedProjects) if (item.id == id) return item;
......@@ -743,7 +743,7 @@ public class Queue extends ResourceController implements Saveable {
private void _getBuildableItems(Computer c, ItemList<BuildableItem> col, List<BuildableItem> result) {
Node node = c.getNode();
if (node == null) // Deleted computers cannot take build items...
if (node == null) // Deleted computers cannot take build items...
return;
for (BuildableItem p : col.values()) {
if (node.canTake(p) == null)
......@@ -967,6 +967,102 @@ public class Queue extends ResourceController implements Saveable {
return !buildables.containsKey(t) && !pendings.containsKey(t);
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
* @param runnable the operation to perform.
* @since 1.592
*/
public static void withLock(Runnable runnable) {
final Jenkins jenkins = Jenkins.getInstance();
final Queue queue = jenkins == null ? null : jenkins.getQueue();
if (queue == null) {
runnable.run();
} else {
queue._withLock(runnable);
}
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
*
* @param callable the operation to perform.
* @param <V> the type of return value
* @param <T> the type of exception.
* @return the result of the callable.
* @throws T the exception of the callable
* @since 1.592
*/
public static <V, T extends Throwable> V withLock(hudson.remoting.Callable<V, T> callable) throws T {
final Jenkins jenkins = Jenkins.getInstance();
final Queue queue = jenkins == null ? null : jenkins.getQueue();
if (queue == null) {
return callable.call();
} else {
return queue._withLock(callable);
}
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
*
* @param callable the operation to perform.
* @param <V> the type of return value
* @return the result of the callable.
* @throws Exception if the callable throws an exception.
* @since 1.592
*/
public static <V> V withLock(java.util.concurrent.Callable<V> callable) throws Exception {
final Jenkins jenkins = Jenkins.getInstance();
final Queue queue = jenkins == null ? null : jenkins.getQueue();
if (queue == null) {
return callable.call();
} else {
return queue._withLock(callable);
}
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
* @param runnable the operation to perform.
* @since 1.592
*/
protected synchronized void _withLock(Runnable runnable) {
runnable.run();
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
*
* @param callable the operation to perform.
* @param <V> the type of return value
* @param <T> the type of exception.
* @return the result of the callable.
* @throws T the exception of the callable
* @since 1.592
*/
protected synchronized <V, T extends Throwable> V _withLock(hudson.remoting.Callable<V, T> callable) throws T {
return callable.call();
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
*
* @param callable the operation to perform.
* @param <V> the type of return value
* @return the result of the callable.
* @throws Exception if the callable throws an exception.
* @since 1.592
*/
protected synchronized <V> V _withLock(java.util.concurrent.Callable<V> callable) throws Exception {
return callable.call();
}
/**
* Queue maintenance.
*
......@@ -1138,7 +1234,7 @@ public class Queue extends ResourceController implements Saveable {
* Marks {@link Task}s that are not affected by the {@linkplain Jenkins#isQuietingDown()} quieting down},
* because these tasks keep other tasks executing.
*
* @since 1.336
* @since 1.336
*/
public interface NonBlockingTask extends Task {}
......@@ -1222,7 +1318,7 @@ public class Queue extends ResourceController implements Saveable {
* Also used by default for {@link hudson.model.Queue.Item#hasCancelPermission}.
*/
boolean hasAbortPermission();
/**
* Returns the URL of this task relative to the context root of the application.
*
......@@ -1234,7 +1330,7 @@ public class Queue extends ResourceController implements Saveable {
* URL that ends with '/'.
*/
String getUrl();
/**
* True if the task allows concurrent builds, where the same {@link Task} is executed
* by multiple executors concurrently on the same or different nodes.
......@@ -1305,11 +1401,11 @@ public class Queue extends ResourceController implements Saveable {
* Called by {@link Executor} to perform the task
*/
void run();
/**
* Estimate of how long will it take to execute this executable.
* Measured in milliseconds.
*
*
* Please, consider using {@link Executables#getEstimatedDurationFor(Queue.Executable)}
* to protected against AbstractMethodErrors!
*
......@@ -1335,7 +1431,7 @@ public class Queue extends ResourceController implements Saveable {
*/
@Exported
public final int id;
/**
* Project to be built.
*/
......@@ -1343,7 +1439,7 @@ public class Queue extends ResourceController implements Saveable {
public final Task task;
private /*almost final*/ transient FutureImpl future;
private final long inQueueSince;
/**
......@@ -1367,7 +1463,7 @@ public class Queue extends ResourceController implements Saveable {
*/
@Exported
public boolean isStuck() { return false; }
/**
* Since when is this item in the queue.
* @return Unix timestamp
......@@ -1376,7 +1472,7 @@ public class Queue extends ResourceController implements Saveable {
public long getInQueueSince() {
return this.inQueueSince;
}
/**
* Returns a human readable presentation of how long this item is already in the queue.
* E.g. something like '3 minutes 40 seconds'
......@@ -1398,7 +1494,7 @@ public class Queue extends ResourceController implements Saveable {
* If this task needs to be run on a node with a particular label,
* return that {@link Label}. Otherwise null, indicating
* it can run on anywhere.
*
*
* <p>
* This code takes {@link LabelAssignmentAction} into account, then fall back to {@link SubTask#getAssignedLabel()}
*/
......@@ -1409,11 +1505,11 @@ public class Queue extends ResourceController implements Saveable {
}
return task.getAssignedLabel();
}
/**
* Test if the specified {@link SubTask} needs to be run on a node with a particular label, and
* return that {@link Label}. Otherwise null, indicating it can run on anywhere.
*
*
* <p>
* This code takes {@link LabelAssignmentAction} into account, then falls back to {@link SubTask#getAssignedLabel()}
*/
......@@ -1455,7 +1551,7 @@ public class Queue extends ResourceController implements Saveable {
this.inQueueSince = System.currentTimeMillis();
for (Action action: actions) addAction(action);
}
protected Item(Task task, List<Action> actions, int id, FutureImpl future, long inQueueSince) {
this.task = task;
this.id = id;
......@@ -1463,7 +1559,7 @@ public class Queue extends ResourceController implements Saveable {
this.inQueueSince = inQueueSince;
for (Action action: actions) addAction(action);
}
protected Item(Item item) {
this(item.task, new ArrayList<Action>(item.getAllActions()), item.id, item.future, item.inQueueSince);
}
......@@ -1516,7 +1612,7 @@ public class Queue extends ResourceController implements Saveable {
public boolean hasCancelPermission() {
return task.hasAbortPermission();
}
public String getDisplayName() {
// TODO Auto-generated method stub
return null;
......@@ -1598,16 +1694,16 @@ public class Queue extends ResourceController implements Saveable {
}
}
/**
* An optional interface for actions on Queue.Item.
* Lets the action cooperate in queue management.
*
*
* @since 1.300-ish.
*/
public interface QueueAction extends Action {
/**
* Returns whether the new item should be scheduled.
* Returns whether the new item should be scheduled.
* An action should return true if the associated task is 'different enough' to warrant a separate execution.
*/
boolean shouldSchedule(List<Action> actions);
......@@ -1619,7 +1715,7 @@ public class Queue extends ResourceController implements Saveable {
* <p>
* This handler is consulted every time someone tries to submit a task to the queue.
* If any of the registered handlers returns false, the task will not be added
* to the queue, and the task will never get executed.
* to the queue, and the task will never get executed.
*
* <p>
* The other use case is to add additional {@link Action}s to the task
......@@ -1636,7 +1732,7 @@ public class Queue extends ResourceController implements Saveable {
* upon the start of the build. This list is live, and can be mutated.
*/
public abstract boolean shouldSchedule(Task p, List<Action> actions);
/**
* All registered {@link QueueDecisionHandler}s
*/
......@@ -1644,13 +1740,13 @@ public class Queue extends ResourceController implements Saveable {
return ExtensionList.lookup(QueueDecisionHandler.class);
}
}
/**
* {@link Item} in the {@link Queue#waitingList} stage.
*/
public static final class WaitingItem extends Item implements Comparable<WaitingItem> {
private static final AtomicInteger COUNTER = new AtomicInteger(0);
/**
* This item can be run after this time.
*/
......@@ -1661,7 +1757,7 @@ public class Queue extends ResourceController implements Saveable {
super(project, actions, COUNTER.incrementAndGet(), new FutureImpl(project));
this.timestamp = timestamp;
}
public int compareTo(WaitingItem that) {
int r = this.timestamp.getTime().compareTo(that.timestamp.getTime());
if (r != 0) return r;
......@@ -1750,13 +1846,13 @@ public class Queue extends ResourceController implements Saveable {
return CauseOfBlockage.fromMessage(Messages._Queue_InProgress());
return CauseOfBlockage.fromMessage(Messages._Queue_BlockedBy(r.getDisplayName()));
}
for (QueueTaskDispatcher d : QueueTaskDispatcher.all()) {
CauseOfBlockage cause = d.canRun(this);
if (cause != null)
return cause;
}
return task.getCauseOfBlockage();
}
......@@ -2052,7 +2148,7 @@ public class Queue extends ResourceController implements Saveable {
cancel();
}
}
/**
* {@link ArrayList} of {@link Item} with more convenience methods.
*/
......@@ -2065,7 +2161,7 @@ public class Queue extends ResourceController implements Saveable {
}
return null;
}
public List<T> getAll(Task task) {
List<T> result = new ArrayList<T>();
for (T item: this) {
......@@ -2075,11 +2171,11 @@ public class Queue extends ResourceController implements Saveable {
}
return result;
}
public boolean containsKey(Task task) {
return get(task) != null;
}
public T remove(Task task) {
Iterator<T> it = iterator();
while (it.hasNext()) {
......@@ -2091,12 +2187,12 @@ public class Queue extends ResourceController implements Saveable {
}
return null;
}
public void put(Task task, T item) {
assert item.task.equals(task);
add(item);
}
public ItemList<T> values() {
return this;
}
......
......@@ -23,7 +23,11 @@
*/
package hudson.slaves;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.Queue;
import jenkins.model.Jenkins;
import java.io.IOException;
import java.util.logging.Logger;
......@@ -46,19 +50,30 @@ public class CloudRetentionStrategy extends RetentionStrategy<AbstractCloudCompu
}
@Override
public synchronized long check(AbstractCloudComputer c) {
AbstractCloudSlave computerNode = c.getNode();
public synchronized long check(final AbstractCloudComputer c) {
final AbstractCloudSlave computerNode = c.getNode();
if (c.isIdle() && !disabled && computerNode != null) {
final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds();
if (idleMilliseconds > MINUTES.toMillis(idleMinutes)) {
LOGGER.log(Level.INFO, "Disconnecting {0}",c.getName());
try {
computerNode.terminate();
} catch (InterruptedException e) {
LOGGER.log(WARNING,"Failed to terminate "+c.getName(),e);
} catch (IOException e) {
LOGGER.log(WARNING,"Failed to terminate "+c.getName(),e);
}
Queue.withLock(new Runnable() {
@Override
public void run() {
// re-check idle now that we are within the Queue lock
if (c.isIdle()) {
final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds();
if (idleMilliseconds > MINUTES.toMillis(idleMinutes)) {
LOGGER.log(Level.INFO, "Disconnecting {0}", c.getName());
try {
computerNode.terminate();
} catch (InterruptedException e) {
LOGGER.log(WARNING, "Failed to terminate " + c.getName(), e);
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to terminate " + c.getName(), e);
}
}
}
}
});
}
}
return 1;
......
/*
* The MIT License
*
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
*
*
* 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
......@@ -208,11 +208,11 @@ public abstract class RetentionStrategy<T extends Computer> extends AbstractDesc
}
@Override
public synchronized long check(SlaveComputer c) {
public synchronized long check(final SlaveComputer c) {
if (c.isOffline() && c.isLaunchSupported()) {
final HashMap<Computer, Integer> availableComputers = new HashMap<Computer, Integer>();
for (Computer o : Jenkins.getInstance().getComputers()) {
if ((o.isOnline() || o.isConnecting()) && o.isPartiallyIdle()) {
if ((o.isOnline() || o.isConnecting()) && o.isPartiallyIdle() && o.isAcceptingTasks()) {
final int idleExecutors = o.countIdle();
if (idleExecutors>0)
availableComputers.put(o, idleExecutors);
......@@ -257,10 +257,21 @@ public abstract class RetentionStrategy<T extends Computer> extends AbstractDesc
} else if (c.isIdle()) {
final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds();
if (idleMilliseconds > idleDelay * 1000 * 60 /*MINS->MILLIS*/) {
// we've been idle for long enough
logger.log(Level.INFO, "Disconnecting computer {0} as it has been idle for {1}",
new Object[]{c.getName(), Util.getTimeSpanString(idleMilliseconds)});
c.disconnect(OfflineCause.create(Messages._RetentionStrategy_Demand_OfflineIdle()));
Queue.withLock(new Runnable() {
@Override
public void run() {
// re-check idle now that we are within the Queue lock
if (c.isIdle()) {
final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds();
if (idleMilliseconds > idleDelay * 1000 * 60 /*MINS->MILLIS*/) {
// we've been idle for long enough
logger.log(Level.INFO, "Disconnecting computer {0} as it has been idle for {1}",
new Object[]{c.getName(), Util.getTimeSpanString(idleMilliseconds)});
c.disconnect(OfflineCause.create(Messages._RetentionStrategy_Demand_OfflineIdle()));
}
}
}
});
}
}
return 1;
......
/*
* The MIT License
*
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
*
*
* 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
......@@ -28,6 +28,7 @@ import hudson.Extension;
import static hudson.Util.fixNull;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.Queue;
import hudson.scheduler.CronTabList;
import hudson.util.FormValidation;
import org.kohsuke.stapler.DataBoundConstructor;
......@@ -43,7 +44,7 @@ import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
/**
* {@link RetentionStrategy} that controls the slave based on a schedule.
* {@link RetentionStrategy} that controls the slave based on a schedule.
*
* @author Stephen Connolly
* @since 1.275
......@@ -198,15 +199,34 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom
new Object[]{c.getName()});
return 1;
} else if (c.isIdle() && c.isAcceptingTasks()) {
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
c.disconnect(OfflineCause.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
Queue.withLock(new Runnable() {
@Override
public void run() {
if (c.isIdle()) {
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
c.disconnect(OfflineCause
.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
} else {
c.setAcceptingTasks(false);
}
}
});
} else if (c.isIdle() && !c.isAcceptingTasks()) {
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished all jobs running when "
+ "it completed its scheduled uptime", new Object[]{c.getName()});
c.disconnect(OfflineCause.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
Queue.withLock(new Runnable() {
@Override
public void run() {
if (c.isIdle()) {
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished all jobs running when "
+ "it completed its scheduled uptime", new Object[]{c.getName()});
c.disconnect(OfflineCause
.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
}
}
});
}
} else {
// no need to get the queue lock as the user has selected the break builds option!
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
c.disconnect(OfflineCause.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
......
......@@ -382,7 +382,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* @since 1.534
*/
private volatile boolean disableRememberMe;
/**
* The project naming strategy defines/restricts the names which can be given to a project/job. e.g. does the name have to follow a naming convention?
*/
......@@ -474,7 +474,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* Active {@link Cloud}s.
*/
public final Hudson.CloudList clouds = new Hudson.CloudList(this);
public static class CloudList extends DescribableList<Cloud,Descriptor<Cloud>> {
public CloudList(Jenkins h) {
super(h);
......@@ -592,7 +592,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* Load statistics of the free roaming jobs and slaves.
*
*
* This includes all executors on {@link hudson.model.Node.Mode#NORMAL} nodes and jobs that do not have any assigned nodes.
*
* @since 1.467
......@@ -685,7 +685,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* Gets the {@link Jenkins} singleton.
* {@link #getInstance()} provides the unchecked versions of the method.
* {@link #getInstance()} provides the unchecked versions of the method.
* @return {@link Jenkins} instance
* @throws IllegalStateException {@link Jenkins} has not been started, or was already shut down
* @since 1.590
......@@ -697,11 +697,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
return instance;
}
/**
* Gets the {@link Jenkins} singleton.
* {@link #getActiveInstance()} provides the checked versions of the method.
* @return The instance. Null if the {@link Jenkins} instance has not been started,
* {@link #getActiveInstance()} provides the checked versions of the method.
* @return The instance. Null if the {@link Jenkins} instance has not been started,
* or was already shut down
*/
@CLIResolver
......@@ -748,7 +748,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
})
protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
long start = System.currentTimeMillis();
// As Jenkins is starting, grant this process full control
ACL.impersonate(ACL.SYSTEM);
try {
......@@ -867,7 +867,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
LOGGER.info(String.format("Took %dms for item listener %s startup",
System.currentTimeMillis()-itemListenerStart,l.getClass().getName()));
}
if (LOG_STARTUP_PERFORMANCE)
LOGGER.info(String.format("Took %dms for complete Jenkins startup",
System.currentTimeMillis()-start));
......@@ -1369,7 +1369,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
if (item.hasPermission(Item.READ))
viewableItems.add(item);
}
return viewableItems;
}
......@@ -1709,9 +1709,14 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
setNodes(nl);
}
public void setNodes(List<? extends Node> nodes) throws IOException {
this.slaves = new NodeList(nodes);
updateComputerList();
public void setNodes(final List<? extends Node> nodes) throws IOException {
Queue.withLock(new Runnable() {
@Override
public void run() {
Jenkins.this.slaves = new NodeList(nodes);
updateComputerList();
}
});
trimLabels();
save();
}
......@@ -2002,7 +2007,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
return workspace;
}
}
return new FilePath(expandVariablesForDirectory(workspaceDir, item));
}
......@@ -2023,7 +2028,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
"ITEM_FULL_NAME", itemFullName.replace(':','$'))); // safe, see JENKINS-12251
}
public String getRawWorkspaceDir() {
return workspaceDir;
}
......@@ -2076,7 +2081,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public boolean isUseSecurity() {
return securityRealm!=SecurityRealm.NO_AUTHENTICATION || authorizationStrategy!=AuthorizationStrategy.UNSECURED;
}
public boolean isUseProjectNamingStrategy(){
return projectNamingStrategy != DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
}
......@@ -2169,7 +2174,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
projectNamingStrategy = ns;
}
public Lifecycle getLifecycle() {
return Lifecycle.get();
}
......@@ -2279,7 +2284,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public AuthorizationStrategy getAuthorizationStrategy() {
return authorizationStrategy;
}
/**
* The strategy used to check the project names.
* @return never <code>null</code>
......@@ -2620,7 +2625,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
throw new IOException(projectsDir+" is not a directory");
throw new IOException("Unable to create "+projectsDir+"\nPermission issue? Please create this directory manually.");
}
File[] subdirs = projectsDir.listFiles();
File[] subdirs = projectsDir.listFiles();
final Set<String> loadedNames = Collections.synchronizedSet(new HashSet<String>());
......@@ -2630,7 +2635,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
// JENKINS-8043: some slaves (eg. swarm slaves) are not saved into the config file
// and will get overwritten when reloading. Make a backup copy now, and re-add them later
NodeList oldSlaves = slaves;
XmlFile cfg = getConfigFile();
if (cfg.exists()) {
// reset some data that may not exist in the disk file
......@@ -2990,9 +2995,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public HttpResponse doToggleCollapse() throws ServletException, IOException {
final StaplerRequest request = Stapler.getCurrentRequest();
final String paneId = request.getParameter("paneId");
PaneStatusProperties.forCurrentUser().toggleCollapsed(paneId);
return HttpResponses.forwardToPreviousPage();
}
......@@ -3084,9 +3089,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
if(name==null || name.length()==0)
throw new Failure(Messages.Hudson_NoName());
if(".".equals(name.trim()))
if(".".equals(name.trim()))
throw new Failure(Messages.Jenkins_NotAllowedName("."));
if("..".equals(name.trim()))
if("..".equals(name.trim()))
throw new Failure(Messages.Jenkins_NotAllowedName(".."));
for( int i=0; i<name.length(); i++ ) {
char ch = name.charAt(i);
......@@ -3655,20 +3660,20 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
/**
* Checks if a top-level view with the given name exists and
* Checks if a top-level view with the given name exists and
* make sure that the name is good as a view name.
*/
public FormValidation doCheckViewName(@QueryParameter String value) {
checkPermission(View.CREATE);
String name = fixEmpty(value);
if (name == null)
if (name == null)
return FormValidation.ok();
// already exists?
if (getView(name) != null)
if (getView(name) != null)
return FormValidation.error(Messages.Hudson_ViewAlreadyExists(name));
// good view name?
try {
checkGoodName(name);
......@@ -3678,7 +3683,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
return FormValidation.ok();
}
/**
* Checks if a top-level view with the given name exists.
* @deprecated 1.512
......@@ -3757,7 +3762,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public void rebuildDependencyGraph() {
DependencyGraph graph = new DependencyGraph();
graph.build();
// volatile acts a as a memory barrier here and therefore guarantees
// volatile acts a as a memory barrier here and therefore guarantees
// that graph is fully build, before it's visible to other threads
dependencyGraph = graph;
dependencyGraphDirty.set(false);
......@@ -3872,22 +3877,22 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
/**
* This method checks all existing jobs to see if displayName is
* This method checks all existing jobs to see if displayName is
* unique. It does not check the displayName against the displayName of the
* job that the user is configuring though to prevent a validation warning
* job that the user is configuring though to prevent a validation warning
* if the user sets the displayName to what it currently is.
* @param displayName
* @param currentJobName
*/
boolean isDisplayNameUnique(String displayName, String currentJobName) {
Collection<TopLevelItem> itemCollection = items.values();
// if there are a lot of projects, we'll have to store their
// if there are a lot of projects, we'll have to store their
// display names in a HashSet or something for a quick check
for(TopLevelItem item : itemCollection) {
if(item.getName().equals(currentJobName)) {
// we won't compare the candidate displayName against the current
// item. This is to prevent an validation warning if the user
// item. This is to prevent an validation warning if the user
// sets the displayName to what the existing display name is
continue;
}
......@@ -3895,10 +3900,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
return false;
}
}
return true;
}
/**
* True if there is no item in Jenkins that has this name
* @param name The name to test
......@@ -3906,7 +3911,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
boolean isNameUnique(String name, String currentJobName) {
Item item = getItem(name);
if(null==item) {
// the candidate name didn't return any items so the name is unique
return true;
......@@ -3915,27 +3920,27 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
// the candidate name returned an item, but the item is the item
// that the user is configuring so this is ok
return true;
}
}
else {
// the candidate name returned an item, so it is not unique
return false;
}
}
/**
* Checks to see if the candidate displayName collides with any
* Checks to see if the candidate displayName collides with any
* existing display names or project names
* @param displayName The display name to test
* @param jobName The name of the job the user is configuring
*/
public FormValidation doCheckDisplayName(@QueryParameter String displayName,
public FormValidation doCheckDisplayName(@QueryParameter String displayName,
@QueryParameter String jobName) {
displayName = displayName.trim();
if(LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Current job name is " + jobName);
}
if(!isNameUnique(displayName, jobName)) {
return FormValidation.warning(Messages.Jenkins_CheckDisplayName_NameNotUniqueWarning(displayName));
}
......@@ -3946,7 +3951,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
return FormValidation.ok();
}
}
public static class MasterComputer extends Computer {
protected MasterComputer() {
super(Jenkins.getInstance());
......@@ -4152,7 +4157,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* Unique random token that identifies the current session.
* Used to make {@link #RESOURCE_PATH} unique so that we can set long "Expires" header.
*
*
* We used to use {@link #VERSION_HASH}, but making this session local allows us to
* reuse the same {@link #RESOURCE_PATH} for static resources in plugins.
*/
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册