@@ -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