in server/src/jetbrains/buildServer/sharedResources/server/SharedResourcesStartBuildPrecondition.java [100:203]
public WaitReason canStart(@NotNull QueuedBuildInfo queuedBuildInfo,
@NotNull Map<QueuedBuildInfo, BuildAgent> canBeStarted,
@NotNull BuildDistributorInput buildDistributorInput,
boolean isEmulationMode) {
final DistributionDataAccessor accessor = new DistributionDataAccessor(buildDistributorInput);
Supplier<Map<Resource, TakenLock>> takenLocksSupplier = new Lazy<Map<Resource, TakenLock>>() {
@Override
protected Map<Resource, TakenLock> createValue() {
return myTakenLocks.collectTakenLocks(myRunningBuildsManager.getRunningBuildsEx(), canBeStarted.keySet());
}
};
CachingProjectResourcesMap resourcesMap = new CachingProjectResourcesMap(myResources);
// get or create our collection of resources
WaitReason reason = null;
final BuildPromotionEx myPromotion = (BuildPromotionEx)queuedBuildInfo.getBuildPromotionInfo();
// we're preserving the values of distributed builds only, if our filter allowed some previous build
// to start and reserved a value for it, there can be another filter for the same build which actually
// prevented it from starting; by doing this cleanup we're removing reserved values of builds which could not start
final ReservedValuesProvider reservedValuesProvider = accessor.getReservedValuesProvider();
reservedValuesProvider.cleanupValuesReservedByObsoleteBuilds(() -> canBeStarted.keySet().stream()
.map(QueuedBuildInfo::getBuildPromotionInfo)
.map(BuildPromotionInfo::getId)
.collect(Collectors.toSet()));
if (TeamCityProperties.getBooleanOrTrue(SharedResourcesPluginConstants.RESOURCES_IN_CHAINS_ENABLED) && myPromotion.isPartOfBuildChain()) {
LOG.debug("Queued build is part of build chain");
final List<BuildPromotionEx> depPromos = myPromotion.getDependentCompositePromotions();
if (depPromos.isEmpty()) {
LOG.debug("Queued build does not have dependent composite promotions");
reason = processSingleBuild(myPromotion, accessor, takenLocksSupplier, myPromotion, isEmulationMode);
} else {
LOG.debug("Queued build does have " + depPromos.size() + " dependent composite " + StringUtil.pluralize("promotion", depPromos.size()));
// contains resources and locks that are INSIDE the build chain
final Map<Resource, Map<BuildPromotionEx, Lock>> chainLocks = new HashMap<>(); // resource -> {promotion -> lock}
// first - get top of the chain. Builds that are already running.
// they have locks already taken
depPromos.forEach(promo -> {
if (myLocksStorage.locksStored(promo)) {
final BuildTypeEx buildType = promo.getBuildType();
if (buildType == null) return;
LOG.debug("build promotion" + promo.getId() + " is running. Loading locks");
final Map<String, Lock> currentNodeLocks = myLocksStorage.load(promo);
if (!currentNodeLocks.isEmpty()) {
// if there are locks - resolve locks against resources according to project hierarchy of composite build
resolve(chainLocks, resourcesMap.getResourcesMap(promo.getBuildType().getProject()), promo, currentNodeLocks);
}
}
});
// rest are queued builds.
// make sure queued builds can start.
// builds inside composite build chain are not affected by the locks taken in the same chain
final List<SQueuedBuild> queued = depPromos.stream()
.map(BuildPromotion::getQueuedBuild)
.filter(Objects::nonNull)
.collect(Collectors.toList());
for (SQueuedBuild compositeQueuedBuild : queued) {
final BuildPromotion compositeBp = compositeQueuedBuild.getBuildPromotion();
SBuildType compositeBuildType = compositeBp.getBuildType();
if (compositeBuildType == null) continue;
final Collection<SharedResourcesFeature> features = myFeatures.searchForFeatures(compositeBp);
if (!features.isEmpty()) {
final Map<String, Lock> locksToTake = myLocks.fromBuildFeaturesAsMap(features);
if (!locksToTake.isEmpty()) {
// resolve locks that build wants to take against actual resources
reason = processBuildInChain(accessor, takenLocksSupplier,
resourcesMap.getResourcesMap(compositeBuildType.getProject()),
chainLocks, locksToTake, compositeBp, isEmulationMode);
if (reason != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Preventing start of the queued build [" + compositeQueuedBuild + "] with reason: [" + reason.getDescription() + "]");
}
break;
}
}
}
}
// process build itself
if (reason == null) {
final BuildTypeEx promoBuildType = myPromotion.getBuildType();
if (promoBuildType != null) {
final Collection<SharedResourcesFeature> features = myFeatures.searchForFeatures(myPromotion);
if (!features.isEmpty()) {
reason = checkForInvalidLocks(myPromotion);
}
final Map<String, Lock> locksToTake = myLocks.fromBuildFeaturesAsMap(features);
if (!locksToTake.isEmpty()) {
reason = processBuildInChain(accessor, takenLocksSupplier, resourcesMap.getResourcesMap(promoBuildType.getProject()), chainLocks, locksToTake, myPromotion, isEmulationMode);
}
}
}
}
} else {
reason = processSingleBuild(myPromotion, accessor, takenLocksSupplier, myPromotion, isEmulationMode);
}
return reason;
}