This is an automated email from the ASF dual-hosted git repository.

joerghoh pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-distribution-core.git


The following commit(s) were added to refs/heads/master by this push:
     new c99143ab SLING-13329 : Cap Distribution Queue status count at 1,000 
items
c99143ab is described below

commit c99143ab4fe45bbcbcafddb27251609e8e10dda4
Author: aamirbhat <[email protected]>
AuthorDate: Fri Nov 13 18:12:14 2026 +0530

    SLING-13329 : Cap Distribution Queue status count at 1,000 items
    
    
    Co-authored-by: aamirh <[email protected]>
---
 .../queue/DistributionQueueStatus.java             | 16 +++++
 .../queue/impl/SimpleAgentDistributionQueue.java   |  3 +-
 .../queue/impl/resource/ActiveResourceQueue.java   | 24 +++++--
 .../queue/impl/resource/ResourceQueue.java         | 23 ++++++-
 .../queue/impl/resource/ResourceQueueProvider.java | 17 ++++-
 .../resource/ResourceQueueProviderFactory.java     | 13 +++-
 .../queue/impl/resource/ResourceQueueUtils.java    | 52 +++++++++++++++
 ...xtendedDistributionServiceResourceProvider.java |  1 +
 .../impl/resource/ResourceQueueUtilsTest.java      | 75 ++++++++++++++++++++++
 9 files changed, 214 insertions(+), 10 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/distribution/queue/DistributionQueueStatus.java
 
b/src/main/java/org/apache/sling/distribution/queue/DistributionQueueStatus.java
index 7f59df7d..bb8b9390 100644
--- 
a/src/main/java/org/apache/sling/distribution/queue/DistributionQueueStatus.java
+++ 
b/src/main/java/org/apache/sling/distribution/queue/DistributionQueueStatus.java
@@ -27,11 +27,19 @@ import org.jetbrains.annotations.NotNull;
 public final class DistributionQueueStatus {
     private final int itemsCount;
 
+    private final boolean itemsCountCapped;
+
     private final DistributionQueueState queueState;
 
     public DistributionQueueStatus(int itemsCount, @NotNull 
DistributionQueueState queueState) {
+        this(itemsCount, queueState, false);
+    }
+
+    public DistributionQueueStatus(
+            int itemsCount, @NotNull DistributionQueueState queueState, 
boolean itemsCountCapped) {
         this.itemsCount = itemsCount;
         this.queueState = queueState;
+        this.itemsCountCapped = itemsCountCapped;
     }
 
     /**
@@ -42,6 +50,14 @@ public final class DistributionQueueStatus {
         return itemsCount;
     }
 
+    /**
+     * Indicates whether the reported item count is capped rather than exact.
+     * @return {@code true} if the reported item count is capped
+     */
+    public boolean isItemsCountCapped() {
+        return itemsCountCapped;
+    }
+
     /**
      * get the state associated with the status of this queue
      * @return a {@link DistributionQueueItemState}
diff --git 
a/src/main/java/org/apache/sling/distribution/queue/impl/SimpleAgentDistributionQueue.java
 
b/src/main/java/org/apache/sling/distribution/queue/impl/SimpleAgentDistributionQueue.java
index c297c8d3..6f6418ea 100644
--- 
a/src/main/java/org/apache/sling/distribution/queue/impl/SimpleAgentDistributionQueue.java
+++ 
b/src/main/java/org/apache/sling/distribution/queue/impl/SimpleAgentDistributionQueue.java
@@ -54,7 +54,8 @@ public class SimpleAgentDistributionQueue extends 
DistributionQueueWrapper {
     private DistributionQueueStatus calculateStatus() {
         DistributionQueueStatus status = super.getStatus();
         if (isPaused) {
-            return new DistributionQueueStatus(status.getItemsCount(), 
DistributionQueueState.PAUSED);
+            return new DistributionQueueStatus(
+                    status.getItemsCount(), DistributionQueueState.PAUSED, 
status.isItemsCountCapped());
         } else {
             return status;
         }
diff --git 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ActiveResourceQueue.java
 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ActiveResourceQueue.java
index bb8fe5b2..33374c7d 100644
--- 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ActiveResourceQueue.java
+++ 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ActiveResourceQueue.java
@@ -45,7 +45,16 @@ public class ActiveResourceQueue extends ResourceQueue {
 
     public ActiveResourceQueue(
             ResourceResolverFactory resolverFactory, String serviceName, 
String queueName, String rootPath) {
-        super(resolverFactory, serviceName, queueName, rootPath);
+        this(resolverFactory, serviceName, queueName, rootPath, false);
+    }
+
+    public ActiveResourceQueue(
+            ResourceResolverFactory resolverFactory,
+            String serviceName,
+            String queueName,
+            String rootPath,
+            boolean useExactQueueLength) {
+        super(resolverFactory, serviceName, queueName, rootPath, 
useExactQueueLength);
     }
 
     @NotNull
@@ -56,14 +65,21 @@ public class ActiveResourceQueue extends ResourceQueue {
             resourceResolver = DistributionUtils.loginService(resolverFactory, 
serviceName);
             Resource queueRoot = 
ResourceQueueUtils.getRootResource(resourceResolver, queueRootPath);
 
-            int count = ResourceQueueUtils.getResourceCount(queueRoot);
+            ResourceQueueUtils.ResourceCountResult countResult = 
useExactQueueLength
+                    ? new 
ResourceQueueUtils.ResourceCountResult(ResourceQueueUtils.getResourceCount(queueRoot),
 false)
+                    : 
ResourceQueueUtils.getResourceCountCappedResult(queueRoot);
 
             DistributionQueueEntry head = 
ResourceQueueUtils.getHead(queueRoot);
             DistributionQueueItem firstItem = (null != head) ? head.getItem() 
: null;
             DistributionQueueItemStatus firstItemStatus = (null != head) ? 
head.getStatus() : null;
-            log.debug("Queue has {} items, with following status for the head: 
{}", count, firstItemStatus);
+            log.debug(
+                    "Queue has {} items, with following status for the head: 
{}",
+                    countResult.getCount(),
+                    firstItemStatus);
             return new DistributionQueueStatus(
-                    count, DistributionQueueUtils.calculateState(firstItem, 
firstItemStatus));
+                    countResult.getCount(),
+                    DistributionQueueUtils.calculateState(firstItem, 
firstItemStatus),
+                    countResult.isCapped());
         } catch (LoginException | PersistenceException e) {
             throw new RuntimeException(e);
         } finally {
diff --git 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueue.java
 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueue.java
index 11bd698b..506b47e5 100644
--- 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueue.java
+++ 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueue.java
@@ -57,13 +57,29 @@ public class ResourceQueue implements DistributionQueue {
     protected final String queueRootPath;
     protected String serviceName;
     protected String queueName;
+    protected final boolean useExactQueueLength;
 
     public ResourceQueue(
             ResourceResolverFactory resolverFactory, String serviceName, 
String queueName, String rootPath) {
+        this(resolverFactory, serviceName, queueName, rootPath, false);
+    }
+
+    /**
+     * @param useExactQueueLength if true, getStatus() reports the exact 
(uncapped) queue length instead
+     *                            of the capped approximation. Backed by the 
resourceQueue provider's
+     *                            "status.useExactQueueLength" OSGi 
configuration.
+     */
+    public ResourceQueue(
+            ResourceResolverFactory resolverFactory,
+            String serviceName,
+            String queueName,
+            String rootPath,
+            boolean useExactQueueLength) {
         this.resolverFactory = resolverFactory;
         this.serviceName = serviceName;
         this.queueName = queueName;
         this.queueRootPath = rootPath + "/" + queueName;
+        this.useExactQueueLength = useExactQueueLength;
         log.debug("starting a Resource Queue {}", queueName);
     }
 
@@ -217,9 +233,12 @@ public class ResourceQueue implements DistributionQueue {
             resourceResolver = DistributionUtils.loginService(resolverFactory, 
serviceName);
             Resource queueRoot = 
ResourceQueueUtils.getRootResource(resourceResolver, queueRootPath);
 
-            int count = ResourceQueueUtils.getResourceCount(queueRoot);
+            ResourceQueueUtils.ResourceCountResult countResult = 
useExactQueueLength
+                    ? new 
ResourceQueueUtils.ResourceCountResult(ResourceQueueUtils.getResourceCount(queueRoot),
 false)
+                    : 
ResourceQueueUtils.getResourceCountCappedResult(queueRoot);
 
-            return new DistributionQueueStatus(count, 
DistributionQueueState.PASSIVE);
+            return new DistributionQueueStatus(
+                    countResult.getCount(), DistributionQueueState.PASSIVE, 
countResult.isCapped());
         } catch (LoginException e) {
             throw new RuntimeException(e);
         } catch (PersistenceException e) {
diff --git 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueProvider.java
 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueProvider.java
index 74af08fa..7f82debf 100644
--- 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueProvider.java
+++ 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueProvider.java
@@ -53,6 +53,7 @@ public class ResourceQueueProvider implements 
DistributionQueueProvider {
     private Scheduler scheduler;
     private String agentName;
     private boolean isActive;
+    private boolean useExactQueueLength;
 
     private final Map<String, ResourceQueue> queueMap = new 
ConcurrentHashMap<>();
 
@@ -65,6 +66,17 @@ public class ResourceQueueProvider implements 
DistributionQueueProvider {
             String agentName,
             Scheduler scheduler,
             boolean isActive) {
+        this(context, resolverFactory, serviceName, agentName, scheduler, 
isActive, false);
+    }
+
+    public ResourceQueueProvider(
+            BundleContext context,
+            ResourceResolverFactory resolverFactory,
+            String serviceName,
+            String agentName,
+            Scheduler scheduler,
+            boolean isActive,
+            boolean useExactQueueLength) {
         if (serviceName == null
                 || (scheduler == null && isActive)
                 || context == null
@@ -78,6 +90,7 @@ public class ResourceQueueProvider implements 
DistributionQueueProvider {
         this.agentRootPath = QUEUES_ROOT + agentName;
         this.scheduler = scheduler;
         this.isActive = isActive;
+        this.useExactQueueLength = useExactQueueLength;
 
         register(context);
     }
@@ -87,9 +100,9 @@ public class ResourceQueueProvider implements 
DistributionQueueProvider {
     public DistributionQueue getQueue(@NotNull String queueName) throws 
DistributionException {
         return queueMap.computeIfAbsent(queueName, name -> {
             if (isActive) {
-                return new ActiveResourceQueue(resolverFactory, serviceName, 
name, agentRootPath);
+                return new ActiveResourceQueue(resolverFactory, serviceName, 
name, agentRootPath, useExactQueueLength);
             } else {
-                return new ResourceQueue(resolverFactory, serviceName, name, 
agentRootPath);
+                return new ResourceQueue(resolverFactory, serviceName, name, 
agentRootPath, useExactQueueLength);
             }
         });
     }
diff --git 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueProviderFactory.java
 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueProviderFactory.java
index 170a602c..1cf2b72e 100644
--- 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueProviderFactory.java
+++ 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueProviderFactory.java
@@ -45,6 +45,14 @@ public class ResourceQueueProviderFactory implements 
DistributionQueueProviderFa
     public @interface Config {
         @AttributeDefinition(name = "Should the Resource-backed queue created 
with a Queue Processor (i.e., ACTIVE)")
         boolean queue_isActive() default false;
+
+        @AttributeDefinition(
+                name = "Use exact (uncapped) queue length for status",
+                description = "By default, queue status/console reporting caps 
the counted queue length for "
+                        + "performance on very large queues, showing an 
approximate 'N+' count instead of the "
+                        + "exact size. Enable this to always compute the exact 
queue length instead, restoring "
+                        + "the pre-cap behavior. This can be slow for queues 
with a very large backlog.")
+        boolean status_useExactQueueLength() default false;
     }
 
     @Reference
@@ -56,16 +64,19 @@ public class ResourceQueueProviderFactory implements 
DistributionQueueProviderFa
     BundleContext context;
 
     private boolean isActive;
+    private boolean useExactQueueLength;
 
     @Activate
     protected void activate(BundleContext context, Config conf) {
         this.isActive = conf.queue_isActive();
+        this.useExactQueueLength = conf.status_useExactQueueLength();
         this.context = context;
     }
 
     @Override
     public DistributionQueueProvider getProvider(String agentName, String 
serviceName) {
-        return new ResourceQueueProvider(context, resourceResolverFactory, 
serviceName, agentName, scheduler, isActive);
+        return new ResourceQueueProvider(
+                context, resourceResolverFactory, serviceName, agentName, 
scheduler, isActive, useExactQueueLength);
     }
 
     @Override
diff --git 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueUtils.java
 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueUtils.java
index e7d141d3..3b4d36d7 100644
--- 
a/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueUtils.java
+++ 
b/src/main/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueUtils.java
@@ -64,6 +64,13 @@ public class ResourceQueueUtils {
     private static final String ENTERED_DATE = "entered.date";
     private static final String PROCESSING_ATTEMPTS = "processing.attempts";
 
+    /**
+     * Cap on how many entries getResourceCountCapped() will traverse when 
computing queue size for
+     * status/console. Queues larger than this report STATUS_ITEMS_COUNT_CAP 
instead of the exact count,
+     * so status lookups on very large queues stay bounded.
+     */
+    static final int STATUS_ITEMS_COUNT_CAP = 1_000;
+
     private static final AtomicLong itemCounter = new AtomicLong(0);
     private static final Logger log = 
LoggerFactory.getLogger(ResourceQueueUtils.class);
 
@@ -307,6 +314,51 @@ public class ResourceQueueUtils {
         return getEntries(root, 0, -1).size();
     }
 
+    /**
+     * Like getResourceCount(), but bounds the amount of traversal done: once 
more than
+     * STATUS_ITEMS_COUNT_CAP entries have been seen, stops early and returns 
STATUS_ITEMS_COUNT_CAP
+     * instead of the exact count. Intended for status/console displays on 
potentially very large
+     * queues, where an exact count is not required and a full traversal would 
be too expensive.
+     * Callers that need the exact size should use getResourceCount() instead.
+     */
+    public static int getResourceCountCapped(Resource root) {
+        return getResourceCountCappedResult(root).getCount();
+    }
+
+    static ResourceCountResult getResourceCountCappedResult(Resource root) {
+        Iterator<Resource> it = new ResourceIterator(root, RESOURCE_FOLDER, 
false, true);
+
+        int count = 0;
+        while (it.hasNext() && count <= STATUS_ITEMS_COUNT_CAP) {
+            it.next();
+            count++;
+
+            if (count > STATUS_ITEMS_COUNT_CAP) {
+                return new ResourceCountResult(STATUS_ITEMS_COUNT_CAP, true);
+            }
+        }
+
+        return new ResourceCountResult(count, false);
+    }
+
+    static final class ResourceCountResult {
+        private final int count;
+        private final boolean capped;
+
+        ResourceCountResult(int count, boolean capped) {
+            this.count = count;
+            this.capped = capped;
+        }
+
+        int getCount() {
+            return count;
+        }
+
+        boolean isCapped() {
+            return capped;
+        }
+    }
+
     private static String getUniqueEntryPath(Resource parent) {
         final StringBuilder sb = new StringBuilder();
         sb.append(parent.getPath());
diff --git 
a/src/main/java/org/apache/sling/distribution/resources/impl/ExtendedDistributionServiceResourceProvider.java
 
b/src/main/java/org/apache/sling/distribution/resources/impl/ExtendedDistributionServiceResourceProvider.java
index b3ac4345..a0136e45 100644
--- 
a/src/main/java/org/apache/sling/distribution/resources/impl/ExtendedDistributionServiceResourceProvider.java
+++ 
b/src/main/java/org/apache/sling/distribution/resources/impl/ExtendedDistributionServiceResourceProvider.java
@@ -141,6 +141,7 @@ public class ExtendedDistributionServiceResourceProvider 
extends DistributionSer
                 result.put("state", queueStatus.getState().name());
                 result.put("empty", queueStatus.isEmpty());
                 result.put("itemsCount", queueStatus.getItemsCount());
+                result.put("itemsCountCapped", 
queueStatus.isItemsCountCapped());
                 result.put("capabilities", queueCapabilities(queue));
 
                 if 
(queueName.startsWith(ErrorQueueDispatchingStrategy.ERROR_PREFIX)) {
diff --git 
a/src/test/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueUtilsTest.java
 
b/src/test/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueUtilsTest.java
index 69cca437..003605c3 100644
--- 
a/src/test/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueUtilsTest.java
+++ 
b/src/test/java/org/apache/sling/distribution/queue/impl/resource/ResourceQueueUtilsTest.java
@@ -23,6 +23,8 @@ import java.util.GregorianCalendar;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.distribution.queue.DistributionQueueItem;
+import org.apache.sling.distribution.queue.DistributionQueueStatus;
+import org.apache.sling.distribution.queue.impl.SimpleAgentDistributionQueue;
 import org.apache.sling.distribution.queue.spi.DistributionQueue;
 import org.apache.sling.testing.resourceresolver.MockResourceResolverFactory;
 import org.junit.Before;
@@ -79,6 +81,79 @@ public class ResourceQueueUtilsTest {
         assertEquals(3, ResourceQueueUtils.getResourceCount(root));
     }
 
+    @Test
+    public void testResourceCountCapped() throws Exception {
+        String agentPath = QUEUES_ROOT + QUEUE_NAME;
+        DistributionQueue queue = new ResourceQueue(rrf, "test", QUEUE_NAME, 
agentPath);
+
+        for (int i = 0; i < ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP + 5; 
i++) {
+            queue.add(new DistributionQueueItem(randomUUID().toString(), 
emptyMap()));
+        }
+
+        ResourceResolver rr = rrf.getResourceResolver(null);
+        Resource root = ResourceQueueUtils.getRootResource(rr, agentPath);
+        assertEquals(ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP, 
ResourceQueueUtils.getResourceCountCapped(root));
+        assertEquals(ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP + 5, 
ResourceQueueUtils.getResourceCount(root));
+    }
+
+    @Test
+    public void testGetStatusUsesCappedCountByDefault() throws Exception {
+        String agentPath = QUEUES_ROOT + QUEUE_NAME;
+        DistributionQueue queue = new ResourceQueue(rrf, "test", QUEUE_NAME, 
agentPath);
+
+        for (int i = 0; i < ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP + 5; 
i++) {
+            queue.add(new DistributionQueueItem(randomUUID().toString(), 
emptyMap()));
+        }
+
+        DistributionQueueStatus status = queue.getStatus();
+        assertEquals(ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP, 
status.getItemsCount());
+        assertTrue(status.isItemsCountCapped());
+
+        DistributionQueueStatus activeDefault = new ActiveResourceQueue(rrf, 
"test", QUEUE_NAME, agentPath).getStatus();
+        DistributionQueueStatus activeExplicit =
+                new ActiveResourceQueue(rrf, "test", QUEUE_NAME, agentPath, 
false).getStatus();
+        assertEquals(status.getItemsCount(), activeDefault.getItemsCount());
+        assertTrue(activeDefault.isItemsCountCapped());
+        assertEquals(activeDefault.getItemsCount(), 
activeExplicit.getItemsCount());
+        assertTrue(activeExplicit.isItemsCountCapped());
+
+        DistributionQueueStatus pausedStatus = new 
SimpleAgentDistributionQueue(queue, true, "test-agent").getStatus();
+        assertTrue(pausedStatus.isItemsCountCapped());
+    }
+
+    @Test
+    public void testGetStatusUsesExactCountWhenConfigured() throws Exception {
+        String agentPath = QUEUES_ROOT + QUEUE_NAME;
+        DistributionQueue queue = new ResourceQueue(rrf, "test", QUEUE_NAME, 
agentPath, true);
+
+        for (int i = 0; i < ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP + 5; 
i++) {
+            queue.add(new DistributionQueueItem(randomUUID().toString(), 
emptyMap()));
+        }
+
+        DistributionQueueStatus status = queue.getStatus();
+        assertEquals(ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP + 5, 
status.getItemsCount());
+        assertFalse(status.isItemsCountCapped());
+
+        DistributionQueueStatus activeExact =
+                new ActiveResourceQueue(rrf, "test", QUEUE_NAME, agentPath, 
true).getStatus();
+        assertEquals(status.getItemsCount(), activeExact.getItemsCount());
+        assertFalse(activeExact.isItemsCountCapped());
+    }
+
+    @Test
+    public void testGetStatusAtCapIsNotCapped() throws Exception {
+        String agentPath = QUEUES_ROOT + QUEUE_NAME;
+        DistributionQueue queue = new ResourceQueue(rrf, "test", QUEUE_NAME, 
agentPath);
+
+        for (int i = 0; i < ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP; i++) {
+            queue.add(new DistributionQueueItem(randomUUID().toString(), 
emptyMap()));
+        }
+
+        assertEquals(
+                ResourceQueueUtils.STATUS_ITEMS_COUNT_CAP, 
queue.getStatus().getItemsCount());
+        assertFalse(queue.getStatus().isItemsCountCapped());
+    }
+
     @Test
     public void testIsSafeToDelete() throws Exception {
 

Reply via email to