in core/src/main/java/hudson/slaves/NodeProvisioner.java [206:347]
private void update() {
long start = LOGGER.isLoggable(Level.FINER) ? System.nanoTime() : 0;
provisioningLock.lock();
try {
lastSuggestedReview = System.currentTimeMillis();
queuedReview = false;
// We need to get the lock on Queue for two reasons:
// 1. We will potentially adding a lot of nodes and we don't want to fight with Queue#maintain to acquire
// the Queue#lock in order to add each node. Much better is to hold the Queue#lock until all nodes
// that were provisioned since last we checked have been added.
// 2. We want to know the idle executors count, which can only be measured if you hold the Queue#lock
// Strictly speaking we don't need an accurate measure for this, but as we had to get the Queue#lock
// anyway, we might as well get an accurate measure.
//
// We do not need the Queue#lock to get the count of items in the queue as that is a lock-free call
// Since adding a node should not (in principle) confuse Queue#maintain (it is only removal of nodes
// that causes issues in Queue#maintain) we should be able to remove the need for Queue#lock
//
// TODO once Nodes#addNode is made lock free, we should be able to remove the requirement for Queue#lock
Queue.withLock(() -> {
Jenkins jenkins = Jenkins.get();
// clean up the cancelled launch activity, then count the # of executors that we are about to
// bring up.
int plannedCapacitySnapshot = 0;
List<PlannedNode> snapPendingLaunches = new ArrayList<>(pendingLaunches.get());
for (PlannedNode f : snapPendingLaunches) {
if (f.future.isDone()) {
try {
Node node = null;
try {
node = f.future.get();
} catch (InterruptedException e) {
throw new AssertionError("InterruptedException occurred", e); // since we confirmed that the future is already done
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (!(cause instanceof AbortException)) {
LOGGER.log(Level.WARNING,
"Unexpected exception encountered while provisioning agent "
+ f.displayName,
cause);
}
fireOnFailure(f, cause);
}
if (node != null) {
fireOnComplete(f, node);
try {
jenkins.addNode(node);
LOGGER.log(Level.INFO,
"{0} provisioning successfully completed. "
+ "We have now {1,number,integer} computer(s)",
new Object[]{f.displayName, jenkins.getComputers().length});
fireOnCommit(f, node);
} catch (IOException e) {
LOGGER.log(Level.WARNING,
"Provisioned agent " + f.displayName + " failed to launch",
e);
fireOnRollback(f, node, e);
}
}
} catch (Error e) {
// we are not supposed to try and recover from Errors
throw e;
} catch (Throwable e) {
// Just log it
LOGGER.log(Level.SEVERE,
"Unexpected uncaught exception encountered while processing agent "
+ f.displayName,
e);
} finally {
while (true) {
List<PlannedNode> orig = pendingLaunches.get();
List<PlannedNode> repl = new ArrayList<>(orig);
// the contract for List.remove(o) is that the first element i where
// (o==null ? get(i)==null : o.equals(get(i)))
// is true will be removed from the list
// since PlannedNode.equals(o) is not final and we cannot assume
// that subclasses do not override with an equals which does not
// assure object identity comparison, we need to manually
// do the removal based on instance identity not equality
boolean changed = false;
for (Iterator<PlannedNode> iterator = repl.iterator(); iterator.hasNext(); ) {
PlannedNode p = iterator.next();
if (p == f) {
iterator.remove();
changed = true;
break;
}
}
if (!changed || pendingLaunches.compareAndSet(orig, repl)) {
break;
}
}
f.spent();
}
} else {
plannedCapacitySnapshot += f.numExecutors;
}
}
float plannedCapacity = plannedCapacitySnapshot;
plannedCapacitiesEMA.update(plannedCapacity);
final LoadStatistics.LoadStatisticsSnapshot snapshot = stat.computeSnapshot();
int availableSnapshot = snapshot.getAvailableExecutors();
int queueLengthSnapshot = snapshot.getQueueLength();
if (queueLengthSnapshot <= availableSnapshot) {
LOGGER.log(Level.FINER,
"Queue length {0} is less than the available capacity {1}. No provisioning strategy required",
new Object[]{queueLengthSnapshot, availableSnapshot});
provisioningState = null;
} else {
provisioningState = new StrategyState(snapshot, label, plannedCapacitySnapshot);
}
});
if (provisioningState != null) {
List<Strategy> strategies = Jenkins.get().getExtensionList(Strategy.class);
for (Strategy strategy : strategies.isEmpty()
? Collections.<Strategy>singletonList(new StandardStrategyImpl())
: strategies) {
LOGGER.log(Level.FINER, "Consulting {0} provisioning strategy with state {1}",
new Object[]{strategy, provisioningState});
if (StrategyDecision.PROVISIONING_COMPLETED == strategy.apply(provisioningState)) {
LOGGER.log(Level.FINER, "Provisioning strategy {0} declared provisioning complete",
strategy);
break;
}
}
}
} finally {
provisioningLock.unlock();
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer(() -> "ran update on " + label + " in " + (System.nanoTime() - start) / 1_000_000 + "ms");
}
}