Skip to content

Commit b310d92

Browse files
Mark Armstrongsb-abhish3k
authored andcommitted
fix domain lock set to reflect tag->untagged fallback and limit inheritance
listRowsToLockForLimitCheck previously only locked ancestor domains that owned an explicit finite resource_limit row for the exact tag, so it missed domains that inherit a finite limit from an ancestor's row, and domains that fall back to the untagged limit when no tag-specific limit is configured (per findCorrectResourceLimitForDomain). Either gap let concurrent reservations bypass domain-level serialization. Replace the set-based approximation with an in-memory walk of the account's domain chain that replicates findCorrectResourceLimitForDomain's nearest-row-with-fallback semantics, using bulk queries (covered by existing indexes) to fetch the chain's resource_limit rows and to resolve the final resource_count rows to lock.
1 parent 27b93b0 commit b310d92

4 files changed

Lines changed: 264 additions & 61 deletions

File tree

engine/schema/src/main/java/com/cloud/configuration/dao/ResourceLimitDao.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ public interface ResourceLimitDao extends GenericDao<ResourceLimitVO, Long> {
3939
void removeResourceLimitsForNonMatchingTags(Long ownerId, ResourceOwnerType ownerType, List<Resource.ResourceType> types, List<String> tags);
4040

4141
/**
42-
* Returns the subset of {@code domainIds} that have an explicit
43-
* {@code resource_limit} row whose {@code max} is not
44-
* {@link Resource#RESOURCE_UNLIMITED} for the supplied
45-
* ({@code type}, {@code tag}). Domains that rely on the global default
46-
* are NOT returned — the caller checks
47-
* {@code findDefaultResourceLimitForDomain} separately.
42+
* Returns the explicit {@code resource_limit} rows owned by any of
43+
* {@code domainIds} for the supplied ({@code type}, {@code tag}),
44+
* regardless of their {@code max} value. Domains with no matching row
45+
* are simply absent from the result — the caller resolves inheritance
46+
* (nearest-ancestor lookup) and the tag -&gt; untagged fallback itself,
47+
* mirroring {@code ResourceLimitManagerImpl#findCorrectResourceLimitForDomain}.
4848
*/
49-
Set<Long> listDomainIdsWithFiniteLimit(Set<Long> domainIds, Resource.ResourceType type, String tag);
49+
List<ResourceLimitVO> listByDomainIdsAndTypeAndTag(Set<Long> domainIds, Resource.ResourceType type, String tag);
5050
}

engine/schema/src/main/java/com/cloud/configuration/dao/ResourceLimitDaoImpl.java

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717
package com.cloud.configuration.dao;
1818

1919
import java.util.ArrayList;
20-
import java.util.Collections;
2120
import java.util.List;
2221
import java.util.Set;
23-
import java.util.stream.Collectors;
2422

2523

2624
import org.apache.commons.collections.CollectionUtils;
@@ -41,8 +39,8 @@ public class ResourceLimitDaoImpl extends GenericDaoBase<ResourceLimitVO, Long>
4139
private SearchBuilder<ResourceLimitVO> IdTypeTagSearch;
4240
private SearchBuilder<ResourceLimitVO> IdTypeNullTagSearch;
4341
private SearchBuilder<ResourceLimitVO> NonMatchingTagsSearch;
44-
private SearchBuilder<ResourceLimitVO> DomainsFiniteLimitTagSearch;
45-
private SearchBuilder<ResourceLimitVO> DomainsFiniteLimitNullTagSearch;
42+
private SearchBuilder<ResourceLimitVO> DomainsLimitTagSearch;
43+
private SearchBuilder<ResourceLimitVO> DomainsLimitNullTagSearch;
4644

4745
public ResourceLimitDaoImpl() {
4846
IdTypeTagSearch = createSearchBuilder();
@@ -66,19 +64,17 @@ public ResourceLimitDaoImpl() {
6664
NonMatchingTagsSearch.and("tags", NonMatchingTagsSearch.entity().getTag(), SearchCriteria.Op.NIN);
6765
NonMatchingTagsSearch.done();
6866

69-
DomainsFiniteLimitTagSearch = createSearchBuilder();
70-
DomainsFiniteLimitTagSearch.and("type", DomainsFiniteLimitTagSearch.entity().getType(), SearchCriteria.Op.EQ);
71-
DomainsFiniteLimitTagSearch.and("domainIds", DomainsFiniteLimitTagSearch.entity().getDomainId(), SearchCriteria.Op.IN);
72-
DomainsFiniteLimitTagSearch.and("tag", DomainsFiniteLimitTagSearch.entity().getTag(), SearchCriteria.Op.EQ);
73-
DomainsFiniteLimitTagSearch.and("max", DomainsFiniteLimitTagSearch.entity().getMax(), SearchCriteria.Op.NEQ);
74-
DomainsFiniteLimitTagSearch.done();
75-
76-
DomainsFiniteLimitNullTagSearch = createSearchBuilder();
77-
DomainsFiniteLimitNullTagSearch.and("type", DomainsFiniteLimitNullTagSearch.entity().getType(), SearchCriteria.Op.EQ);
78-
DomainsFiniteLimitNullTagSearch.and("domainIds", DomainsFiniteLimitNullTagSearch.entity().getDomainId(), SearchCriteria.Op.IN);
79-
DomainsFiniteLimitNullTagSearch.and("tag", DomainsFiniteLimitNullTagSearch.entity().getTag(), SearchCriteria.Op.NULL);
80-
DomainsFiniteLimitNullTagSearch.and("max", DomainsFiniteLimitNullTagSearch.entity().getMax(), SearchCriteria.Op.NEQ);
81-
DomainsFiniteLimitNullTagSearch.done();
67+
DomainsLimitTagSearch = createSearchBuilder();
68+
DomainsLimitTagSearch.and("type", DomainsLimitTagSearch.entity().getType(), SearchCriteria.Op.EQ);
69+
DomainsLimitTagSearch.and("domainIds", DomainsLimitTagSearch.entity().getDomainId(), SearchCriteria.Op.IN);
70+
DomainsLimitTagSearch.and("tag", DomainsLimitTagSearch.entity().getTag(), SearchCriteria.Op.EQ);
71+
DomainsLimitTagSearch.done();
72+
73+
DomainsLimitNullTagSearch = createSearchBuilder();
74+
DomainsLimitNullTagSearch.and("type", DomainsLimitNullTagSearch.entity().getType(), SearchCriteria.Op.EQ);
75+
DomainsLimitNullTagSearch.and("domainIds", DomainsLimitNullTagSearch.entity().getDomainId(), SearchCriteria.Op.IN);
76+
DomainsLimitNullTagSearch.and("tag", DomainsLimitNullTagSearch.entity().getTag(), SearchCriteria.Op.NULL);
77+
DomainsLimitNullTagSearch.done();
8278
}
8379

8480
@Override
@@ -171,21 +167,18 @@ public void removeResourceLimitsForNonMatchingTags(Long ownerId, ResourceOwnerTy
171167
}
172168

173169
@Override
174-
public Set<Long> listDomainIdsWithFiniteLimit(Set<Long> domainIds, ResourceType type, String tag) {
170+
public List<ResourceLimitVO> listByDomainIdsAndTypeAndTag(Set<Long> domainIds, ResourceType type, String tag) {
175171
if (CollectionUtils.isEmpty(domainIds)) {
176-
return Collections.emptySet();
172+
return new ArrayList<>();
177173
}
178174
SearchCriteria<ResourceLimitVO> sc = (tag != null)
179-
? DomainsFiniteLimitTagSearch.create()
180-
: DomainsFiniteLimitNullTagSearch.create();
175+
? DomainsLimitTagSearch.create()
176+
: DomainsLimitNullTagSearch.create();
181177
sc.setParameters("type", type);
182178
sc.setParameters("domainIds", domainIds.toArray());
183179
if (tag != null) {
184180
sc.setParameters("tag", tag);
185181
}
186-
sc.setParameters("max", (long) Resource.RESOURCE_UNLIMITED);
187-
return listBy(sc).stream()
188-
.map(ResourceLimitVO::getDomainId)
189-
.collect(Collectors.toSet());
182+
return listBy(sc);
190183
}
191184
}

server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ protected List<ResourceCountVO> lockAccountAndOwnerDomainRows(long accountId, fi
591591
* Returns the {@code resource_count} row IDs that need {@code FOR UPDATE}
592592
* locks for a limit check: always the account row, plus ancestor-domain
593593
* rows ONLY when their effective limit for ({@code type}, {@code tag})
594-
* is finite. Ancestors with an UNLIMITED (or absent) explicit limit are
594+
* is finite. Ancestors with an UNLIMITED (or absent) effective limit are
595595
* excluded — locking them would only create cross-tenant InnoDB row-lock
596596
* contention with no effect on the check outcome (see
597597
* {@link #checkDomainResourceLimit} which short-circuits when the
@@ -601,6 +601,25 @@ protected List<ResourceCountVO> lockAccountAndOwnerDomainRows(long accountId, fi
601601
* <p>The account row is always included so two concurrent reservations
602602
* against the same account still serialize at the InnoDB level.
603603
*
604+
* <p>"Effective limit" is resolved exactly like
605+
* {@link #findCorrectResourceLimitForDomain}: a bulk query fetches every
606+
* explicit tag-specific {@code resource_limit} row owned by any domain
607+
* in the chain (a second bulk query for untagged rows only runs if some
608+
* domain in the chain turns out to have no tag-specific row of its own
609+
* or an ancestor's), then the chain is walked in memory from the
610+
* account's domain up to (but excluding) ROOT, taking the nearest
611+
* tag-specific row and falling back to the nearest untagged row only
612+
* when no tag-specific row exists anywhere in the domain's own upward
613+
* path. This correctly locks domains that inherit a finite limit from
614+
* an ancestor's row (not just domains that own one), and domains that
615+
* fall back to a finite untagged limit because no tag-specific limit is
616+
* configured.
617+
*
618+
* <p>The final domain-row lookup is a single bulk
619+
* {@link ResourceCountDao#findByOwnersAndTypeAndTag} call over exactly
620+
* the domains found to have a finite effective limit — no more, no
621+
* fewer, and no per-domain round trips.
622+
*
604623
* <p>If the global default for this type is itself finite (only possible
605624
* for {@code primary_storage}/{@code secondary_storage} via
606625
* {@code domainResourceLimitMap}), every ancestor inherits that finite
@@ -634,18 +653,87 @@ protected Set<Long> listRowsToLockForLimitCheck(long accountId, ResourceType typ
634653
if (account == null) {
635654
return rowIds;
636655
}
637-
Set<Long> ancestorDomainIds = _domainDao.getDomainParentIds(account.getDomainId());
638-
Set<Long> finiteLimitDomainIds = _resourceLimitDao.listDomainIdsWithFiniteLimit(ancestorDomainIds, type, tag);
639656

640-
for (Long ancestorDomainId : finiteLimitDomainIds) {
641-
ResourceCountVO row = _resourceCountDao.findByOwnerAndTypeAndTag(ancestorDomainId, ResourceOwnerType.Domain, type, tag);
642-
if (row != null) {
657+
List<Long> ancestorChain = getOrderedAncestorDomainIds(account.getDomainId());
658+
Set<Long> finiteLimitDomainIds = findAncestorDomainsWithFiniteLimit(ancestorChain, type, tag);
659+
if (!finiteLimitDomainIds.isEmpty()) {
660+
List<ResourceCountVO> domainRows = _resourceCountDao.findByOwnersAndTypeAndTag(
661+
new ArrayList<>(finiteLimitDomainIds), ResourceOwnerType.Domain, type, tag);
662+
for (ResourceCountVO row : domainRows) {
643663
rowIds.add(row.getId());
644664
}
645665
}
666+
646667
return rowIds;
647668
}
648669

670+
/**
671+
* Returns the account's domain plus every ancestor up to (but excluding)
672+
* ROOT, ordered from the account's own domain outward — the same order
673+
* {@link #findCorrectResourceLimitForDomain} and
674+
* {@link #checkDomainResourceLimit} walk.
675+
*/
676+
private List<Long> getOrderedAncestorDomainIds(long domainId) {
677+
List<Long> chain = new ArrayList<>();
678+
Long currentId = domainId;
679+
while (currentId != null && currentId != Domain.ROOT_DOMAIN) {
680+
chain.add(currentId);
681+
DomainVO domain = _domainDao.findById(currentId);
682+
currentId = (domain != null) ? domain.getParent() : null;
683+
}
684+
return chain;
685+
}
686+
687+
/**
688+
* Returns the subset of {@code ancestorChain} whose effective limit for
689+
* ({@code type}, {@code tag}) is finite, per
690+
* {@link #findCorrectResourceLimitForDomain}'s nearest-row-with-fallback
691+
* semantics. The untagged fallback query only runs if at least one
692+
* domain in the chain actually lacks a tag-specific row.
693+
*/
694+
private Set<Long> findAncestorDomainsWithFiniteLimit(List<Long> ancestorChain, ResourceType type, String tag) {
695+
if (ancestorChain.isEmpty()) {
696+
return Collections.emptySet();
697+
}
698+
699+
Set<Long> chainSet = new HashSet<>(ancestorChain);
700+
Map<Long, ResourceLimitVO> tagRowsByDomain = indexByDomainId(_resourceLimitDao.listByDomainIdsAndTypeAndTag(chainSet, type, tag));
701+
Map<Long, ResourceLimitVO> untaggedRowsByDomain = null;
702+
703+
Set<Long> finiteLimitDomainIds = new HashSet<>();
704+
for (int i = 0; i < ancestorChain.size(); i++) {
705+
ResourceLimitVO nearestRow = findNearestRowFrom(ancestorChain, i, tagRowsByDomain);
706+
if (nearestRow == null && StringUtils.isNotEmpty(tag)) {
707+
if (untaggedRowsByDomain == null) {
708+
untaggedRowsByDomain = indexByDomainId(_resourceLimitDao.listByDomainIdsAndTypeAndTag(chainSet, type, null));
709+
}
710+
nearestRow = findNearestRowFrom(ancestorChain, i, untaggedRowsByDomain);
711+
}
712+
if (nearestRow != null && nearestRow.getMax().longValue() != Resource.RESOURCE_UNLIMITED) {
713+
finiteLimitDomainIds.add(ancestorChain.get(i));
714+
}
715+
}
716+
return finiteLimitDomainIds;
717+
}
718+
719+
private static ResourceLimitVO findNearestRowFrom(List<Long> chain, int startIndex, Map<Long, ResourceLimitVO> rowsByDomain) {
720+
for (int i = startIndex; i < chain.size(); i++) {
721+
ResourceLimitVO row = rowsByDomain.get(chain.get(i));
722+
if (row != null) {
723+
return row;
724+
}
725+
}
726+
return null;
727+
}
728+
729+
private static Map<Long, ResourceLimitVO> indexByDomainId(List<ResourceLimitVO> rows) {
730+
Map<Long, ResourceLimitVO> map = new HashMap<>();
731+
for (ResourceLimitVO row : rows) {
732+
map.put(row.getDomainId(), row);
733+
}
734+
return map;
735+
}
736+
649737
@Override
650738
public long findDefaultResourceLimitForDomain(ResourceType resourceType) {
651739
Long resourceLimit;

0 commit comments

Comments
 (0)