From 2171aef9d6ce195ac508eab20d2aa379dde880fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Fri, 7 Mar 2025 14:27:22 +0100 Subject: [PATCH 01/34] [maven-release-plugin] prepare for next development iteration --- activemq-all/pom.xml | 2 +- activemq-amqp/pom.xml | 2 +- activemq-blueprint/pom.xml | 2 +- activemq-broker/pom.xml | 2 +- activemq-cf/pom.xml | 2 +- activemq-client-jakarta/pom.xml | 2 +- activemq-client/pom.xml | 2 +- activemq-console/pom.xml | 2 +- activemq-http/pom.xml | 2 +- activemq-jaas/pom.xml | 2 +- activemq-jdbc-store/pom.xml | 2 +- activemq-jms-pool/pom.xml | 2 +- activemq-kahadb-store/pom.xml | 2 +- activemq-karaf-itest/pom.xml | 2 +- activemq-karaf/pom.xml | 2 +- activemq-log4j-appender/pom.xml | 2 +- activemq-mqtt/pom.xml | 2 +- activemq-openwire-generator/pom.xml | 2 +- activemq-openwire-legacy/pom.xml | 2 +- activemq-osgi/pom.xml | 2 +- activemq-partition/pom.xml | 2 +- activemq-pool/pom.xml | 2 +- activemq-ra/pom.xml | 2 +- activemq-rar/pom.xml | 2 +- activemq-run/pom.xml | 2 +- activemq-runtime-config/pom.xml | 2 +- activemq-shiro/pom.xml | 2 +- activemq-spring/pom.xml | 2 +- activemq-stomp/pom.xml | 2 +- activemq-tooling/activemq-junit/pom.xml | 2 +- activemq-tooling/activemq-maven-plugin/pom.xml | 2 +- activemq-tooling/activemq-memtest-maven-plugin/pom.xml | 2 +- activemq-tooling/activemq-perf-maven-plugin/pom.xml | 2 +- activemq-tooling/pom.xml | 2 +- activemq-unit-tests/pom.xml | 2 +- activemq-web-console/pom.xml | 2 +- activemq-web-demo/pom.xml | 2 +- activemq-web/pom.xml | 2 +- assembly/pom.xml | 2 +- pom.xml | 6 +++--- 40 files changed, 42 insertions(+), 42 deletions(-) diff --git a/activemq-all/pom.xml b/activemq-all/pom.xml index 77bb262b4cc..3f65d6e5685 100644 --- a/activemq-all/pom.xml +++ b/activemq-all/pom.xml @@ -14,7 +14,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-all diff --git a/activemq-amqp/pom.xml b/activemq-amqp/pom.xml index dd585b095f6..59bd7a06a86 100644 --- a/activemq-amqp/pom.xml +++ b/activemq-amqp/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-amqp diff --git a/activemq-blueprint/pom.xml b/activemq-blueprint/pom.xml index bb0305fb8d0..1f548c6a4cd 100644 --- a/activemq-blueprint/pom.xml +++ b/activemq-blueprint/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-blueprint diff --git a/activemq-broker/pom.xml b/activemq-broker/pom.xml index ad5bd86f40f..3b79ffa5e68 100644 --- a/activemq-broker/pom.xml +++ b/activemq-broker/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-broker diff --git a/activemq-cf/pom.xml b/activemq-cf/pom.xml index 6c965a58020..0412fce7c88 100644 --- a/activemq-cf/pom.xml +++ b/activemq-cf/pom.xml @@ -24,7 +24,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-cf diff --git a/activemq-client-jakarta/pom.xml b/activemq-client-jakarta/pom.xml index 7784ad2e417..aa201ca0fa1 100644 --- a/activemq-client-jakarta/pom.xml +++ b/activemq-client-jakarta/pom.xml @@ -20,7 +20,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-client-jakarta bundle diff --git a/activemq-client/pom.xml b/activemq-client/pom.xml index 90e8b6b01ab..0f1337e3c22 100644 --- a/activemq-client/pom.xml +++ b/activemq-client/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-client diff --git a/activemq-console/pom.xml b/activemq-console/pom.xml index c6424b6b060..65af3c78f6e 100644 --- a/activemq-console/pom.xml +++ b/activemq-console/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-console diff --git a/activemq-http/pom.xml b/activemq-http/pom.xml index d6eb18348fb..a6637535008 100644 --- a/activemq-http/pom.xml +++ b/activemq-http/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-http diff --git a/activemq-jaas/pom.xml b/activemq-jaas/pom.xml index 44d757c3726..11384e506ea 100644 --- a/activemq-jaas/pom.xml +++ b/activemq-jaas/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-jaas diff --git a/activemq-jdbc-store/pom.xml b/activemq-jdbc-store/pom.xml index 0e16dc96c6c..6f5b4b9e9ad 100644 --- a/activemq-jdbc-store/pom.xml +++ b/activemq-jdbc-store/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-jdbc-store diff --git a/activemq-jms-pool/pom.xml b/activemq-jms-pool/pom.xml index 1cbe71c45e9..cc3b011f3fa 100644 --- a/activemq-jms-pool/pom.xml +++ b/activemq-jms-pool/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-jms-pool diff --git a/activemq-kahadb-store/pom.xml b/activemq-kahadb-store/pom.xml index 2cd3352ce16..1226050c5d5 100644 --- a/activemq-kahadb-store/pom.xml +++ b/activemq-kahadb-store/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-kahadb-store diff --git a/activemq-karaf-itest/pom.xml b/activemq-karaf-itest/pom.xml index 81045bf1e7c..dcb55592362 100644 --- a/activemq-karaf-itest/pom.xml +++ b/activemq-karaf-itest/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-karaf-itest diff --git a/activemq-karaf/pom.xml b/activemq-karaf/pom.xml index 6e74ad7b8ee..4e1cc4d2c19 100644 --- a/activemq-karaf/pom.xml +++ b/activemq-karaf/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-karaf diff --git a/activemq-log4j-appender/pom.xml b/activemq-log4j-appender/pom.xml index 156770b3e63..43e417717e5 100644 --- a/activemq-log4j-appender/pom.xml +++ b/activemq-log4j-appender/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-log4j-appender diff --git a/activemq-mqtt/pom.xml b/activemq-mqtt/pom.xml index 7846caee60e..78120c9f92a 100644 --- a/activemq-mqtt/pom.xml +++ b/activemq-mqtt/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-mqtt diff --git a/activemq-openwire-generator/pom.xml b/activemq-openwire-generator/pom.xml index c1aade97990..b5ab9e0cf5e 100644 --- a/activemq-openwire-generator/pom.xml +++ b/activemq-openwire-generator/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-openwire-generator diff --git a/activemq-openwire-legacy/pom.xml b/activemq-openwire-legacy/pom.xml index ba8f587a856..5bf482ef81f 100644 --- a/activemq-openwire-legacy/pom.xml +++ b/activemq-openwire-legacy/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-openwire-legacy diff --git a/activemq-osgi/pom.xml b/activemq-osgi/pom.xml index e5d8b8712d1..a2c8fe7d602 100644 --- a/activemq-osgi/pom.xml +++ b/activemq-osgi/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-osgi diff --git a/activemq-partition/pom.xml b/activemq-partition/pom.xml index 5b1f1220e0f..043067d86a1 100644 --- a/activemq-partition/pom.xml +++ b/activemq-partition/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-partition diff --git a/activemq-pool/pom.xml b/activemq-pool/pom.xml index 3927b361cbc..0c7e4f3a655 100644 --- a/activemq-pool/pom.xml +++ b/activemq-pool/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-pool diff --git a/activemq-ra/pom.xml b/activemq-ra/pom.xml index 9ad783e91f3..29b64745763 100644 --- a/activemq-ra/pom.xml +++ b/activemq-ra/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-ra diff --git a/activemq-rar/pom.xml b/activemq-rar/pom.xml index f3874e7d442..b6357f51e82 100644 --- a/activemq-rar/pom.xml +++ b/activemq-rar/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-rar diff --git a/activemq-run/pom.xml b/activemq-run/pom.xml index f36b0f8e607..77e06426e9b 100644 --- a/activemq-run/pom.xml +++ b/activemq-run/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-run diff --git a/activemq-runtime-config/pom.xml b/activemq-runtime-config/pom.xml index 3af0d9a9a74..20487744eef 100644 --- a/activemq-runtime-config/pom.xml +++ b/activemq-runtime-config/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-runtime-config diff --git a/activemq-shiro/pom.xml b/activemq-shiro/pom.xml index 42bea67246b..940cadcc151 100644 --- a/activemq-shiro/pom.xml +++ b/activemq-shiro/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-shiro diff --git a/activemq-spring/pom.xml b/activemq-spring/pom.xml index 0961e3ecce1..40d0dc06efe 100644 --- a/activemq-spring/pom.xml +++ b/activemq-spring/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-spring diff --git a/activemq-stomp/pom.xml b/activemq-stomp/pom.xml index 730c05aa760..8740dc6c6c6 100644 --- a/activemq-stomp/pom.xml +++ b/activemq-stomp/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-stomp diff --git a/activemq-tooling/activemq-junit/pom.xml b/activemq-tooling/activemq-junit/pom.xml index 837d972f662..48b906c7e78 100644 --- a/activemq-tooling/activemq-junit/pom.xml +++ b/activemq-tooling/activemq-junit/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.0 + 5.19.1-SNAPSHOT activemq-junit diff --git a/activemq-tooling/activemq-maven-plugin/pom.xml b/activemq-tooling/activemq-maven-plugin/pom.xml index 0b34bd9b19d..5dc37e467a0 100644 --- a/activemq-tooling/activemq-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-maven-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.0 + 5.19.1-SNAPSHOT activemq-maven-plugin diff --git a/activemq-tooling/activemq-memtest-maven-plugin/pom.xml b/activemq-tooling/activemq-memtest-maven-plugin/pom.xml index 018864f0d72..61d616ae536 100644 --- a/activemq-tooling/activemq-memtest-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-memtest-maven-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.0 + 5.19.1-SNAPSHOT activemq-memtest-maven-plugin diff --git a/activemq-tooling/activemq-perf-maven-plugin/pom.xml b/activemq-tooling/activemq-perf-maven-plugin/pom.xml index 9a5a9bdb287..04d09e78b1b 100644 --- a/activemq-tooling/activemq-perf-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-perf-maven-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.0 + 5.19.1-SNAPSHOT activemq-perf-maven-plugin diff --git a/activemq-tooling/pom.xml b/activemq-tooling/pom.xml index be775ffcdf8..860e7ab3b33 100644 --- a/activemq-tooling/pom.xml +++ b/activemq-tooling/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT org.apache.activemq.tooling diff --git a/activemq-unit-tests/pom.xml b/activemq-unit-tests/pom.xml index 603f0be5a09..ed2667b006a 100644 --- a/activemq-unit-tests/pom.xml +++ b/activemq-unit-tests/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-unit-tests diff --git a/activemq-web-console/pom.xml b/activemq-web-console/pom.xml index 11759994dcf..42e5437f974 100644 --- a/activemq-web-console/pom.xml +++ b/activemq-web-console/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-web-console diff --git a/activemq-web-demo/pom.xml b/activemq-web-demo/pom.xml index f5f9cb29f5c..5fadbbd0771 100644 --- a/activemq-web-demo/pom.xml +++ b/activemq-web-demo/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-web-demo diff --git a/activemq-web/pom.xml b/activemq-web/pom.xml index 74fcd4d9e70..b2883ffc3ba 100644 --- a/activemq-web/pom.xml +++ b/activemq-web/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT activemq-web diff --git a/assembly/pom.xml b/assembly/pom.xml index a1b2f464f20..a6a8b5c2ce6 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT apache-activemq diff --git a/pom.xml b/pom.xml index 31fb9559b70..b940d4fb8ca 100644 --- a/pom.xml +++ b/pom.xml @@ -25,14 +25,14 @@ org.apache.activemq activemq-parent - 5.19.0 + 5.19.1-SNAPSHOT pom ActiveMQ 2005 - 2025-03-07T13:22:49Z + 2025-03-07T13:27:22Z 3.1.4 activemq-${project.version} @@ -247,7 +247,7 @@ scm:git:http://gitbox.apache.org/repos/asf/activemq.git scm:git:https://gitbox.apache.org/repos/asf/activemq.git https://github.com/apache/activemq - activemq-5.19.0 + main From af2a38c8971a923241d749c846c5921f70dfdaff Mon Sep 17 00:00:00 2001 From: Benjamin Graf Date: Thu, 13 Mar 2025 22:13:14 +0100 Subject: [PATCH 02/34] AMQ-9685 - Virtual topic name should have at least one character to avoid Exception (cherry picked from commit 0ffe52a28b85dfb4a31ec6d7d522cb578341c8a9) --- .../broker/region/virtual/VirtualTopic.java | 2 +- .../activemq/broker/virtual/AMQ9685Test.java | 81 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/virtual/VirtualTopic.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/virtual/VirtualTopic.java index c57d80a16f4..7b6bd71ad27 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/virtual/VirtualTopic.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/virtual/VirtualTopic.java @@ -67,7 +67,7 @@ public ActiveMQDestination getMappedDestinations() { public Destination interceptMappedDestination(Destination destination) { // do a reverse map from destination to get actual virtual destination final String physicalName = destination.getActiveMQDestination().getPhysicalName(); - final Pattern pattern = Pattern.compile(getRegex(prefix) + "(.*)" + getRegex(postfix)); + final Pattern pattern = Pattern.compile(getRegex(prefix) + "(.+)" + getRegex(postfix)); final Matcher matcher = pattern.matcher(physicalName); if (matcher.matches()) { final String virtualName = matcher.group(1); diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java new file mode 100644 index 00000000000..35f3d6b9b63 --- /dev/null +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.broker.virtual; + +import jakarta.jms.Connection; +import jakarta.jms.Destination; +import jakarta.jms.Session; + +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.broker.BrokerService; +import org.apache.activemq.broker.region.DestinationInterceptor; +import org.apache.activemq.broker.region.virtual.VirtualDestination; +import org.apache.activemq.broker.region.virtual.VirtualDestinationInterceptor; +import org.apache.activemq.broker.region.virtual.VirtualTopic; +import org.apache.activemq.command.ActiveMQQueue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class AMQ9685Test { + + private BrokerService brokerService; + private Connection connection; + + @Before + public void init() throws Exception { + brokerService = createBroker(); + brokerService.start(); + connection = createConnection(); + connection.start(); + } + + @After + public void after() throws Exception { + try { + connection.close(); + } catch (Exception e) { + //swallow any error so broker can still be stopped + } + brokerService.stop(); + } + + @Test + public void testBrokenWildcardQueueName() throws Exception { + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Destination destination = new ActiveMQQueue("Consumer.foo."); + session.createConsumer(destination, null); + } + + private Connection createConnection() throws Exception { + ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerService.getVmConnectorURI()); + cf.setWatchTopicAdvisories(false); + return cf.createConnection(); + } + + private BrokerService createBroker() throws Exception { + BrokerService broker = new BrokerService(); + broker.setAdvisorySupport(false); + broker.setPersistent(false); + + VirtualTopic virtualTopic = new VirtualTopic(); + VirtualDestinationInterceptor interceptor = new VirtualDestinationInterceptor(); + interceptor.setVirtualDestinations(new VirtualDestination[]{virtualTopic}); + broker.setDestinationInterceptors(new DestinationInterceptor[]{interceptor}); + return broker; + } +} From a7de0333daef4f5df582fb063010bd21530693b0 Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Tue, 18 Mar 2025 15:41:02 -0400 Subject: [PATCH 03/34] AMQ-9685 - Improve test (cherry picked from commit 4b22a4c5c5c584228a35490cf0c0b6ffad916978) --- .../activemq/broker/virtual/AMQ9685Test.java | 59 +++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java index 35f3d6b9b63..d7ef7f5e1ee 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java @@ -22,25 +22,40 @@ import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; +import org.apache.activemq.broker.jmx.BrokerViewMBean; import org.apache.activemq.broker.region.DestinationInterceptor; import org.apache.activemq.broker.region.virtual.VirtualDestination; import org.apache.activemq.broker.region.virtual.VirtualDestinationInterceptor; import org.apache.activemq.broker.region.virtual.VirtualTopic; +import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQQueue; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import static org.junit.Assert.*; public class AMQ9685Test { + @Rule + public final TemporaryFolder temp = new TemporaryFolder(); private BrokerService brokerService; private Connection connection; + private String dir; + private ActiveMQQueue destination = new ActiveMQQueue("Consumer.foo."); @Before public void init() throws Exception { + dir = temp.newFolder().getAbsolutePath(); brokerService = createBroker(); brokerService.start(); - connection = createConnection(); + connection = new ActiveMQConnectionFactory( + brokerService.getVmConnectorURI()).createConnection(); connection.start(); } @@ -55,22 +70,38 @@ public void after() throws Exception { } @Test - public void testBrokenWildcardQueueName() throws Exception { + public void testVirtualTopicMissingNameManual() throws Exception { + // Test manual creation + brokerService.getRegionBroker().addDestination(brokerService.getAdminConnectionContext(), + destination, false); + + // Verify created + assertTrue(brokerService.getBroker().getDestinationMap().containsKey(destination)); + assertTrue(brokerService.getPersistenceAdapter().getDestinations().contains(destination)); + + // Verify restart without issue + restart(); + } + + @Test + public void testVirtualTopicMissingNameConsumer() throws Exception { Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - Destination destination = new ActiveMQQueue("Consumer.foo."); + // test consumer attempt dynamic creation session.createConsumer(destination, null); - } - private Connection createConnection() throws Exception { - ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerService.getVmConnectorURI()); - cf.setWatchTopicAdvisories(false); - return cf.createConnection(); + // Verify created + assertTrue(brokerService.getBroker().getDestinationMap().containsKey(destination)); + assertTrue(brokerService.getPersistenceAdapter().getDestinations().contains(destination)); + + // Verify restart without issue + restart(); } - private BrokerService createBroker() throws Exception { + private BrokerService createBroker() { BrokerService broker = new BrokerService(); broker.setAdvisorySupport(false); - broker.setPersistent(false); + broker.setPersistent(true); + broker.setDataDirectory(dir); VirtualTopic virtualTopic = new VirtualTopic(); VirtualDestinationInterceptor interceptor = new VirtualDestinationInterceptor(); @@ -78,4 +109,12 @@ private BrokerService createBroker() throws Exception { broker.setDestinationInterceptors(new DestinationInterceptor[]{interceptor}); return broker; } + + private void restart() throws Exception { + brokerService.stop(); + brokerService.waitUntilStopped(); + brokerService = createBroker(); + brokerService.start(); + brokerService.waitUntilStarted(); + } } From c49960d61c878ea12280b2871402286938aa0427 Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Tue, 18 Mar 2025 15:44:41 -0400 Subject: [PATCH 04/34] fix imports (cherry picked from commit be361fd7e66447c95f0024e75a42ee4c5b2a547f) --- .../apache/activemq/broker/virtual/AMQ9685Test.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java index d7ef7f5e1ee..59396d95492 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/virtual/AMQ9685Test.java @@ -16,18 +16,14 @@ */ package org.apache.activemq.broker.virtual; -import jakarta.jms.Connection; -import jakarta.jms.Destination; -import jakarta.jms.Session; - +import javax.jms.Connection; +import javax.jms.Session; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; -import org.apache.activemq.broker.jmx.BrokerViewMBean; import org.apache.activemq.broker.region.DestinationInterceptor; import org.apache.activemq.broker.region.virtual.VirtualDestination; import org.apache.activemq.broker.region.virtual.VirtualDestinationInterceptor; import org.apache.activemq.broker.region.virtual.VirtualTopic; -import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQQueue; import org.junit.After; import org.junit.Before; @@ -35,10 +31,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; - -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; public class AMQ9685Test { From 58006d4894b08ab7adae7dbf0ade220bd30850ac Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Fri, 11 Apr 2025 08:14:09 -0400 Subject: [PATCH 05/34] AMQ-9689 - Network of Broker durable sync TTL fixes and improvements (#1419) This commit makes several improvements and fixes for syncing durable subscriptions when a network bridge connects. 1) A bug was fixed during durable sync that would cause the clientId to not always be included for durables in the subscription list which could cause a loop to be created as the other broker would not be able to tell where the network subscription came from. 2) During reactivation when dynamicOnly is false and durable sync is set to true, we make sure to include the TTL information (full broker path) from the online consumer attached to durables so that TTL info is properly propagated so we don't incorrectly create demand. Thisonly works if consumers are online, so for TTL > 1 it is still recommended to set dynamicOnly to true and allow only online consumers drive demand. 3) For TTL 1, we can handle sync correctly on restarts even if durables are offline and missing consumer TTL info because we know that we should ignore proxy durables (bridge durables for other bridges) entirely because they will be > 1 hop away. 4) Some other minor improvements were made like filtering everything if TTL is 0 and also consolidating logic. (cherry picked from commit 953737ca082e1ff67cf0b408e7ec72cd89be75ac) (cherry picked from commit 182c598df544661e0d2e947dafaf1020ca331d6d) --- .../DemandForwardingBridgeSupport.java | 125 +++++----- .../network/DurableConduitBridge.java | 8 +- .../network/NetworkBridgeConfiguration.java | 1 + .../activemq/util/NetworkBridgeUtils.java | 123 ++++++---- .../DurableFiveBrokerNetworkBridgeTest.java | 220 +++++++++++++++++- 5 files changed, 375 insertions(+), 102 deletions(-) diff --git a/activemq-broker/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java b/activemq-broker/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java index 57afc85d11e..8d16445fb96 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java +++ b/activemq-broker/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java @@ -113,7 +113,7 @@ */ public abstract class DemandForwardingBridgeSupport implements NetworkBridge, BrokerServiceAware { private static final Logger LOG = LoggerFactory.getLogger(DemandForwardingBridgeSupport.class); - protected static final String DURABLE_SUB_PREFIX = "NC-DS_"; + protected static final String DURABLE_SUB_PREFIX = NetworkBridgeConfiguration.DURABLE_SUB_PREFIX; protected final Transport localBroker; protected final Transport remoteBroker; protected IdGenerator idGenerator = new IdGenerator(); @@ -664,23 +664,8 @@ public void run() { } } - /** - * Checks whether or not this consumer is a direct bridge network subscription - * @param info - * @return - */ - protected boolean isDirectBridgeConsumer(ConsumerInfo info) { - return (info.getSubscriptionName() != null && info.getSubscriptionName().startsWith(DURABLE_SUB_PREFIX)) && - (info.getClientId() == null || info.getClientId().startsWith(configuration.getName())); - } - protected boolean isProxyBridgeSubscription(String clientId, String subName) { - if (subName != null && clientId != null) { - if (subName.startsWith(DURABLE_SUB_PREFIX) && !clientId.startsWith(configuration.getName())) { - return true; - } - } - return false; + return NetworkBridgeUtils.isProxyBridgeSubscription(configuration, clientId, subName); } /** @@ -750,49 +735,61 @@ protected void serviceRemoteCommand(Command command) { } else if (command instanceof BrokerSubscriptionInfo) { final BrokerSubscriptionInfo brokerSubscriptionInfo = (BrokerSubscriptionInfo) command; - //Start in a new thread so we don't block the transport waiting for staticDestinations - syncExecutor.execute(new Runnable() { - - @Override - public void run() { - try { - staticDestinationsLatch.await(); - //Make sure after the countDown of staticDestinationsLatch we aren't stopping - if (!disposed.get()) { - BrokerSubscriptionInfo subInfo = brokerSubscriptionInfo; - LOG.debug("Received Remote BrokerSubscriptionInfo on {} from {}", - brokerService.getBrokerName(), subInfo.getBrokerName()); - - if (configuration.isSyncDurableSubs() && configuration.isConduitSubscriptions() - && !configuration.isDynamicOnly()) { - if (started.get()) { - if (subInfo.getSubscriptionInfos() != null) { - for (ConsumerInfo info : subInfo.getSubscriptionInfos()) { - //re-add any process any non-NC consumers that match the - //dynamicallyIncludedDestinations list - //Also re-add network consumers that are not part of this direct - //bridge (proxy of proxy bridges) - if((info.getSubscriptionName() == null || !isDirectBridgeConsumer(info)) && - NetworkBridgeUtils.matchesDestinations(dynamicallyIncludedDestinations, info.getDestination())) { - serviceRemoteConsumerAdvisory(info); - } - } - } + // Skip the durable sync if any of the following are true: + // 1) if the flag is set to false. + // 2) If dynamicOnly is true, this means means to only activate when the real + // consumers come back so we need to skip. This mode is useful espeically when + // setting TTL > 1 as the TTL info is tied to consumers + // 3) If conduit subscriptions is disable we also skip, for the same reason we + // skip when dynamicOnly is true, that we need to let consumers entirely drive + // the creation/removal of subscriptions as each consumer gets their own + if (!configuration.isSyncDurableSubs() || !configuration.isConduitSubscriptions() + || configuration.isDynamicOnly()) { + return; + } - //After re-added, clean up any empty durables - for (Iterator i = subscriptionMapByLocalId.values().iterator(); i.hasNext(); ) { - DemandSubscription ds = i.next(); - if (NetworkBridgeUtils.matchesDestinations(dynamicallyIncludedDestinations, ds.getLocalInfo().getDestination())) { - cleanupDurableSub(ds, i); - } - } + //Start in a new thread so we don't block the transport waiting for staticDestinations + syncExecutor.execute(() -> { + try { + staticDestinationsLatch.await(); + + //Make sure after the countDown of staticDestinationsLatch we aren't stopping + if (!disposed.get() && started.get()) { + final BrokerSubscriptionInfo subInfo = brokerSubscriptionInfo; + LOG.debug("Received Remote BrokerSubscriptionInfo on {} from {}", + brokerService.getBrokerName(), subInfo.getBrokerName()); + + // Go through and subs sent and see if we can add demand + if (subInfo.getSubscriptionInfos() != null) { + // Re-add and process subscriptions on the remote broker to add demand + for (ConsumerInfo info : subInfo.getSubscriptionInfos()) { + // Brokers filter what is sent, but the filtering logic has changed between + // versions, plus some durables sent are only processed for removes so we + // need to filter what to process for adding demand + if (NetworkBridgeUtils.matchesConfigForDurableSync(configuration, + info.getClientId(), info.getSubscriptionName(), info.getDestination())) { + serviceRemoteConsumerAdvisory(info); } } } - } catch (Exception e) { - LOG.warn("Error processing BrokerSubscriptionInfo: {}", e.getMessage(), e); - LOG.debug(e.getMessage(), e); + + //After processing demand to add, clean up any empty durables + for (Iterator i = subscriptionMapByLocalId.values().iterator(); i.hasNext(); ) { + DemandSubscription ds = i.next(); + // This filters on destinations to see if we should process possible removal + // based on the bridge configuration (included dests, TTL, etc). + if (NetworkBridgeUtils.matchesDestinations(configuration.getDynamicallyIncludedDestinations(), + ds.getLocalInfo().getDestination())) { + // Note that this method will further check that there are no remote + // demand that was previously added or associated. If there are remote + // subscriptions tied to the DS, then it will not be removed. + cleanupDurableSub(ds, i); + } + } } + } catch (Exception e) { + LOG.warn("Error processing BrokerSubscriptionInfo: {}", e.getMessage(), e); + LOG.debug(e.getMessage(), e); } }); @@ -1427,7 +1424,7 @@ protected void setupStaticDestinations() { if (dests != null) { for (ActiveMQDestination dest : dests) { if (isPermissableDestination(dest)) { - DemandSubscription sub = createDemandSubscription(dest, null); + DemandSubscription sub = createDemandSubscription(dest, null, null); if (sub != null) { sub.setStaticallyIncluded(true); try { @@ -1684,7 +1681,8 @@ protected DemandSubscription doCreateDemandSubscription(ConsumerInfo info) throw return result; } - final protected DemandSubscription createDemandSubscription(ActiveMQDestination destination, final String subscriptionName) { + final protected DemandSubscription createDemandSubscription(ActiveMQDestination destination, final String subscriptionName, + BrokerId[] brokerPath) { ConsumerInfo info = new ConsumerInfo(); info.setNetworkSubscription(true); info.setDestination(destination); @@ -1694,7 +1692,16 @@ final protected DemandSubscription createDemandSubscription(ActiveMQDestination } // Indicate that this subscription is being made on behalf of the remote broker. - info.setBrokerPath(new BrokerId[]{remoteBrokerId}); + // If we have existing brokerPath info then use it, this is important to + // preserve TTL information + if (brokerPath == null || brokerPath.length == 0) { + info.setBrokerPath(new BrokerId[]{remoteBrokerId}); + } else { + info.setBrokerPath(brokerPath); + if (!contains(brokerPath, remoteBrokerId)) { + addRemoteBrokerToBrokerPath(info); + } + } // the remote info held by the DemandSubscription holds the original // consumerId, the local info get's overwritten @@ -1778,7 +1785,7 @@ protected NetworkBridgeFilter createNetworkBridgeFilter(ConsumerInfo info) throw return filterFactory.create(info, getRemoteBrokerPath(), configuration.getMessageTTL(), configuration.getConsumerTTL()); } - protected void addRemoteBrokerToBrokerPath(ConsumerInfo info) throws IOException { + protected void addRemoteBrokerToBrokerPath(ConsumerInfo info) { info.setBrokerPath(appendToBrokerPath(info.getBrokerPath(), getRemoteBrokerPath())); } diff --git a/activemq-broker/src/main/java/org/apache/activemq/network/DurableConduitBridge.java b/activemq-broker/src/main/java/org/apache/activemq/network/DurableConduitBridge.java index 8d14f7492fe..9860bd13e15 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/network/DurableConduitBridge.java +++ b/activemq-broker/src/main/java/org/apache/activemq/network/DurableConduitBridge.java @@ -74,10 +74,14 @@ protected void setupStaticDestinations() { String candidateSubName = getSubscriberName(dest); for (Subscription subscription : topicRegion.getDurableSubscriptions().values()) { - String subName = subscription.getConsumerInfo().getSubscriptionName(); + ConsumerInfo subInfo = subscription.getConsumerInfo(); + String subName = subInfo.getSubscriptionName(); String clientId = subscription.getContext().getClientId(); if (subName != null && subName.equals(candidateSubName) && clientId.startsWith(configuration.getName())) { - DemandSubscription sub = createDemandSubscription(dest, subName); + // Include the brokerPath if it exists so that we can handle TTL more correctly + // This only works if the consumers are online as offline consumers are missing TTL + // For TTL > 1 configurations setting dynamicOnly to true may make more sense + DemandSubscription sub = createDemandSubscription(dest, subName, subInfo.getBrokerPath()); if (sub != null) { sub.getLocalInfo().setSubscriptionName(getSubscriberName(dest)); sub.setStaticallyIncluded(true); diff --git a/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridgeConfiguration.java b/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridgeConfiguration.java index 87ad022606b..911fb0bbe4e 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridgeConfiguration.java +++ b/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridgeConfiguration.java @@ -28,6 +28,7 @@ * Configuration for a NetworkBridge */ public class NetworkBridgeConfiguration { + public static final String DURABLE_SUB_PREFIX = "NC-DS_"; private boolean conduitSubscriptions = true; /** diff --git a/activemq-broker/src/main/java/org/apache/activemq/util/NetworkBridgeUtils.java b/activemq-broker/src/main/java/org/apache/activemq/util/NetworkBridgeUtils.java index 700baf678d7..4b39bb2bf7b 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/util/NetworkBridgeUtils.java +++ b/activemq-broker/src/main/java/org/apache/activemq/util/NetworkBridgeUtils.java @@ -20,7 +20,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - import org.apache.activemq.advisory.AdvisoryBroker; import org.apache.activemq.advisory.AdvisorySupport; import org.apache.activemq.broker.BrokerService; @@ -36,6 +35,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.activemq.network.NetworkBridgeConfiguration.DURABLE_SUB_PREFIX; + public class NetworkBridgeUtils { private static final Logger LOG = LoggerFactory.getLogger(NetworkBridgeUtils.class); @@ -54,31 +55,37 @@ public static BrokerSubscriptionInfo getBrokerSubscriptionInfo(final BrokerServi TopicRegion topicRegion = (TopicRegion) regionBroker.getTopicRegion(); Set subscriptionInfos = new HashSet<>(); - //Add all durable subscriptions to the set that match the network config - //which currently is just the dynamicallyIncludedDestinations list - for (SubscriptionKey key : topicRegion.getDurableSubscriptions().keySet()) { - DurableTopicSubscription sub = topicRegion.getDurableSubscriptions().get(key); - if (sub != null && NetworkBridgeUtils.matchesNetworkConfig(config, sub.getConsumerInfo().getDestination())) { + // Add all durable subscriptions to the set that match the network config + // which currently is just the dynamicallyIncludedDestinations list + for (Map.Entry entry : topicRegion.getDurableSubscriptions().entrySet()) { + final SubscriptionKey key = entry.getKey(); + final DurableTopicSubscription sub = entry.getValue(); + // We must use the key for matchesConfigForDurableSync() because the clientId for the ConsumerInfo object + // may be null if the subscription is offline, which is why we copy the ConsumerInfo below + // and set the clientId to match the key. The correct clientId is important for the receiving broker + // to do proper filtering when TTL is set + if (sub != null && NetworkBridgeUtils.matchesConfigForDurableSync(config, key.getClientId(), + key.getSubscriptionName(), sub.getActiveMQDestination())) { ConsumerInfo ci = sub.getConsumerInfo().copy(); ci.setClientId(key.getClientId()); subscriptionInfos.add(ci); } } - //We also need to iterate over all normal subscriptions and check if they are part of - //any dynamicallyIncludedDestination that is configured with forceDurable to be true - //over the network bridge. If forceDurable is true then we want to add the consumer to the set + // We also need to iterate over all normal subscriptions and check if they are part of + // any dynamicallyIncludedDestination that is configured with forceDurable to be true + // over the network bridge. If forceDurable is true then we want to add the consumer to the set for (Subscription sub : topicRegion.getSubscriptions().values()) { if (sub != null && NetworkBridgeUtils.isForcedDurable(sub.getConsumerInfo(), - config.getDynamicallyIncludedDestinations())) { + config.getDynamicallyIncludedDestinations())) { subscriptionInfos.add(sub.getConsumerInfo().copy()); } } try { - //Lastly, if isUseVirtualDestSubs is configured on this broker (to fire advisories) and - //configured on the network connector (to listen to advisories) then also add any virtual - //dest subscription to the set if forceDurable is true for its destination + // Lastly, if isUseVirtualDestSubs is configured on this broker (to fire advisories) and + // configured on the network connector (to listen to advisories) then also add any virtual + // dest subscription to the set if forceDurable is true for its destination AdvisoryBroker ab = (AdvisoryBroker) brokerService.getBroker().getAdaptor(AdvisoryBroker.class); if (ab != null && brokerService.isUseVirtualDestSubs() && config.isUseVirtualDestSubs()) { for (ConsumerInfo info : ab.getVirtualDestinationConsumers().keySet()) { @@ -97,10 +104,9 @@ public static BrokerSubscriptionInfo getBrokerSubscriptionInfo(final BrokerServi } public static boolean isForcedDurable(final ConsumerInfo info, - final List dynamicallyIncludedDestinations) { - return dynamicallyIncludedDestinations != null - ? isForcedDurable(info, - dynamicallyIncludedDestinations.toArray(new ActiveMQDestination[0]), null) : false; + final List dynamicallyIncludedDestinations) { + return dynamicallyIncludedDestinations != null && isForcedDurable(info, + dynamicallyIncludedDestinations.toArray(new ActiveMQDestination[0]), null); } public static boolean isForcedDurable(final ConsumerInfo info, @@ -113,7 +119,7 @@ public static boolean isForcedDurable(final ConsumerInfo info, ActiveMQDestination destination = info.getDestination(); if (AdvisorySupport.isAdvisoryTopic(destination) || destination.isTemporary() || - destination.isQueue()) { + destination.isQueue()) { return false; } @@ -128,44 +134,71 @@ public static boolean isForcedDurable(final ConsumerInfo info, return false; } - public static boolean matchesNetworkConfig(final NetworkBridgeConfiguration config, - ActiveMQDestination destination) { - List includedDests = config.getDynamicallyIncludedDestinations(); - if (includedDests != null && includedDests.size() > 0) { - for (ActiveMQDestination dest : includedDests) { - DestinationFilter inclusionFilter = DestinationFilter.parseFilter(dest); - if (dest != null && inclusionFilter.matches(destination) && dest.getDestinationType() == destination.getDestinationType()) { - return true; - } - } + /** + * This method is used to determine which durable subscriptions should be sent from + * a broker to a remote broker so that the remote broker can process the subscriptions + * to re-add demand when the bridge is first started during the durable sync phase + * of a bridge starting. We can cut down on the amount of durables sent/processed + * based on how the bridge is configured. + * + * @param config + * @param clientId + * @param subscriptionName + * @param destination + * @return + */ + public static boolean matchesConfigForDurableSync(final NetworkBridgeConfiguration config, + String clientId, String subscriptionName, ActiveMQDestination destination) { + + // If consumerTTL was set to 0 then we return false because no demand will be + // generated over the bridge as the messages will be limited to the local broker + if (config.getConsumerTTL() == 0) { + return false; } - return false; + // If this is a remote demand consumer for the current bridge we can also skip + // This consumer was created by another consumer on the remote broker, so we + // ignore this consumer for demand, or else we'd end up with a loop + if (isDirectBridgeConsumer(config, clientId, subscriptionName)) { + return false; + } + + // if TTL is set to 1 then we won't ever handle proxy durables as they + // are at least 2 hops away so the TTL check would always fail. Proxy durables + // are subs for other bridges, so we can skip these as well. + if (config.getConsumerTTL() == 1 && isProxyBridgeSubscription(config, clientId, + subscriptionName)) { + return false; + } + + // Verify the destination matches the dynamically included destination list + return matchesDestinations(config.getDynamicallyIncludedDestinations(), destination); } - public static boolean matchesDestinations(ActiveMQDestination[] dests, final ActiveMQDestination destination) { - if (dests != null && dests.length > 0) { - for (ActiveMQDestination dest : dests) { - DestinationFilter inclusionFilter = DestinationFilter.parseFilter(dest); - if (dest != null && inclusionFilter.matches(destination) && dest.getDestinationType() == destination.getDestinationType()) { + public static boolean matchesDestination(ActiveMQDestination destFilter, ActiveMQDestination destToMatch) { + DestinationFilter inclusionFilter = DestinationFilter.parseFilter(destFilter); + return inclusionFilter.matches(destToMatch) && destFilter.getDestinationType() == destToMatch.getDestinationType(); + } + + public static boolean matchesDestinations(final List includedDests, final ActiveMQDestination destination) { + if (includedDests != null && !includedDests.isEmpty()) { + for (ActiveMQDestination dest : includedDests) { + if (matchesDestination(dest, destination)) { return true; } } } - return false; } public static ActiveMQDestination findMatchingDestination(ActiveMQDestination[] dests, ActiveMQDestination destination) { - if (dests != null && dests.length > 0) { + if (dests != null) { for (ActiveMQDestination dest : dests) { - DestinationFilter inclusionFilter = DestinationFilter.parseFilter(dest); - if (dest != null && inclusionFilter.matches(destination) && dest.getDestinationType() == destination.getDestinationType()) { + if (matchesDestination(dest, destination)) { return dest; } } } - return null; } @@ -181,4 +214,16 @@ public static boolean isDestForcedDurable(final ActiveMQDestination destination) return isForceDurable; } + + public static boolean isDirectBridgeConsumer(NetworkBridgeConfiguration config, String clientId, String subName) { + return (subName != null && subName.startsWith(DURABLE_SUB_PREFIX)) && + (clientId == null || clientId.startsWith(config.getName())); + } + + public static boolean isProxyBridgeSubscription(NetworkBridgeConfiguration config, String clientId, String subName) { + if (subName != null && clientId != null) { + return subName.startsWith(DURABLE_SUB_PREFIX) && !clientId.startsWith(config.getName()); + } + return false; + } } diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/network/DurableFiveBrokerNetworkBridgeTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/network/DurableFiveBrokerNetworkBridgeTest.java index 86672c5577d..876e44dd1ce 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/network/DurableFiveBrokerNetworkBridgeTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/network/DurableFiveBrokerNetworkBridgeTest.java @@ -25,6 +25,7 @@ import javax.jms.MessageConsumer; import javax.jms.Session; +import junit.framework.AssertionFailedError; import org.apache.activemq.JmsMultipleBrokersTestSupport; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.Destination; @@ -49,6 +50,11 @@ public class DurableFiveBrokerNetworkBridgeTest extends JmsMultipleBrokersTestSu @Override protected NetworkConnector bridgeBrokers(String localBrokerName, String remoteBrokerName) throws Exception { + return bridgeBrokers(localBrokerName, remoteBrokerName, false, -1); + } + + protected NetworkConnector bridgeBrokers(String localBrokerName, String remoteBrokerName, + boolean dynamicOnly, int networkTTL) throws Exception { NetworkConnector connector = super.bridgeBrokers(localBrokerName, remoteBrokerName); ArrayList includedDestinations = new ArrayList<>(); includedDestinations.add(new ActiveMQTopic("TEST.FOO?forceDurable=true")); @@ -57,7 +63,8 @@ protected NetworkConnector bridgeBrokers(String localBrokerName, String remoteBr connector.setDecreaseNetworkConsumerPriority(false); connector.setConduitSubscriptions(true); connector.setSyncDurableSubs(true); - connector.setNetworkTTL(-1); + connector.setDynamicOnly(dynamicOnly); + connector.setNetworkTTL(networkTTL); connector.setClientIdToken("|"); return connector; } @@ -604,6 +611,203 @@ public void testDurablePropagationMultipleBridgesDifferentDestinations() throws assertNCDurableSubsCount(brokers.get("Broker_A_A").broker, dest2, 0); } + /* + * The following tests should work for correct sync/propagation of durables even if + * TTL is missing on broker restart. This is for TTL: 0, -1, 1, or 4 (same number + * of network hops in the network) + */ + public void testDurablePropagationSyncTtl0BrokerRestart() throws Exception { + testDurablePropagation(0, false, true, List.of(0, 0, 0, 0, 0)); + } + + public void testDurablePropagationSyncTtl1BrokerRestart() throws Exception { + testDurablePropagation(1, false, true, List.of(0, 1, 0, 1, 0)); + } + + public void testDurablePropagationSyncTtl4BrokerRestart() throws Exception { + testDurablePropagation(4, false, true, List.of(1, 2, 2, 2, 1)); + } + + // This test also tests the fix in NetworkBridgeUtils.getBrokerSubscriptionInfo() + // where the clientId was missing for the offline durable which could cause demand to be created + // by mistake and create a loop. Without the clientId the sync could not tell the + // network durable was for its direct bridge (was created because of a local consumer + // on its broker) so a loop could be created on restart if the sync happened before the + // real consumer connected first. + public void testDurablePropagationSyncTtlNotSetBrokerRestart() throws Exception { + testDurablePropagation(-1, false, true, List.of(1, 2, 2, 2, 1)); + } + + /* + * The following test demonstrates the problem with missing TTL and is only solved + * by making dynamicOnly true, on restart consumers have yet to come back online + * for offline durables so we end up missing TTL and create extra demand on sync. + * TTL is missing on broker restart. This is for TTL > 1 but less than same number + * of network hops in the network + */ + public void testDurablePropagationDynamicFalseTtl2BrokerRestartFail() throws Exception { + try { + testDurablePropagation(2, false, true, List.of(0, 1, 2, 1, 0)); + fail("Exepected to fail"); + } catch (AssertionFailedError e) { + // expected + } + } + + public void testDurablePropagationDynamicFalseTtl3BrokerRestartFail() throws Exception { + try { + testDurablePropagation(2, false, true, List.of(0, 2, 2, 2, 0)); + fail("Exepected to fail"); + } catch (AssertionFailedError e) { + // expected + } + } + + /* + * The following tests make sure propagation works correctly with TTL set + * when dynamicOnly is false or true, and durable sync enabled. + * When false, durables get reactivated and a sync is done and TTL can be + * missing for offline durables so this works correctly ONLY if the consumers + * are online and have correct TTL info. When true, consumers drive reactivation + * entirely and the sync is skipped. + * + * For dynamicOnly being false, these tests for TTL > 1 demonstrate the improvement in + * createDemandSubscription() inside of DemandForwardingBridgeSupport + * where we now include the full broker path (all TTL info) if the + * consumer is already online that created the subscription. Before, + * we just included the remote broker. + * + * If brokers are restarted/consumers offline, TTL can be missing so it's recommended + * to set dynamicOnly to true if TTL is > 1 (TTL 1 and TTL -1 can still be handled) + * to prevent propagation of durables that shouldn't exist and let consumers drive + * the reactivation. + * + * The tests keep the consumers online and only restart the connectors and not + * the brokers so the consumer info and TTL are preserved for the tests. + */ + public void testDurablePropagationDynamicFalseTtl0() throws Exception { + testDurablePropagation(0, false, List.of(0, 0, 0, 0, 0)); + } + + public void testDurablePropagationDynamicFalseTtl1() throws Exception { + testDurablePropagation(1, false, List.of(0, 1, 0, 1, 0)); + } + + public void testDurablePropagationDynamicFalseTtl2() throws Exception { + testDurablePropagation(2, false, List.of(0, 1, 2, 1, 0)); + } + + public void testDurablePropagationDynamicFalseTtl3() throws Exception { + testDurablePropagation(3, false, List.of(0, 2, 2, 2, 0)); + } + + public void testDurablePropagationDynamicFalseTtl4() throws Exception { + testDurablePropagation(4, false, List.of(1, 2, 2, 2, 1)); + } + + public void testDurablePropagationDynamicFalseTtlNotSet() throws Exception { + testDurablePropagation(-1, false, List.of(1, 2, 2, 2, 1)); + } + + public void testDurablePropagationDynamicTrueTtl0() throws Exception { + testDurablePropagation(0, true, List.of(0, 0, 0, 0, 0)); + } + + public void testDurablePropagationDynamicTrueTtl1() throws Exception { + testDurablePropagation(1, true, List.of(0, 1, 0, 1, 0)); + } + + public void testDurablePropagationDynamicTrueTtl2() throws Exception { + testDurablePropagation(2, true, List.of(0, 1, 2, 1, 0)); + } + + public void testDurablePropagationDynamicTrueTtl3() throws Exception { + testDurablePropagation(3, true, List.of(0, 2, 2, 2, 0)); + } + + public void testDurablePropagationDynamicTrueTtl4() throws Exception { + testDurablePropagation(4, true, List.of(1, 2, 2, 2, 1)); + } + + public void testDurablePropagationDynamicTrueTtlNotSet() throws Exception { + testDurablePropagation(-1, true, List.of(1, 2, 2, 2, 1)); + } + + private void testDurablePropagation(int ttl, boolean dynamicOnly, + List expected) throws Exception { + testDurablePropagation(ttl, dynamicOnly, false, expected); + } + + private void testDurablePropagation(int ttl, boolean dynamicOnly, boolean restartBrokers, + List expected) throws Exception { + duplex = true; + + // Setup broker networks + NetworkConnector nc1 = bridgeBrokers("Broker_A_A", "Broker_B_B", dynamicOnly, ttl); + NetworkConnector nc2 = bridgeBrokers("Broker_B_B", "Broker_C_C", dynamicOnly, ttl); + NetworkConnector nc3 = bridgeBrokers("Broker_C_C", "Broker_D_D", dynamicOnly, ttl); + NetworkConnector nc4 = bridgeBrokers("Broker_D_D", "Broker_E_E", dynamicOnly, ttl); + + startAllBrokers(); + stopNetworkConnectors(nc1, nc2, nc3, nc4); + + // Setup destination + ActiveMQTopic dest = (ActiveMQTopic) createDestination("TEST.FOO", true); + + // Setup consumers + Session ses = createSession("Broker_A_A"); + Session ses2 = createSession("Broker_E_E"); + MessageConsumer clientA = ses.createDurableSubscriber(dest, "subA"); + MessageConsumer clientE = ses2.createDurableSubscriber(dest, "subE"); + Thread.sleep(1000); + + assertNCDurableSubsCount(brokers.get("Broker_A_A").broker, dest, 0); + assertNCDurableSubsCount(brokers.get("Broker_B_B").broker, dest, 0); + assertNCDurableSubsCount(brokers.get("Broker_C_C").broker, dest, 0); + assertNCDurableSubsCount(brokers.get("Broker_D_D").broker, dest, 0); + assertNCDurableSubsCount(brokers.get("Broker_E_E").broker, dest, 0); + + startNetworkConnectors(nc1, nc2, nc3, nc4); + Thread.sleep(1000); + + // Check that the correct network durables exist + assertNCDurableSubsCount(brokers.get("Broker_A_A").broker, dest, expected.get(0)); + assertNCDurableSubsCount(brokers.get("Broker_B_B").broker, dest, expected.get(1)); + assertNCDurableSubsCount(brokers.get("Broker_C_C").broker, dest, expected.get(2)); + assertNCDurableSubsCount(brokers.get("Broker_D_D").broker, dest, expected.get(3)); + assertNCDurableSubsCount(brokers.get("Broker_E_E").broker, dest, expected.get(4)); + + if (restartBrokers) { + // go offline and restart to make sure sync works to re-enable and doesn't + // propagate wrong demand + clientA.close(); + clientE.close(); + destroyAllBrokers(); + setUp(); + brokers.values().forEach(bi -> bi.broker.setDeleteAllMessagesOnStartup(false)); + bridgeBrokers("Broker_A_A", "Broker_B_B", dynamicOnly, ttl); + bridgeBrokers("Broker_B_B", "Broker_C_C", dynamicOnly, ttl); + bridgeBrokers("Broker_C_C", "Broker_D_D", dynamicOnly, ttl); + bridgeBrokers("Broker_D_D", "Broker_E_E", dynamicOnly, ttl); + startAllBrokers(); + } else { + // restart just the network connectors but leave the consumers online + // to test sync works ok. Things should work for all cases both dynamicOnly + // false and true because TTL info still exits and consumers are online + stopNetworkConnectors(nc1, nc2, nc3, nc4); + Thread.sleep(1000); + startNetworkConnectors(nc1, nc2, nc3, nc4); + Thread.sleep(1000); + } + + // after restarting the bridges, check sync/demand are correct + assertNCDurableSubsCount(brokers.get("Broker_A_A").broker, dest, expected.get(0)); + assertNCDurableSubsCount(brokers.get("Broker_B_B").broker, dest, expected.get(1)); + assertNCDurableSubsCount(brokers.get("Broker_C_C").broker, dest, expected.get(2)); + assertNCDurableSubsCount(brokers.get("Broker_D_D").broker, dest, expected.get(3)); + assertNCDurableSubsCount(brokers.get("Broker_E_E").broker, dest, expected.get(4)); + } + protected void assertNCDurableSubsCount(final BrokerService brokerService, final ActiveMQTopic dest, final int count) throws Exception { assertTrue(Wait.waitFor(new Condition() { @@ -628,7 +832,7 @@ protected List getNCDurableSubs(final BrokerService br for (SubscriptionKey key : destination.getDurableTopicSubs().keySet()) { if (key.getSubscriptionName().startsWith(DemandForwardingBridge.DURABLE_SUB_PREFIX)) { DurableTopicSubscription sub = destination.getDurableTopicSubs().get(key); - if (sub != null) { + if (sub != null && sub.isActive()) { subs.add(sub); } } @@ -657,6 +861,18 @@ protected void configureBroker(BrokerService broker) { broker.setDataDirectory("target" + File.separator + "test-data" + File.separator + "DurableFiveBrokerNetworkBridgeTest"); } + protected void startNetworkConnectors(NetworkConnector... connectors) throws Exception { + for (NetworkConnector connector : connectors) { + connector.start(); + } + } + + protected void stopNetworkConnectors(NetworkConnector... connectors) throws Exception { + for (NetworkConnector connector : connectors) { + connector.stop(); + } + } + protected Session createSession(String broker) throws Exception { Connection con = createConnection(broker); con.start(); From 54775454fbcd47b9d8176974461c65dc089a7af4 Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Wed, 7 May 2025 10:39:10 -0400 Subject: [PATCH 06/34] AMQ-9698 - Fix message expiration on durable subs (#1423) This commit fixes multiple problems with handling message expiration on durable topic subscriptions. 1) Memory usage tracking is fixed on expiration by correctly decrementing the counter inside AbstractStoreCursor when calling the remove(message) method, which was previously missed. 2) A new refrence type is used to wrap references in TopicStorePrefetch so that if multiple subscriptions share a reference in their cursors each one can expire the message. Previously only one would expire as the message would be marked as expired and skipped. 3) On client expiration, the references are properly decremented so memory tracking is correct. 4) The expiration thread for Topics has been improved to be much more efficient for KahaDB by only scanning for expired messages if there are durables eligible for expiration. The thread also now checks the index to see if expired messages are associated with the subs still so we don't expire the same sub multiple times. Only messages that need to still be processed are returned which further cuts down memory usage. (cherry picked from commit 4abcfa1ad6808ba6786cf402b4feedef3659d0a2) (cherry picked from commit 9a1f00b0f0b44e8dd781367fdf32d311e4f75fd3) --- .../region/DurableTopicSubscription.java | 10 + .../broker/region/PrefetchSubscription.java | 8 +- .../apache/activemq/broker/region/Topic.java | 64 +++- .../region/cursors/AbstractStoreCursor.java | 12 +- .../region/cursors/TopicStorePrefetch.java | 27 ++ .../apache/activemq/store/MessageStore.java | 8 + .../activemq/store/ProxyMessageStore.java | 4 + .../store/ProxyTopicMessageStore.java | 9 + .../activemq/store/TopicMessageStore.java | 18 ++ .../store/memory/MemoryMessageStore.java | 5 + .../store/memory/MemoryTopicMessageStore.java | 6 + .../activemq/store/jdbc/JDBCMessageStore.java | 6 + .../store/jdbc/JDBCTopicMessageStore.java | 7 + .../store/journal/JournalMessageStore.java | 7 + .../journal/JournalTopicMessageStore.java | 8 + .../activemq/store/kahadb/KahaDBStore.java | 61 +++- .../store/kahadb/TempKahaDBStore.java | 11 + ...sSendReceiveWithMessageExpirationTest.java | 86 +++++- .../broker/MessageExpirationTest.java | 7 + .../AbstractPendingMessageCursorTest.java | 2 +- .../StoreCursorRemoveFromCacheTest.java | 143 +++++++++ .../cursors/StoreQueueCursorOrderTest.java | 5 + .../bugs/MessageExpirationReaperTest.java | 58 +++- .../kahadb/KahaDBRecoverExpiredTest.java | 286 ++++++++++++++++++ 24 files changed, 837 insertions(+), 21 deletions(-) create mode 100644 activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/StoreCursorRemoveFromCacheTest.java create mode 100644 activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBRecoverExpiredTest.java diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/DurableTopicSubscription.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/DurableTopicSubscription.java index 13106cd0f34..bcdb4948136 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/DurableTopicSubscription.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/DurableTopicSubscription.java @@ -298,6 +298,16 @@ public void removePending(MessageReference node) throws IOException { pending.remove(node); } + @Override + protected void processExpiredAck(ConnectionContext context, Destination dest, + MessageReference node) { + + // Each subscription needs to expire both on the store and + // decrement the reference count + super.processExpiredAck(context, dest, node); + node.decrementReferenceCount(); + } + @Override protected void doAddRecoveredMessage(MessageReference message) throws Exception { synchronized (pending) { diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/PrefetchSubscription.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/PrefetchSubscription.java index 93b3b2ae576..6bf17af6f4a 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/PrefetchSubscription.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/PrefetchSubscription.java @@ -298,9 +298,8 @@ public final void acknowledge(final ConnectionContext context,final MessageAck a inAckRange = true; } if (inAckRange) { - Destination regionDestination = nodeDest; if (broker.isExpired(node)) { - regionDestination.messageExpired(context, this, node); + processExpiredAck(context, nodeDest, node); } iter.remove(); decrementPrefetchCounter(node); @@ -396,6 +395,11 @@ public final void acknowledge(final ConnectionContext context,final MessageAck a } } + protected void processExpiredAck(final ConnectionContext context, final Destination dest, + final MessageReference node) { + dest.messageExpired(context, this, node); + } + private void registerRemoveSync(ConnectionContext context, final MessageReference node) { // setup a Synchronization to remove nodes from the // dispatched list. diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java index 92e50c04bf4..4fcd508cf9e 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java @@ -21,6 +21,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -29,6 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; import org.apache.activemq.advisory.AdvisorySupport; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.ConnectionContext; @@ -53,6 +56,7 @@ import org.apache.activemq.filter.NonCachedMessageEvaluationContext; import org.apache.activemq.management.MessageFlowStats; import org.apache.activemq.store.MessageRecoveryListener; +import org.apache.activemq.store.MessageStore.StoreType; import org.apache.activemq.store.NoLocalSubscriptionAware; import org.apache.activemq.store.PersistenceAdapter; import org.apache.activemq.store.TopicMessageStore; @@ -826,14 +830,60 @@ protected void dispatch(final ConnectionContext context, Message message) throws } private final AtomicBoolean expiryTaskInProgress = new AtomicBoolean(false); - private final Runnable expireMessagesWork = new Runnable() { - @Override - public void run() { - List browsedMessages = new InsertionCountList(); - doBrowse(browsedMessages, getMaxExpirePageSize()); + private final Runnable expireMessagesWork = () -> { + try { + final TopicMessageStore store = Topic.this.topicStore; + if (store != null && store.getType() == StoreType.KAHADB) { + if (store.getMessageCount() == 0) { + LOG.debug("Skipping topic expiration check for {}, store size is 0", destination); + return; + } + + // get the sub keys that should be checked for expired messages + final var subs = durableSubscribers.entrySet().stream() + .filter(entry -> isEligibleForExpiration(entry.getValue())) + .map(Entry::getKey).collect(Collectors.toSet()); + + if (subs.isEmpty()) { + LOG.debug("Skipping topic expiration check for {}, no eligible subscriptions to check", destination); + return; + } + + // For each eligible subscription, return the messages in the store that are expired + // The same message refs are shared between subs if duplicated so this is efficient + var expired = store.recoverExpired(subs, getMaxExpirePageSize()); + + final ConnectionContext connectionContext = createConnectionContext(); + // Go through any expired messages and remove for each sub + for (Entry> entry : expired.entrySet()) { + DurableTopicSubscription sub = durableSubscribers.get(entry.getKey()); + List expiredMessages = entry.getValue(); + + // If the sub still exists and there are expired messages then process + if (sub != null && !expiredMessages.isEmpty()) { + // There's a small race condition here if the sub comes online, + // but it's not a big deal as at worst there maybe be duplicate acks for + // the expired message but the store can handle it + if (isEligibleForExpiration(sub)) { + expiredMessages.forEach(message -> { + message.setRegionDestination(Topic.this); + messageExpired(connectionContext, sub, message); + }); + } + } + } + } else { + // If not KahaDB, fall back to the legacy browse method because + // the recoverExpired() method is not supported + doBrowse(new InsertionCountList<>(), getMaxExpirePageSize()); + } + } catch (Throwable e) { + LOG.warn("Failed to expire messages on Topic: {}", getActiveMQDestination().getPhysicalName(), e); + } finally { expiryTaskInProgress.set(false); } }; + private final Runnable expireMessagesTask = new Runnable() { @Override public void run() { @@ -927,6 +977,10 @@ private void clearPendingAndDispatch(DurableTopicSubscription durableTopicSubscr } } + private static boolean isEligibleForExpiration(DurableTopicSubscription sub) { + return sub.isEnableMessageExpirationOnActiveDurableSubs() || !sub.isActive(); + } + public Map getDurableTopicSubs() { return durableSubscribers; } diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/AbstractStoreCursor.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/AbstractStoreCursor.java index 30089e35515..8eec87e09fa 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/AbstractStoreCursor.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/AbstractStoreCursor.java @@ -114,7 +114,7 @@ public synchronized boolean recoverMessage(Message message, boolean cached) thro } } message.incrementReferenceCount(); - batchList.addMessageLast(message); + batchList.addMessageLast(createBatchListRef(message)); clearIterator(true); recovered = true; } else if (!cached) { @@ -136,6 +136,10 @@ public synchronized boolean recoverMessage(Message message, boolean cached) thro return recovered; } + protected MessageReference createBatchListRef(Message message) { + return message; + } + protected boolean duplicateFromStoreExcepted(Message message) { // expected for messages pending acks with kahadb.concurrentStoreAndDispatchQueues=true for // which this existing unused flag has been repurposed @@ -448,13 +452,15 @@ public final synchronized void remove() { @Override public final synchronized void remove(MessageReference node) { - if (batchList.remove(node) != null) { + final PendingNode message = batchList.remove(node); + if (message != null) { size--; setCacheEnabled(false); + // decrement reference count if removed from batchList + message.getMessage().decrementReferenceCount(); } } - @Override public final synchronized void clear() { gc(); diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/TopicStorePrefetch.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/TopicStorePrefetch.java index a97b1e8b504..6b75806cc18 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/TopicStorePrefetch.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/TopicStorePrefetch.java @@ -16,6 +16,8 @@ */ package org.apache.activemq.broker.region.cursors; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.activemq.broker.region.IndirectMessageReference; import org.apache.activemq.broker.region.MessageReference; import org.apache.activemq.broker.region.Subscription; import org.apache.activemq.broker.region.Topic; @@ -151,6 +153,11 @@ protected void doFillBatch() throws Exception { } } + @Override + protected MessageReference createBatchListRef(Message message) { + return new TopicStoreMessageReference(message); + } + public byte getLastRecoveredPriority() { return lastRecoveredPriority; } @@ -168,4 +175,24 @@ public Subscription getSubscription() { public String toString() { return "TopicStorePrefetch(" + clientId + "," + subscriberName + ",storeHasMessages=" + this.storeHasMessages +") " + this.subscription.getConsumerInfo().getConsumerId() + " - " + super.toString(); } + + // This extends IndirectMessageReference to allow expiring messages for multiple + // durable subscriptions. Each durable subscription needs to ack the message in the store so + // each durable sub will now get their own reference so that the subscription can expire + // correctly and not just the first subscription. + static class TopicStoreMessageReference extends IndirectMessageReference { + private final AtomicBoolean processAsExpired = new AtomicBoolean(false); + + public TopicStoreMessageReference(Message message) { + super(message); + } + + @Override + public boolean canProcessAsExpired() { + // mark original message ref as expired, this won't be used + // by this class but someone may get the original message and check it + super.canProcessAsExpired(); + return processAsExpired.compareAndSet(false, true); + } + } } diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/MessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/MessageStore.java index aa0e85e0ec3..0339d72d986 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/MessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/MessageStore.java @@ -215,4 +215,12 @@ default void recoverMessages(final MessageRecoveryContext messageRecoveryContext void registerIndexListener(IndexListener indexListener); + StoreType getType(); + + enum StoreType { + MEMORY, JDBC, KAHADB, TEMP_KAHADB, + // deprecated, removed in 6.x + JOURNAL + } + } diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/ProxyMessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/ProxyMessageStore.java index d2f953aef5b..dfb369bd18f 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/ProxyMessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/ProxyMessageStore.java @@ -185,4 +185,8 @@ public MessageStoreStatistics getMessageStoreStatistics() { return delegate.getMessageStoreStatistics(); } + @Override + public StoreType getType() { + return delegate.getType(); + } } diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/ProxyTopicMessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/ProxyTopicMessageStore.java index 09b6529fa9e..23c9e81809d 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/ProxyTopicMessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/ProxyTopicMessageStore.java @@ -18,6 +18,9 @@ import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.activemq.broker.ConnectionContext; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.Message; @@ -25,6 +28,7 @@ import org.apache.activemq.command.MessageId; import org.apache.activemq.command.SubscriptionInfo; import org.apache.activemq.usage.MemoryUsage; +import org.apache.activemq.util.SubscriptionKey; /** * A simple proxy that delegates to another MessageStore. @@ -235,4 +239,9 @@ public long getMessageSize(String clientId, String subscriberName) public MessageStoreSubscriptionStatistics getMessageStoreSubStatistics() { return ((TopicMessageStore)delegate).getMessageStoreSubStatistics(); } + + @Override + public Map> recoverExpired(Set subs, int max) throws Exception { + return ((TopicMessageStore)delegate).recoverExpired(subs, max); + } } diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/TopicMessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/TopicMessageStore.java index 95d5b72ed54..6ee046cff6a 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/TopicMessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/TopicMessageStore.java @@ -20,10 +20,15 @@ import javax.jms.JMSException; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.activemq.broker.ConnectionContext; +import org.apache.activemq.command.Message; import org.apache.activemq.command.MessageAck; import org.apache.activemq.command.MessageId; import org.apache.activemq.command.SubscriptionInfo; +import org.apache.activemq.util.SubscriptionKey; /** * A MessageStore for durable topic subscriptions @@ -154,4 +159,17 @@ public interface TopicMessageStore extends MessageStore { * @throws IOException */ void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroactive) throws IOException; + + /** + * Iterates over the pending messages in a topic and recovers any expired messages found for + * each of the subscriptions up to the maximum number of messages to search. Only subscriptions + * that have at least 1 expired message will be returned in the map. + * + * @param subs + * @param max + * @return Expired messages for each subscription + * @throws Exception + */ + Map> recoverExpired(Set subs, int max) throws Exception; + } diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryMessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryMessageStore.java index 7a0f69b04ea..3b3128649cc 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryMessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryMessageStore.java @@ -177,6 +177,11 @@ public void recoverMessageStoreStatistics() throws IOException { } } + @Override + public StoreType getType() { + return StoreType.MEMORY; + } + protected static final void incMessageStoreStatistics(final MessageStoreStatistics stats, final Message message) { if (stats != null && message != null) { stats.getMessageCount().increment(); diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryTopicMessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryTopicMessageStore.java index dd8be2be8ea..13a13c0759a 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryTopicMessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryTopicMessageStore.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.activemq.broker.ConnectionContext; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.Message; @@ -180,6 +181,11 @@ public void resetBatching(String clientId, String subscriptionName) { } } + @Override + public Map> recoverExpired(Set subs, int max) { + throw new UnsupportedOperationException("recoverExpired not supported"); + } + // Disabled for the memory store, can be enabled later if necessary private final MessageStoreSubscriptionStatistics stats = new MessageStoreSubscriptionStatistics(false); diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCMessageStore.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCMessageStore.java index 8adc2f78ee9..78c27b71839 100644 --- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCMessageStore.java +++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCMessageStore.java @@ -482,4 +482,10 @@ public String toString() { return destination.getPhysicalName() + ",pendingSize:" + pendingAdditions.size(); } + + @Override + public StoreType getType() { + return StoreType.JDBC; + } + } diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCTopicMessageStore.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCTopicMessageStore.java index a857bcf4ef7..7f29a46a64d 100644 --- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCTopicMessageStore.java +++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCTopicMessageStore.java @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -40,6 +41,7 @@ import org.apache.activemq.store.TopicMessageStore; import org.apache.activemq.util.ByteSequence; import org.apache.activemq.util.IOExceptionSupport; +import org.apache.activemq.util.SubscriptionKey; import org.apache.activemq.wireformat.WireFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -360,6 +362,11 @@ public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroacti } } + @Override + public Map> recoverExpired(Set subs, int max) { + throw new UnsupportedOperationException("recoverExpired not supported"); + } + /** * @see org.apache.activemq.store.TopicMessageStore#lookupSubscription(String, * String) diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalMessageStore.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalMessageStore.java index 7ec10c4d9a6..b1f6ba5576c 100644 --- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalMessageStore.java +++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalMessageStore.java @@ -16,6 +16,8 @@ */ package org.apache.activemq.store.journal; +import static org.apache.activemq.store.MessageStore.StoreType.JOURNAL; + import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -426,4 +428,9 @@ public void setBatch(MessageId messageId) throws Exception { longTermStore.setBatch(messageId); } + @Override + public StoreType getType() { + return JOURNAL; + } + } diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalTopicMessageStore.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalTopicMessageStore.java index 7aa61a269c2..f4971f67376 100644 --- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalTopicMessageStore.java +++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalTopicMessageStore.java @@ -20,6 +20,9 @@ import java.util.HashMap; import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.activeio.journal.RecordLocation; import org.apache.activemq.broker.ConnectionContext; import org.apache.activemq.command.ActiveMQTopic; @@ -81,6 +84,11 @@ public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroacti longTermStore.addSubscription(subscriptionInfo, retroactive); } + @Override + public Map> recoverExpired(Set subs, int max) { + throw new UnsupportedOperationException("recoverExpired not supported"); + } + @Override public void addMessage(ConnectionContext context, Message message) throws IOException { super.addMessage(context, message); diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java index b987eea6d13..0f5406f7a27 100644 --- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java @@ -83,12 +83,14 @@ import org.apache.activemq.store.kahadb.data.KahaUpdateMessageCommand; import org.apache.activemq.store.kahadb.disk.journal.Location; import org.apache.activemq.store.kahadb.disk.page.Transaction; +import org.apache.activemq.store.kahadb.disk.page.Transaction.CallableClosure; import org.apache.activemq.store.kahadb.disk.util.SequenceSet; import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl; import org.apache.activemq.usage.MemoryUsage; import org.apache.activemq.usage.SystemUsage; import org.apache.activemq.util.IOExceptionSupport; import org.apache.activemq.util.ServiceStopper; +import org.apache.activemq.util.SubscriptionKey; import org.apache.activemq.util.ThreadPoolUtils; import org.apache.activemq.wireformat.WireFormat; import org.slf4j.Logger; @@ -958,9 +960,14 @@ public MessageStoreStatistics execute(Transaction tx) throws IOException { unlockAsyncJobQueue(); } } + + @Override + public StoreType getType() { + return StoreType.KAHADB; + } } - class KahaDBTopicMessageStore extends KahaDBMessageStore implements TopicMessageStore { + class KahaDBTopicMessageStore extends KahaDBMessageStore implements TopicMessageStore{ private final AtomicInteger subscriptionCount = new AtomicInteger(); protected final MessageStoreSubscriptionStatistics messageStoreSubStats = new MessageStoreSubscriptionStatistics(isEnableSubscriptionStatistics()); @@ -1323,6 +1330,58 @@ public void execute(Transaction tx) throws Exception { } } + @Override + public Map> recoverExpired(Set subscriptions, int max) throws Exception { + indexLock.writeLock().lock(); + try { + return pageFile.tx().execute( + (CallableClosure>, Exception>) tx -> { + StoredDestination sd = getStoredDestination(dest, tx); + sd.orderIndex.resetCursorPosition(); + int count = 0; + final Map> expired = new HashMap<>(); + final Map subKeys = new HashMap<>(); + + // Check each subscription and track the ones that exist + for (SubscriptionKey sub : subscriptions) { + final String subKeyString = subscriptionKey(sub.getClientId(), sub.getSubscriptionName()); + if (sd.subscriptionCache.contains(subKeyString)) { + subKeys.put(subKeyString, sub); + } + } + + // Iterate one time through the topic and check each message, stopping if we run out + // or reach the max + for (Iterator> iterator = + sd.orderIndex.iterator(tx, new MessageOrderCursor()); count < max && iterator.hasNext(); ) { + count++; + Entry entry = iterator.next(); + Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName()); + if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) { + continue; + } + + final Message msg = loadMessage(entry.getValue().location); + if (msg.isExpired()) { + // For every message that is expired, go through and check each subscription to see + // if the message has already been acked. We don't want to return subs that have already + // acked the message. + for(Entry subKeyEntry : subKeys.entrySet()) { + SequenceSet sequence = sd.ackPositions.get(tx, subKeyEntry.getKey()); + if (sequence != null && sequence.contains(entry.getKey())) { + List expMessages = expired.computeIfAbsent(subKeyEntry.getValue(), m -> new ArrayList<>()); + expMessages.add(msg); + } + } + } + } + return expired; + }); + } finally { + indexLock.writeLock().unlock(); + } + } + @Override public void resetBatching(String clientId, String subscriptionName) { try { diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java index f7a49b8c186..942076b2f1b 100644 --- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -63,6 +64,7 @@ import org.apache.activemq.usage.MemoryUsage; import org.apache.activemq.usage.SystemUsage; import org.apache.activemq.util.ByteSequence; +import org.apache.activemq.util.SubscriptionKey; import org.apache.activemq.wireformat.WireFormat; public class TempKahaDBStore extends TempMessageDatabase implements PersistenceAdapter, BrokerServiceAware { @@ -299,6 +301,10 @@ public Integer execute(Transaction tx) throws IOException { getMessageStoreStatistics().getMessageCount().setCount(count); } + @Override + public StoreType getType() { + return StoreType.TEMP_KAHADB; + } } class KahaDBTopicMessageStore extends KahaDBMessageStore implements TopicMessageStore { @@ -332,6 +338,11 @@ public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroacti process(command); } + @Override + public Map> recoverExpired(Set subs, int max) { + throw new UnsupportedOperationException("recoverExpired not supported"); + } + @Override public void deleteSubscription(String clientId, String subscriptionName) throws IOException { KahaSubscriptionCommand command = new KahaSubscriptionCommand(); diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/JmsSendReceiveWithMessageExpirationTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/JmsSendReceiveWithMessageExpirationTest.java index 391253ee70c..2f61e41a3e0 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/JmsSendReceiveWithMessageExpirationTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/JmsSendReceiveWithMessageExpirationTest.java @@ -16,6 +16,8 @@ */ package org.apache.activemq; +import static org.junit.Assert.assertEquals; + import java.util.Date; import java.util.Vector; import java.util.concurrent.TimeUnit; @@ -31,8 +33,12 @@ import javax.jms.Topic; import org.apache.activemq.broker.BrokerRegistry; +import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.DestinationStatistics; +import org.apache.activemq.broker.region.DurableTopicSubscription; +import org.apache.activemq.broker.region.Subscription; import org.apache.activemq.command.ActiveMQDestination; +import org.apache.activemq.store.TopicMessageStore; import org.apache.activemq.util.Wait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,15 +57,20 @@ public class JmsSendReceiveWithMessageExpirationTest extends TestSupport { protected Destination producerDestination; protected boolean durable; protected int deliveryMode = DeliveryMode.PERSISTENT; - protected long timeToLive = 5000; + protected long timeToLive = 3000; protected boolean verbose; protected Connection connection; + protected BrokerService brokerService; protected void setUp() throws Exception { super.setUp(); + brokerService = new BrokerService(); + brokerService.setPersistent(false); + brokerService.start(); + data = new String[messageCount]; for (int i = 0; i < messageCount; i++) { @@ -225,7 +236,8 @@ public void testConsumeExpiredTopic() throws Exception { consumerDestination = session.createTopic(getConsumerSubject()); producerDestination = session.createTopic(getProducerSubject()); - MessageConsumer consumer = createConsumer(); + MessageConsumer consumer1 = createConsumer(); + MessageConsumer consumer2 = session.createConsumer(consumerDestination); connection.start(); for (int i = 0; i < data.length; i++) { @@ -247,7 +259,64 @@ public void testConsumeExpiredTopic() throws Exception { Thread.sleep(timeToLive + 1000); // message should have expired. - assertNull(consumer.receive(1000)); + assertNull(consumer1.receive(1000)); + assertNull(consumer2.receive(100)); + + for (Subscription consumer : brokerService.getDestination( + (ActiveMQDestination) consumerDestination) + .getConsumers()) { + assertEquals(0, consumer.getPendingQueueSize()); + } + + // Memory usage should be 0 after expiration + assertEquals(0, brokerService.getDestination((ActiveMQDestination) consumerDestination) + .getMemoryUsage().getUsage()); + } + + public void testConsumeExpiredTopicDurable() throws Exception { + brokerService.stop(); + + // Use persistent broker and durables so restart + brokerService = new BrokerService(); + brokerService.setPersistent(true); + brokerService.start(); + connection.close(); + connection = createConnection(); + connection.setClientID(getClass().getName()); + connection.start(); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + MessageProducer producer = createProducer(timeToLive); + Topic topic = session.createTopic("test.expiration.topic"); + MessageConsumer consumer1 = session.createDurableSubscriber(topic, "sub1"); + MessageConsumer consumer2 = session.createDurableSubscriber(topic, "sub2"); + + for (int i = 0; i < data.length; i++) { + Message message = session.createTextMessage(data[i]); + message.setStringProperty("stringProperty", data[i]); + message.setIntProperty("intProperty", i); + producer.send(topic, message); + } + + // sleeps a second longer than the expiration time. + // Basically waits till topic expires. + Thread.sleep(timeToLive + 1000); + + // message should have expired for both clients + assertNull(consumer1.receive(1000)); + assertNull(consumer2.receive(100)); + + TopicMessageStore store = (TopicMessageStore) brokerService.getDestination((ActiveMQDestination) topic).getMessageStore(); + assertEquals(0, store.getMessageCount(getClass().getName(), "sub1")); + assertEquals(0, store.getMessageCount(getClass().getName(), "sub2")); + + for (Subscription consumer : brokerService.getDestination((ActiveMQDestination) topic) + .getConsumers()) { + assertEquals(0, consumer.getPendingQueueSize()); + } + // Memory usage should be 0 after expiration + assertEquals(0, brokerService.getDestination((ActiveMQDestination) topic) + .getMemoryUsage().getUsage()); } /** @@ -303,8 +372,15 @@ protected void tearDown() throws Exception { LOG.info("Dumping stats..."); LOG.info("Closing down connection"); - session.close(); - connection.close(); + try { + session.close(); + connection.close(); + } catch (Exception e) { + // ignore + } + if (brokerService != null) { + brokerService.stop(); + } } } diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/MessageExpirationTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/MessageExpirationTest.java index 5c7f29d257e..2f3ee82ad2e 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/MessageExpirationTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/MessageExpirationTest.java @@ -18,6 +18,7 @@ import javax.jms.DeliveryMode; + import junit.framework.Test; import org.apache.activemq.broker.region.policy.PolicyEntry; @@ -30,6 +31,7 @@ import org.apache.activemq.command.MessageAck; import org.apache.activemq.command.ProducerInfo; import org.apache.activemq.command.SessionInfo; +import org.apache.activemq.util.Wait; public class MessageExpirationTest extends BrokerTestSupport { @@ -261,6 +263,11 @@ public void testMessagesInSubscriptionPendingListExpire() throws Exception { assertNoMessagesLeft(connection); connection.send(closeConnectionInfo(connectionInfo)); + + if (!destination.isTemporary()) { + assertTrue(Wait.waitFor( + () -> broker.getDestination(destination).getMemoryUsage().getUsage() == 0, 1000, 100)); + } } public static Test suite() { diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/AbstractPendingMessageCursorTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/AbstractPendingMessageCursorTest.java index cd92da6969b..e6d0537f059 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/AbstractPendingMessageCursorTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/AbstractPendingMessageCursorTest.java @@ -76,7 +76,7 @@ public abstract class AbstractPendingMessageCursorTest extends AbstractStoreStat protected boolean enableSubscriptionStatistics; @Rule - public Timeout globalTimeout= new Timeout(60, TimeUnit.SECONDS); + public Timeout globalTimeout = new Timeout(60, TimeUnit.SECONDS); /** * @param prioritizedMessages diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/StoreCursorRemoveFromCacheTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/StoreCursorRemoveFromCacheTest.java new file mode 100644 index 00000000000..4dd93646ed0 --- /dev/null +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/StoreCursorRemoveFromCacheTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.activemq.broker.region.cursors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.function.BiConsumer; +import org.apache.activemq.broker.BrokerService; +import org.apache.activemq.broker.region.DestinationStatistics; +import org.apache.activemq.broker.region.MessageReference; +import org.apache.activemq.broker.region.Queue; +import org.apache.activemq.command.ActiveMQQueue; +import org.apache.activemq.command.ActiveMQTextMessage; +import org.apache.activemq.command.MessageId; +import org.apache.activemq.store.MessageStore; +import org.apache.activemq.store.kahadb.KahaDBStore; +import org.apache.activemq.usage.SystemUsage; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class StoreCursorRemoveFromCacheTest { + + @Rule + public TemporaryFolder dataFileDir = new TemporaryFolder(); + + private final ActiveMQQueue destination = new ActiveMQQueue("queue"); + private BrokerService broker; + private SystemUsage systemUsage; + private KahaDBStore store; + + @Before + public void setUp() throws Exception { + broker = new BrokerService(); + broker.setUseJmx(false); + broker.setPersistent(true); + KahaDBStore store = new KahaDBStore(); + store.setDirectory(dataFileDir.getRoot()); + broker.setPersistenceAdapter(store); + broker.start(); + systemUsage = broker.getSystemUsage(); + this.store = (KahaDBStore) broker.getPersistenceAdapter(); + } + + @After + public void tearDown() throws Exception { + broker.stop(); + } + + @Test(timeout = 10000) + public void testRemoveFromCacheIterator() throws Exception { + testRemoveFromCache((cursor, ref) -> { + // test removing using the iterator + cursor.remove(); + }); + } + + @Test(timeout = 10000) + public void testRemoveFromCacheRemoveMethod() throws Exception { + testRemoveFromCache((cursor, ref) -> { + // test using the remove method directly + // remove should also decrement after AMQ-9698, previously it did not + cursor.remove(ref); + assertEquals(0, ref.getReferenceCount()); + + // Call a second time to make sure we don't go negative + // and it will skip + cursor.remove(ref); + assertEquals(0, ref.getReferenceCount()); + }); + } + + private void testRemoveFromCache(BiConsumer remove) throws Exception { + var systemUsage = broker.getSystemUsage(); + final KahaDBStore store = (KahaDBStore) broker.getPersistenceAdapter(); + final MessageStore messageStore = store.createQueueMessageStore(destination); + final Queue queue = new Queue(broker, destination, messageStore, new DestinationStatistics(), null); + var memoryUsage = queue.getMemoryUsage(); + + // create cursor and make sure cache is enabled + QueueStorePrefetch cursor = new QueueStorePrefetch(queue, broker.getBroker()); + cursor.setSystemUsage(systemUsage); + cursor.start(); + assertTrue("cache enabled", cursor.isUseCache() && cursor.isCacheEnabled()); + + for (int i = 0; i < 10; i++) { + ActiveMQTextMessage msg = getMessage(i); + msg.setMemoryUsage(memoryUsage); + cursor.addMessageLast(msg); + // reference count of 1 for the cache + assertEquals(1, msg.getReferenceCount()); + } + + assertTrue(memoryUsage.getUsage() > 0); + + cursor.reset(); + while (cursor.hasNext()) { + // next will increment again so need to decrement the reference + // next is required to be called for remove() to work + var ref = cursor.next(); + assertEquals(2, ref.getReferenceCount()); + ref.decrementReferenceCount(); + remove.accept(cursor, ref); + assertEquals(0, ref.getReferenceCount()); + } + + assertEquals(0, memoryUsage.getUsage()); + assertEquals(0, cursor.size()); + } + + private ActiveMQTextMessage getMessage(int i) throws Exception { + ActiveMQTextMessage message = new ActiveMQTextMessage(); + MessageId id = new MessageId("11111:22222:" + i); + id.setBrokerSequenceId(i); + id.setProducerSequenceId(i); + message.setMessageId(id); + message.setDestination(destination); + message.setPersistent(true); + message.setResponseRequired(true); + message.setText("Msg:" + i + " " + "test"); + assertEquals(message.getMessageId().getProducerSequenceId(), i); + return message; + } + +} diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/StoreQueueCursorOrderTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/StoreQueueCursorOrderTest.java index 5a1ab90d589..771b884d801 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/StoreQueueCursorOrderTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/StoreQueueCursorOrderTest.java @@ -516,6 +516,11 @@ public void setBatch(MessageId message) { batch.incrementAndGet(); } + @Override + public StoreType getType() { + return StoreType.MEMORY; + } + @Override public void recoverMessageStoreStatistics() throws IOException { this.getMessageStoreStatistics().reset(); diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/MessageExpirationReaperTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/MessageExpirationReaperTest.java index 4c8527a52cb..65f3f1802c2 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/MessageExpirationReaperTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/MessageExpirationReaperTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import javax.jms.*; import javax.management.ObjectName; @@ -30,6 +31,7 @@ import org.apache.activemq.broker.region.policy.PolicyMap; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQTopic; +import org.apache.activemq.util.Wait; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -164,11 +166,59 @@ public void testExpiredMessagesOnTopic() throws Exception{ consumer.close(); // Let the messages reach an expiry time - Thread.sleep(2000); + assertTrue("Incorrect queue size count", Wait.waitFor(() -> view.getQueueSize() == 0, + 3000, 100)); + assertTrue("Incorrect inflight count: " + view.getInFlightCount(), + Wait.waitFor(() -> view.getInFlightCount() == 0, 3000, 100)); + assertTrue("Incorrect expired size count", Wait.waitFor(() -> view.getEnqueueCount() == view.getExpiredCount(), + 3000, 100)); + + // Memory usage should be 0 after expiration + assertEquals(0, broker.getDestination(destination).getMemoryUsage().getUsage()); + } - assertEquals("Incorrect inflight count: " + view.getInFlightCount(), 0, view.getInFlightCount()); - assertEquals("Incorrect queue size count", 0, view.getQueueSize()); - assertEquals("Incorrect expired size count", view.getEnqueueCount(), view.getExpiredCount()); + @Test + public void testExpiredMessagesOnTopic2Durables() throws Exception{ + Session session = createSession(); + + // use a zero prefetch so messages don't go inflight + ActiveMQTopic destination = new ActiveMQTopic(destinationName + "?consumer.prefetchSize=0"); + + MessageProducer producer = session.createProducer(destination); + MessageConsumer consumer = session.createDurableSubscriber(destination, "test-durable"); + MessageConsumer consumer2 = session.createDurableSubscriber(destination, "test-durable-2"); + + producer.setTimeToLive(500); + + final int count = 3; + // Send some messages with an expiration + for (int i = 0; i < count; i++) { + TextMessage message = session.createTextMessage("" + i); + producer.send(message); + } + + DestinationViewMBean view = createView(destination); + // not expired yet... + assertEquals("Incorrect enqueue count", 3, view.getEnqueueCount() ); + + // close consumer so topic thinks consumer is inactive + consumer.close(); + consumer2.close(); + + // Let the messages reach an expiry time + assertTrue("Incorrect queue size count", Wait.waitFor(() -> view.getQueueSize() == 0, + 3000, 100)); + assertTrue("Incorrect inflight count: " + view.getInFlightCount(), + Wait.waitFor(() -> view.getInFlightCount() == 0, 3000, 100)); + + // should be 2x enqueued for 2 subs + assertTrue("Incorrect expired size count", Wait.waitFor(() -> view.getEnqueueCount() * 2 == view.getExpiredCount(), + 3000, 100)); + + // check store and memory usage + org.apache.activemq.broker.region.Destination brokerDest = broker.getDestination(destination); + assertEquals("Incorrect queue size count", 0, brokerDest.getMessageStore().getMessageCount()); + assertEquals(0, brokerDest.getMemoryUsage().getUsage()); } protected DestinationViewMBean createView(ActiveMQDestination destination) throws Exception { diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBRecoverExpiredTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBRecoverExpiredTest.java new file mode 100644 index 00000000000..3e9bfe0cf9e --- /dev/null +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBRecoverExpiredTest.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.store.kahadb; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TopicSession; +import java.io.File; +import java.net.URI; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.broker.BrokerService; +import org.apache.activemq.broker.TransportConnector; +import org.apache.activemq.broker.region.Destination; +import org.apache.activemq.command.ActiveMQTextMessage; +import org.apache.activemq.command.ActiveMQTopic; +import org.apache.activemq.command.MessageAck; +import org.apache.activemq.store.TopicMessageStore; +import org.apache.activemq.util.SubscriptionKey; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.Timeout; + +/** + * Test for {@link TopicMessageStore#recoverExpired(Set, int)} + */ +public class KahaDBRecoverExpiredTest { + + @Rule + public Timeout globalTimeout = new Timeout(60, TimeUnit.SECONDS); + + @Rule + public TemporaryFolder dataFileDir = new TemporaryFolder(new File("target")); + + private BrokerService broker; + private URI brokerConnectURI; + private final ActiveMQTopic topic = new ActiveMQTopic("test.topic"); + private final SubscriptionKey subKey1 = new SubscriptionKey("clientId", "sub1"); + private final SubscriptionKey subKey2 = new SubscriptionKey("clientId", "sub2"); + + @Before + public void startBroker() throws Exception { + broker = new BrokerService(); + broker.setPersistent(true); + KahaDBPersistenceAdapter persistenceAdapter = new KahaDBPersistenceAdapter(); + persistenceAdapter.setDirectory(dataFileDir.getRoot()); + broker.setPersistenceAdapter(persistenceAdapter); + //set up a transport + TransportConnector connector = broker + .addConnector(new TransportConnector()); + connector.setUri(new URI("tcp://0.0.0.0:0")); + connector.setName("tcp"); + broker.start(); + broker.waitUntilStarted(); + brokerConnectURI = broker.getConnectorByName("tcp").getConnectUri(); + } + + @After + public void stopBroker() throws Exception { + broker.stop(); + broker.waitUntilStopped(); + } + + private Session initializeSubs() throws JMSException { + Connection connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); + connection.setClientID("clientId"); + connection.start(); + + Session session = connection.createSession(false, TopicSession.AUTO_ACKNOWLEDGE); + session.createDurableSubscriber(topic, "sub1"); + session.createDurableSubscriber(topic, "sub2"); + + return session; + } + + // test recover expired works in general, verify does not return + // expired if subs have already acked + @Test + public void testRecoverExpired() throws Exception { + try (Session session = initializeSubs()) { + MessageProducer prod = session.createProducer(topic); + + Destination dest = broker.getDestination(topic); + TopicMessageStore store = (TopicMessageStore) dest.getMessageStore(); + + // nothing should be expired yet, no messags + var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + assertTrue(expired.isEmpty()); + + // Sent 10 messages, alternating no expiration and 1 second ttl + for (int i = 0; i < 10; i++) { + ActiveMQTextMessage message = new ActiveMQTextMessage(); + message.setText("message" + i); + var ttl = i % 2 == 0 ? 1000 : 0; + prod.send(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, ttl); + } + + // wait for the time to pass the point of needing expiration + Thread.sleep(1500); + // We should now find both durables have 5 expired messages + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + assertEquals(2, expired.size()); + assertEquals(5, expired.get(subKey1).size()); + assertEquals(5, expired.get(subKey2).size()); + + // Acknowledge the first 2 messages of only the first sub + for (int i = 0; i < 2; i++) { + MessageAck ack = new MessageAck(); + ack.setLastMessageId(expired.get(subKey1).get(i).getMessageId()); + ack.setAckType(MessageAck.EXPIRED_ACK_TYPE); + ack.setDestination(topic); + store.acknowledge(broker.getAdminConnectionContext(),"clientId", "sub1", + ack.getLastMessageId(), ack); + } + + // Now the first sub should only have 3 expired, but still 5 on the second + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + assertEquals(3, expired.get(subKey1).size()); + assertEquals(5, expired.get(subKey2).size()); + + // ack all remaining + for (Entry> entry : expired.entrySet()) { + for (org.apache.activemq.command.Message message : entry.getValue()) { + MessageAck ack = new MessageAck(); + ack.setLastMessageId(message.getMessageId()); + ack.setAckType(MessageAck.EXPIRED_ACK_TYPE); + ack.setDestination(topic); + store.acknowledge(broker.getAdminConnectionContext(),entry.getKey().getClientId(), + entry.getKey().getSubscriptionName(), ack.getLastMessageId(), ack); + } + } + + // should be empty again + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + assertTrue(expired.isEmpty()); + } + + } + + // test max number of messages to check works + @Test + public void testRecoverExpiredMax() throws Exception { + try (Session session = initializeSubs()) { + MessageProducer prod = session.createProducer(topic); + + Destination dest = broker.getDestination(topic); + TopicMessageStore store = (TopicMessageStore) dest.getMessageStore(); + + // nothing should be expired yet, no messags + var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + assertTrue(expired.isEmpty()); + + // Sent 50 messages with no ttl followed by 50 with ttl + ActiveMQTextMessage message = new ActiveMQTextMessage(); + for (int i = 0; i < 100; i++) { + message.setText("message" + i); + var ttl = i >= 50 ? 1000 : 0; + prod.send(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, ttl); + } + + // wait for the time to pass the point of needing expiration + Thread.sleep(1500); + + // We should now find both durables have 50 expired messages + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + assertEquals(2, expired.size()); + assertEquals(50, expired.get(subKey1).size()); + assertEquals(50, expired.get(subKey2).size()); + + // Max is 50, should find none expired + expired = store.recoverExpired(Set.of(subKey1, subKey2), 50); + assertTrue(expired.isEmpty()); + + // We should now find both durables have 25 expired messages with + // max at 75 + expired = store.recoverExpired(Set.of(subKey1, subKey2), 75); + assertEquals(2, expired.size()); + assertEquals(25, expired.get(subKey1).size()); + assertEquals(25, expired.get(subKey2).size()); + + // Acknowledge the first 25 messages of only the first sub + for (int i = 0; i < 25; i++) { + MessageAck ack = new MessageAck(); + ack.setLastMessageId(expired.get(subKey1).get(i).getMessageId()); + ack.setAckType(MessageAck.EXPIRED_ACK_TYPE); + ack.setDestination(topic); + store.acknowledge(broker.getAdminConnectionContext(),"clientId", "sub1", + ack.getLastMessageId(), ack); + } + + // We should now find 25 on sub1 and 50 on sub2 with a max of 100 + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + assertEquals(2, expired.size()); + assertEquals(25, expired.get(subKey1).size()); + assertEquals(50, expired.get(subKey2).size()); + + } + + } + + // Test that filtering works by the set of subscriptions + @Test + public void testRecoverExpiredSubSet() throws Exception { + try (Session session = initializeSubs()) { + MessageProducer prod = session.createProducer(topic); + + Destination dest = broker.getDestination(topic); + TopicMessageStore store = (TopicMessageStore) dest.getMessageStore(); + + // nothing should be expired yet, no messags + var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + assertTrue(expired.isEmpty()); + + // Send 10 expired + for (int i = 0; i < 10; i++) { + ActiveMQTextMessage message = new ActiveMQTextMessage(); + message.setText("message" + i); + prod.send(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, 1000); + } + + // wait for the time to pass the point of needing expiration + Thread.sleep(1500); + + // Test getting each sub individually, get sub2 first + expired = store.recoverExpired(Set.of(subKey2), 100); + assertEquals(1, expired.size()); + assertEquals(10, expired.get(subKey2).size()); + + // ack the first message of sub2 + MessageAck ack = new MessageAck(); + ack.setLastMessageId(expired.get(subKey2).get(0).getMessageId()); + ack.setAckType(MessageAck.EXPIRED_ACK_TYPE); + ack.setDestination(topic); + store.acknowledge(broker.getAdminConnectionContext(),"clientId", "sub2", + ack.getLastMessageId(), ack); + + // check only sub2 has 9 + expired = store.recoverExpired(Set.of(subKey2), 100); + assertEquals(1, expired.size()); + assertEquals(9, expired.get(subKey2).size()); + + // check only sub1 still has 10 + expired = store.recoverExpired(Set.of(subKey1), 100); + assertEquals(1, expired.size()); + assertEquals(10, expired.get(subKey1).size()); + + // verify passing in unmatched sub leaves it out of the result set + var unmatched = new SubscriptionKey("clientId", "sub3"); + expired = store.recoverExpired(Set.of(unmatched), 100); + assertTrue(expired.isEmpty()); + + // try 2 that exist and 1 that doesn't + expired = store.recoverExpired(Set.of(subKey1, subKey2, unmatched), 100); + assertEquals(2, expired.size()); + + } + + } + +} From ea2b81808f00dfaad4b774c3edcd00db0a22208d Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Thu, 8 May 2025 08:57:09 -0400 Subject: [PATCH 07/34] AMQ-9698 - Add recovery listener to store recoverExpired() method This allows the store to check if the memory usage is full in the system before continuing to try and load messages to expire on durable subs (cherry picked from commit 6dac231f8d4da64ac160b6ff790bd543aaa70428) --- .../apache/activemq/broker/region/Topic.java | 33 ++++- .../store/ProxyTopicMessageStore.java | 5 +- .../activemq/store/TopicMessageStore.java | 20 ++- .../store/memory/MemoryTopicMessageStore.java | 3 +- .../store/jdbc/JDBCTopicMessageStore.java | 3 +- .../journal/JournalTopicMessageStore.java | 3 +- .../activemq/store/kahadb/KahaDBStore.java | 16 ++- .../store/kahadb/TempKahaDBStore.java | 3 +- .../bugs/MessageExpirationReaperTest.java | 34 +++++ .../kahadb/KahaDBRecoverExpiredTest.java | 136 +++++++++++++++--- 10 files changed, 219 insertions(+), 37 deletions(-) diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java index 4fcd508cf9e..27c5cf132c8 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -54,7 +53,6 @@ import org.apache.activemq.command.SubscriptionInfo; import org.apache.activemq.filter.MessageEvaluationContext; import org.apache.activemq.filter.NonCachedMessageEvaluationContext; -import org.apache.activemq.management.MessageFlowStats; import org.apache.activemq.store.MessageRecoveryListener; import org.apache.activemq.store.MessageStore.StoreType; import org.apache.activemq.store.NoLocalSubscriptionAware; @@ -829,6 +827,34 @@ protected void dispatch(final ConnectionContext context, Message message) throws } } + + /** + * Simple recovery listener that will check if the topic memory usage is full + * when hasSpace() is called. This could be enhanced in the future if needed. + */ + private final MessageRecoveryListener expiryListener = new MessageRecoveryListener() { + + @Override + public boolean recoverMessage(Message message) { + return true; + } + + @Override + public boolean recoverMessageReference(MessageId ref) { + return true; + } + + @Override + public boolean hasSpace() { + return !Topic.this.memoryUsage.isFull(); + } + + @Override + public boolean isDuplicate(MessageId ref) { + return false; + } + }; + private final AtomicBoolean expiryTaskInProgress = new AtomicBoolean(false); private final Runnable expireMessagesWork = () -> { try { @@ -851,7 +877,8 @@ protected void dispatch(final ConnectionContext context, Message message) throws // For each eligible subscription, return the messages in the store that are expired // The same message refs are shared between subs if duplicated so this is efficient - var expired = store.recoverExpired(subs, getMaxExpirePageSize()); + var expired = store.recoverExpired(subs, getMaxExpirePageSize(), + expiryListener); final ConnectionContext connectionContext = createConnectionContext(); // Go through any expired messages and remove for each sub diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/ProxyTopicMessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/ProxyTopicMessageStore.java index 23c9e81809d..d9b92500c01 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/ProxyTopicMessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/ProxyTopicMessageStore.java @@ -241,7 +241,8 @@ public MessageStoreSubscriptionStatistics getMessageStoreSubStatistics() { } @Override - public Map> recoverExpired(Set subs, int max) throws Exception { - return ((TopicMessageStore)delegate).recoverExpired(subs, max); + public Map> recoverExpired(Set subs, int max, + MessageRecoveryListener listener) throws Exception { + return ((TopicMessageStore)delegate).recoverExpired(subs, max, listener); } } diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/TopicMessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/TopicMessageStore.java index 6ee046cff6a..05f48539dc7 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/TopicMessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/TopicMessageStore.java @@ -161,15 +161,21 @@ public interface TopicMessageStore extends MessageStore { void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroactive) throws IOException; /** - * Iterates over the pending messages in a topic and recovers any expired messages found for - * each of the subscriptions up to the maximum number of messages to search. Only subscriptions - * that have at least 1 expired message will be returned in the map. + * Iterates over the pending messages in a topic and recovers any expired messages found for each + * of the subscriptions up to the maximum number of messages to search. Only subscriptions that + * have at least 1 expired message will be returned in the map. + *
+ * The expiry listener is only used to verify if there is space. Messages that are expired + * and will be added to 1 or more subscription in the returned map will be passed to + * the callback. The callback will only be called once per each unique message. + * + * @param subs The subscription keys to check for expired messages + * @param maxBrowse The maximum number of messages to check + * @param listener * - * @param subs - * @param max * @return Expired messages for each subscription - * @throws Exception */ - Map> recoverExpired(Set subs, int max) throws Exception; + Map> recoverExpired(Set subs, int maxBrowse, + MessageRecoveryListener listener) throws Exception; } diff --git a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryTopicMessageStore.java b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryTopicMessageStore.java index 13a13c0759a..0aa6dc6ce07 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryTopicMessageStore.java +++ b/activemq-broker/src/main/java/org/apache/activemq/store/memory/MemoryTopicMessageStore.java @@ -182,7 +182,8 @@ public void resetBatching(String clientId, String subscriptionName) { } @Override - public Map> recoverExpired(Set subs, int max) { + public Map> recoverExpired(Set subs, int max, + MessageRecoveryListener listener) { throw new UnsupportedOperationException("recoverExpired not supported"); } diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCTopicMessageStore.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCTopicMessageStore.java index 7f29a46a64d..26390ba3089 100644 --- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCTopicMessageStore.java +++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/jdbc/JDBCTopicMessageStore.java @@ -363,7 +363,8 @@ public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroacti } @Override - public Map> recoverExpired(Set subs, int max) { + public Map> recoverExpired(Set subs, int max, + MessageRecoveryListener listener) { throw new UnsupportedOperationException("recoverExpired not supported"); } diff --git a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalTopicMessageStore.java b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalTopicMessageStore.java index f4971f67376..1613caab503 100644 --- a/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalTopicMessageStore.java +++ b/activemq-jdbc-store/src/main/java/org/apache/activemq/store/journal/JournalTopicMessageStore.java @@ -85,7 +85,8 @@ public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroacti } @Override - public Map> recoverExpired(Set subs, int max) { + public Map> recoverExpired(Set subs, int max, + MessageRecoveryListener listener) { throw new UnsupportedOperationException("recoverExpired not supported"); } diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java index 0f5406f7a27..b6f77c31658 100644 --- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java @@ -1331,11 +1331,11 @@ public void execute(Transaction tx) throws Exception { } @Override - public Map> recoverExpired(Set subscriptions, int max) throws Exception { + public Map> recoverExpired(Set subscriptions, int maxBrowse, + MessageRecoveryListener listener) throws Exception { indexLock.writeLock().lock(); try { - return pageFile.tx().execute( - (CallableClosure>, Exception>) tx -> { + return pageFile.tx().execute(tx -> { StoredDestination sd = getStoredDestination(dest, tx); sd.orderIndex.resetCursorPosition(); int count = 0; @@ -1351,9 +1351,10 @@ public Map> recoverExpired(Set su } // Iterate one time through the topic and check each message, stopping if we run out - // or reach the max + // hit the max browse limit, or if the listener returns false for hasSpace() + final Set uniqueExpired = new HashSet<>(); for (Iterator> iterator = - sd.orderIndex.iterator(tx, new MessageOrderCursor()); count < max && iterator.hasNext(); ) { + sd.orderIndex.iterator(tx, new MessageOrderCursor()); count < maxBrowse && iterator.hasNext() && listener.hasSpace(); ) { count++; Entry entry = iterator.next(); Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName()); @@ -1371,6 +1372,11 @@ public Map> recoverExpired(Set su if (sequence != null && sequence.contains(entry.getKey())) { List expMessages = expired.computeIfAbsent(subKeyEntry.getValue(), m -> new ArrayList<>()); expMessages.add(msg); + // pass unique messages to the listener so it can do any custom + // handling for the next call to listener.hasSpace() + if (uniqueExpired.add(entry.getKey())) { + listener.recoverMessage(msg); + } } } } diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java index 942076b2f1b..6bbf9534eec 100644 --- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/TempKahaDBStore.java @@ -339,7 +339,8 @@ public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroacti } @Override - public Map> recoverExpired(Set subs, int max) { + public Map> recoverExpired(Set subs, int max, + MessageRecoveryListener listener) { throw new UnsupportedOperationException("recoverExpired not supported"); } diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/MessageExpirationReaperTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/MessageExpirationReaperTest.java index 65f3f1802c2..4e5d98684b7 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/MessageExpirationReaperTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/MessageExpirationReaperTest.java @@ -221,6 +221,40 @@ public void testExpiredMessagesOnTopic2Durables() throws Exception{ assertEquals(0, brokerDest.getMemoryUsage().getUsage()); } + @Test + public void testExpiredMessagesOnTopicRecoveryListener() throws Exception{ + Session session = createSession(); + + // use a zero prefetch so messages don't go inflight + ActiveMQTopic destination = new ActiveMQTopic(destinationName + "?consumer.prefetchSize=0"); + + MessageProducer producer = session.createProducer(destination); + MessageConsumer consumer = session.createDurableSubscriber(destination, "test-durable"); + producer.setTimeToLive(1000); + + final int count = 3; + // Send some messages with an expiration + for (int i = 0; i < count; i++) { + TextMessage message = session.createTextMessage("" + i); + producer.send(message); + } + + // Set a very low memory usage so we will be > 100% which should prevent the expiry + // thread from continuing + broker.getSystemUsage().getMemoryUsage().setLimit(1024); + DestinationViewMBean view = createView(destination); + + // not expired yet... + assertEquals("Incorrect enqueue count", 3, view.getEnqueueCount() ); + + // close consumer so topic thinks consumer is inactive + consumer.close(); + + // Memory is > 100% so we shouldn't expire + Thread.sleep(3000); + assertEquals(0, view.getExpiredCount()); + } + protected DestinationViewMBean createView(ActiveMQDestination destination) throws Exception { String domain = "org.apache.activemq"; ObjectName name; diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBRecoverExpiredTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBRecoverExpiredTest.java index 3e9bfe0cf9e..5491b2755ad 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBRecoverExpiredTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBRecoverExpiredTest.java @@ -27,17 +27,22 @@ import javax.jms.TopicSession; import java.io.File; import java.net.URI; +import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.TransportConnector; import org.apache.activemq.broker.region.Destination; +import org.apache.activemq.broker.region.Topic; import org.apache.activemq.command.ActiveMQTextMessage; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.command.MessageAck; +import org.apache.activemq.command.MessageId; +import org.apache.activemq.store.MessageRecoveryListener; import org.apache.activemq.store.TopicMessageStore; import org.apache.activemq.util.SubscriptionKey; import org.junit.After; @@ -48,7 +53,7 @@ import org.junit.rules.Timeout; /** - * Test for {@link TopicMessageStore#recoverExpired(Set, int)} + * Test for {@link TopicMessageStore#recoverExpired(Set, int, org.apache.activemq.store.MessageRecoveryListener)} */ public class KahaDBRecoverExpiredTest { @@ -110,7 +115,7 @@ public void testRecoverExpired() throws Exception { TopicMessageStore store = (TopicMessageStore) dest.getMessageStore(); // nothing should be expired yet, no messags - var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, listener); assertTrue(expired.isEmpty()); // Sent 10 messages, alternating no expiration and 1 second ttl @@ -124,7 +129,7 @@ public void testRecoverExpired() throws Exception { // wait for the time to pass the point of needing expiration Thread.sleep(1500); // We should now find both durables have 5 expired messages - expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, listener); assertEquals(2, expired.size()); assertEquals(5, expired.get(subKey1).size()); assertEquals(5, expired.get(subKey2).size()); @@ -140,7 +145,7 @@ public void testRecoverExpired() throws Exception { } // Now the first sub should only have 3 expired, but still 5 on the second - expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, listener); assertEquals(3, expired.get(subKey1).size()); assertEquals(5, expired.get(subKey2).size()); @@ -157,7 +162,7 @@ public void testRecoverExpired() throws Exception { } // should be empty again - expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, listener); assertTrue(expired.isEmpty()); } @@ -173,7 +178,7 @@ public void testRecoverExpiredMax() throws Exception { TopicMessageStore store = (TopicMessageStore) dest.getMessageStore(); // nothing should be expired yet, no messags - var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, listener); assertTrue(expired.isEmpty()); // Sent 50 messages with no ttl followed by 50 with ttl @@ -188,18 +193,18 @@ public void testRecoverExpiredMax() throws Exception { Thread.sleep(1500); // We should now find both durables have 50 expired messages - expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, listener); assertEquals(2, expired.size()); assertEquals(50, expired.get(subKey1).size()); assertEquals(50, expired.get(subKey2).size()); // Max is 50, should find none expired - expired = store.recoverExpired(Set.of(subKey1, subKey2), 50); + expired = store.recoverExpired(Set.of(subKey1, subKey2), 50, listener); assertTrue(expired.isEmpty()); // We should now find both durables have 25 expired messages with // max at 75 - expired = store.recoverExpired(Set.of(subKey1, subKey2), 75); + expired = store.recoverExpired(Set.of(subKey1, subKey2), 75, listener); assertEquals(2, expired.size()); assertEquals(25, expired.get(subKey1).size()); assertEquals(25, expired.get(subKey2).size()); @@ -215,7 +220,7 @@ public void testRecoverExpiredMax() throws Exception { } // We should now find 25 on sub1 and 50 on sub2 with a max of 100 - expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, listener); assertEquals(2, expired.size()); assertEquals(25, expired.get(subKey1).size()); assertEquals(50, expired.get(subKey2).size()); @@ -234,7 +239,7 @@ public void testRecoverExpiredSubSet() throws Exception { TopicMessageStore store = (TopicMessageStore) dest.getMessageStore(); // nothing should be expired yet, no messags - var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100); + var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, listener); assertTrue(expired.isEmpty()); // Send 10 expired @@ -248,7 +253,7 @@ public void testRecoverExpiredSubSet() throws Exception { Thread.sleep(1500); // Test getting each sub individually, get sub2 first - expired = store.recoverExpired(Set.of(subKey2), 100); + expired = store.recoverExpired(Set.of(subKey2), 100, listener); assertEquals(1, expired.size()); assertEquals(10, expired.get(subKey2).size()); @@ -261,26 +266,125 @@ public void testRecoverExpiredSubSet() throws Exception { ack.getLastMessageId(), ack); // check only sub2 has 9 - expired = store.recoverExpired(Set.of(subKey2), 100); + expired = store.recoverExpired(Set.of(subKey2), 100, listener); assertEquals(1, expired.size()); assertEquals(9, expired.get(subKey2).size()); // check only sub1 still has 10 - expired = store.recoverExpired(Set.of(subKey1), 100); + expired = store.recoverExpired(Set.of(subKey1), 100, listener); assertEquals(1, expired.size()); assertEquals(10, expired.get(subKey1).size()); // verify passing in unmatched sub leaves it out of the result set var unmatched = new SubscriptionKey("clientId", "sub3"); - expired = store.recoverExpired(Set.of(unmatched), 100); + expired = store.recoverExpired(Set.of(unmatched), 100, listener); assertTrue(expired.isEmpty()); // try 2 that exist and 1 that doesn't - expired = store.recoverExpired(Set.of(subKey1, subKey2, unmatched), 100); + expired = store.recoverExpired(Set.of(subKey1, subKey2, unmatched), 100, listener); assertEquals(2, expired.size()); } } + // test recovery listener works with hasSpace() + @Test + public void testRecoverExpiredRecoveryListener() throws Exception { + try (Session session = initializeSubs()) { + MessageProducer prod = session.createProducer(topic); + + Destination dest = broker.getDestination(topic); + TopicMessageStore store = (TopicMessageStore) dest.getMessageStore(); + + // Sent 50 messages with no ttl followed by 50 with ttl + ActiveMQTextMessage message = new ActiveMQTextMessage(); + for (int i = 0; i < 10; i++) { + message.setText("message" + i); + prod.send(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, 1000); + } + + // wait for the time to pass the point of needing expiration + Thread.sleep(1500); + + // don't return any, has space is false + final AtomicBoolean hasSpaceCalled = new AtomicBoolean(); + var expired = store.recoverExpired(Set.of(subKey1, subKey2), 100, new MessageRecoveryListener() { + @Override + public boolean recoverMessage(org.apache.activemq.command.Message message) { + return true; + } + + @Override + public boolean recoverMessageReference(MessageId ref) { + return false; + } + + @Override + public boolean hasSpace() { + hasSpaceCalled.set(true); + return false; + } + + @Override + public boolean isDuplicate(MessageId ref) { + return false; + } + }); + + assertTrue(expired.isEmpty()); + assertTrue(hasSpaceCalled.get()); + + // check we only call recoverMessage() once for each unique id\ + Set ids = new HashSet<>(); + store.recoverExpired(Set.of(subKey1, subKey2), 100, new MessageRecoveryListener() { + @Override + public boolean recoverMessage(org.apache.activemq.command.Message message) { + // assert unique + assertTrue("duplicate message passed to listener", ids.add(message.getMessageId())); + return true; + } + + @Override + public boolean recoverMessageReference(MessageId ref) { + return false; + } + + @Override + public boolean hasSpace() { + return true; + } + + @Override + public boolean isDuplicate(MessageId ref) { + return false; + } + }); + } + + } + + private final MessageRecoveryListener listener = new MessageRecoveryListener() { + + @Override + public boolean recoverMessage(org.apache.activemq.command.Message message) { + return true; + } + + @Override + public boolean recoverMessageReference(MessageId ref) { + return true; + } + + @Override + public boolean hasSpace() { + return true; + } + + @Override + public boolean isDuplicate(MessageId ref) { + return false; + } + }; + } From 780008cdcdb44afccaee821cd901abbab859305b Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Wed, 28 May 2025 18:01:06 +0300 Subject: [PATCH 08/34] AMQ-9716: Fix `maxMessageSize=-1` to correctly disable message size limit (#1441) (#1441) (cherry picked from commit d226bfeb14fdc3efa1d33e35a95882435cc4a6c9) --- .../java/org/apache/activemq/web/MessageServletSupport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activemq-web/src/main/java/org/apache/activemq/web/MessageServletSupport.java b/activemq-web/src/main/java/org/apache/activemq/web/MessageServletSupport.java index d038295f9f2..643f2deb180 100644 --- a/activemq-web/src/main/java/org/apache/activemq/web/MessageServletSupport.java +++ b/activemq-web/src/main/java/org/apache/activemq/web/MessageServletSupport.java @@ -358,7 +358,7 @@ protected String getPostedMessageBody(HttpServletRequest request) throws IOExcep if (answer == null && contentType != null && contentLengthLong > -1l) { LOG.debug("Content-Type={} Content-Length={} maxMessageSize={}", contentType, contentLengthLong, maxMessageSize); - if (contentLengthLong > maxMessageSize) { + if (maxMessageSize != -1 && contentLengthLong > maxMessageSize) { LOG.warn("Message body exceeds max allowed size. Content-Type={} Content-Length={} maxMessageSize={}", contentType, contentLengthLong, maxMessageSize); throw new IOException("Message body exceeds max allowed size"); } @@ -397,6 +397,6 @@ protected String getSelector(HttpServletRequest request) throws IOException { } private boolean isMaxBodySizeExceeded(int totalRead, int expectedBodySize) { - return totalRead < 0 || totalRead >= Integer.MAX_VALUE || totalRead >= maxMessageSize || totalRead > expectedBodySize; + return totalRead < 0 || totalRead == Integer.MAX_VALUE || (maxMessageSize != -1 && totalRead >= maxMessageSize) || totalRead > expectedBodySize; } } From d0cee10145cf35ac918d9bbac17b0a871469d05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Lemos?= Date: Thu, 29 May 2025 21:45:19 -0700 Subject: [PATCH 09/34] AMQ-9697: Removed inline JS and CSS from the Web Console. Added CSP header to jetty.xml (#1428) (cherry picked from commit 4db953d5de532d26b1f54dc069637468191a6e5d) --- activemq-web-console/src/main/webapp/403.html | 14 +--- activemq-web-console/src/main/webapp/404.html | 14 +--- activemq-web-console/src/main/webapp/500.html | 14 +--- .../src/main/webapp/connections.jsp | 2 +- .../src/main/webapp/decorators/head.jsp | 11 +-- .../src/main/webapp/decorators/header.jsp | 6 +- .../src/main/webapp/js/head.js | 20 +++++ .../src/main/webapp/js/message.js | 78 ++++++++++++++++++ .../src/main/webapp/js/queueGraph.js | 38 +++++++++ .../src/main/webapp/login.html | 12 +-- .../src/main/webapp/message.jsp | 80 +++---------------- .../src/main/webapp/network.jsp | 2 +- .../src/main/webapp/queueGraph.jsp | 33 +++----- .../src/main/webapp/scheduled.jsp | 4 +- .../src/main/webapp/slave.jsp | 14 +--- .../src/main/webapp/styles/head.css | 26 ++++++ .../src/main/webapp/styles/header.css | 46 +++++++++++ .../src/main/webapp/styles/site.css | 4 + .../src/main/webapp/xml/queues.jsp | 3 - assembly/src/release/conf/jetty.xml | 11 +++ assembly/src/release/webapps/index.html | 17 ++-- 21 files changed, 281 insertions(+), 168 deletions(-) create mode 100644 activemq-web-console/src/main/webapp/js/head.js create mode 100644 activemq-web-console/src/main/webapp/js/message.js create mode 100644 activemq-web-console/src/main/webapp/js/queueGraph.js create mode 100644 activemq-web-console/src/main/webapp/styles/head.css create mode 100644 activemq-web-console/src/main/webapp/styles/header.css diff --git a/activemq-web-console/src/main/webapp/403.html b/activemq-web-console/src/main/webapp/403.html index bfc95506f67..7503723b737 100644 --- a/activemq-web-console/src/main/webapp/403.html +++ b/activemq-web-console/src/main/webapp/403.html @@ -26,13 +26,7 @@ Apache ActiveMQ - - - - + @@ -54,10 +48,10 @@ @@ -75,7 +69,7 @@ -
+

Restricted!

diff --git a/activemq-web-console/src/main/webapp/404.html b/activemq-web-console/src/main/webapp/404.html index cf11458a4ed..98638913a92 100644 --- a/activemq-web-console/src/main/webapp/404.html +++ b/activemq-web-console/src/main/webapp/404.html @@ -26,13 +26,7 @@ Apache ActiveMQ - - - - + @@ -54,10 +48,10 @@ @@ -75,7 +69,7 @@ -
+

Page Not Found!

diff --git a/activemq-web-console/src/main/webapp/500.html b/activemq-web-console/src/main/webapp/500.html index f9305ff4ae7..ee6966bc7dc 100644 --- a/activemq-web-console/src/main/webapp/500.html +++ b/activemq-web-console/src/main/webapp/500.html @@ -26,13 +26,7 @@ Apache ActiveMQ - - - - + @@ -54,10 +48,10 @@ @@ -75,7 +69,7 @@ -
+

Error!

Exception occurred while processing this request, check the log for more information!

diff --git a/activemq-web-console/src/main/webapp/connections.jsp b/activemq-web-console/src/main/webapp/connections.jsp index 5663b358de7..951db78cbb3 100644 --- a/activemq-web-console/src/main/webapp/connections.jsp +++ b/activemq-web-console/src/main/webapp/connections.jsp @@ -57,7 +57,7 @@
-
+

Network Connectors

diff --git a/activemq-web-console/src/main/webapp/decorators/head.jsp b/activemq-web-console/src/main/webapp/decorators/head.jsp index abb76eaf741..6da545b73e8 100644 --- a/activemq-web-console/src/main/webapp/decorators/head.jsp +++ b/activemq-web-console/src/main/webapp/decorators/head.jsp @@ -20,17 +20,14 @@ <c:out value="${requestContext.brokerQuery.brokerAdmin.brokerName} : ${pageTitle}" /> - + + + - + diff --git a/activemq-web-console/src/main/webapp/decorators/header.jsp b/activemq-web-console/src/main/webapp/decorators/header.jsp index 99bd1d42dec..63674886621 100644 --- a/activemq-web-console/src/main/webapp/decorators/header.jsp +++ b/activemq-web-console/src/main/webapp/decorators/header.jsp @@ -33,9 +33,9 @@ @@ -71,5 +71,5 @@
- - + @@ -146,12 +154,12 @@ No message could be found for ID " onclick="return confirm('Are you sure you want to retry this message?')" - title="Retry - attempt reprocessing on original destination">Retry + title="Retry - attempt reprocessing on original destination">Retry - + +
+
diff --git a/activemq-web-console/src/main/webapp/js/head.js b/activemq-web-console/src/main/webapp/js/head.js new file mode 100644 index 00000000000..d78cd393e56 --- /dev/null +++ b/activemq-web-console/src/main/webapp/js/head.js @@ -0,0 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +window.onload = function () { + addEvent(window, 'load', prettyPrint) +} diff --git a/activemq-web-console/src/main/webapp/js/message.js b/activemq-web-console/src/main/webapp/js/message.js new file mode 100644 index 00000000000..d45b70a4cb7 --- /dev/null +++ b/activemq-web-console/src/main/webapp/js/message.js @@ -0,0 +1,78 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function sortSelect(selElem) { + const tmpAry = []; + for (var i=0;i 0) { + selElem.options[0] = null; + } + for (var i=0;i Apache ActiveMQ - Login - - - - + @@ -54,10 +48,10 @@ diff --git a/activemq-web-console/src/main/webapp/message.jsp b/activemq-web-console/src/main/webapp/message.jsp index 132d596d685..02f43fc5a81 100644 --- a/activemq-web-console/src/main/webapp/message.jsp +++ b/activemq-web-console/src/main/webapp/message.jsp @@ -133,8 +133,16 @@ No message could be found for ID + + + + + + +
" onclick="return confirm('Are you sure you want to delete the message?')" >Delete">Delete
')">CopyCopy ')" - >MoveMove
@@ -195,70 +201,10 @@ No message could be found for ID -function sortSelect(selElem) { - var tmpAry = new Array(); - for (var i=0;i 0) { - selElem.options[0] = null; - } - for (var i=0;i - - - - - "; - url = url + "&destination=" + encodeURIComponent(value); - - if (confirm("Are you sure?")) - location.href=url; -} - -window.onload=function() { - sortSelect( document.getElementById('queue') ); - selectOptionByText( document.getElementById('queue'), "-- Please select --" ); -} - - + <%@include file="decorators/footer.jsp" %> - diff --git a/activemq-web-console/src/main/webapp/network.jsp b/activemq-web-console/src/main/webapp/network.jsp index df2a08f3110..9ee9b7d7f26 100644 --- a/activemq-web-console/src/main/webapp/network.jsp +++ b/activemq-web-console/src/main/webapp/network.jsp @@ -25,7 +25,7 @@ <%@include file="decorators/header.jsp" %> -
+

Network Bridges

diff --git a/activemq-web-console/src/main/webapp/queueGraph.jsp b/activemq-web-console/src/main/webapp/queueGraph.jsp index ac8b7806a09..a427a287f68 100644 --- a/activemq-web-console/src/main/webapp/queueGraph.jsp +++ b/activemq-web-console/src/main/webapp/queueGraph.jsp @@ -28,36 +28,21 @@ + -<%@include file="decorators/header.jsp" %> + + + - +<%@include file="decorators/header.jsp" %> -
+
<%--- Other values we can graph... diff --git a/activemq-web-console/src/main/webapp/scheduled.jsp b/activemq-web-console/src/main/webapp/scheduled.jsp index cac68c39922..8c32a2f2729 100644 --- a/activemq-web-console/src/main/webapp/scheduled.jsp +++ b/activemq-web-console/src/main/webapp/scheduled.jsp @@ -29,7 +29,7 @@ -
+
@@ -63,7 +63,7 @@
-
+

Scheduler not started!

diff --git a/activemq-web-console/src/main/webapp/slave.jsp b/activemq-web-console/src/main/webapp/slave.jsp index 202d089d92e..8cb1e2a9df5 100644 --- a/activemq-web-console/src/main/webapp/slave.jsp +++ b/activemq-web-console/src/main/webapp/slave.jsp @@ -23,13 +23,7 @@ Apache ActiveMQ - - - - + @@ -51,10 +45,10 @@ @@ -72,7 +66,7 @@ -
+

Broker is currently in slave mode!

diff --git a/activemq-web-console/src/main/webapp/styles/head.css b/activemq-web-console/src/main/webapp/styles/head.css new file mode 100644 index 00000000000..9f46a641c29 --- /dev/null +++ b/activemq-web-console/src/main/webapp/styles/head.css @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* ===================================================== */ +/* Single place to import all css files used in head.jsp */ +/* ===================================================== */ + +@import url('sorttable.css'); +@import url('type-settings.css'); +@import url('site.css'); +@import url('prettify.css'); +@import url('header.css'); \ No newline at end of file diff --git a/activemq-web-console/src/main/webapp/styles/header.css b/activemq-web-console/src/main/webapp/styles/header.css new file mode 100644 index 00000000000..6b74b63ce03 --- /dev/null +++ b/activemq-web-console/src/main/webapp/styles/header.css @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* ============================== */ +/* Styles used in header.jsp */ +/* ============================== */ + +.header-logo-active-mq { + float:left; + width:280px; + display:block; + text-indent:-5000px; + text-decoration:none; + line-height:60px; + margin-top:10px; + margin-left:100px; +} + +.header-logo-apache { + float:right; + width:210px; + display:block; + text-indent:-5000px; + text-decoration:none; + line-height:60px; + margin-top:15px; + margin-right:10px +} + +.body-container { + overflow:hidden; +} \ No newline at end of file diff --git a/activemq-web-console/src/main/webapp/styles/site.css b/activemq-web-console/src/main/webapp/styles/site.css index 6ebcd0347ad..687a81bd723 100644 --- a/activemq-web-console/src/main/webapp/styles/site.css +++ b/activemq-web-console/src/main/webapp/styles/site.css @@ -19,6 +19,10 @@ body { padding: 20px; } +.section-container { + margin-top: 5em +} + /* ====================================================== */ /* Rounded Box Styles */ /* ====================================================== */ diff --git a/activemq-web-console/src/main/webapp/xml/queues.jsp b/activemq-web-console/src/main/webapp/xml/queues.jsp index 4e2ba876875..c7382a149a6 100644 --- a/activemq-web-console/src/main/webapp/xml/queues.jsp +++ b/activemq-web-console/src/main/webapp/xml/queues.jsp @@ -20,13 +20,10 @@ "> - - - diff --git a/assembly/src/release/conf/jetty.xml b/assembly/src/release/conf/jetty.xml index 6f09f6ea2e9..bd8170342dc 100644 --- a/assembly/src/release/conf/jetty.xml +++ b/assembly/src/release/conf/jetty.xml @@ -73,6 +73,17 @@ + + + + + + + + + + + diff --git a/assembly/src/release/webapps/index.html b/assembly/src/release/webapps/index.html index 08332609622..cdfac1b98ae 100644 --- a/assembly/src/release/webapps/index.html +++ b/assembly/src/release/webapps/index.html @@ -25,14 +25,9 @@ - - Apache ActiveMQ - + + Apache ActiveMQ + @@ -54,10 +49,10 @@ @@ -75,7 +70,7 @@ -
+

Welcome to the Apache ActiveMQ!

From 36af213703961b4ec12c63c84170981ea30ec000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sat, 31 May 2025 07:56:01 +0200 Subject: [PATCH 10/34] AMQ-7517: Remove lib in the default classpath (#1442) (cherry picked from commit caeb4cad03d197e432213221d4a52ec5e6010ae1) --- assembly/src/release/bin/activemq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assembly/src/release/bin/activemq b/assembly/src/release/bin/activemq index 130517c6a0e..2b2650fb18c 100755 --- a/assembly/src/release/bin/activemq +++ b/assembly/src/release/bin/activemq @@ -112,7 +112,7 @@ if [ -z "$ACTIVEMQ_USER_CLASSPATH" ] ; then fi # ActiveMQ Classpath configuration -ACTIVEMQ_CLASSPATH="${ACTIVEMQ_BASE%/}/../lib/:$ACTIVEMQ_USER_CLASSPATH" +ACTIVEMQ_CLASSPATH="$ACTIVEMQ_USER_CLASSPATH" # Active MQ configuration directory if [ -z "$ACTIVEMQ_CONF" ] ; then From cf19b850b78eaa7b14295df4262bf800fe82f1ad Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Wed, 4 Jun 2025 08:26:32 -0400 Subject: [PATCH 11/34] AMQ-9721 - Fix performance issues during non-persistent cursor removal (#1447) This fixes the broker so multiple removals are no longer done for the same message leading to having to search the entire non persistent pending list. Durable subscriptions now check the persistence type of the message so the cursor will no longer search everything in a non-persistent pending list when the message is persistent. (cherry picked from commit 5a3abbc877bff6d73c2ab6cecec81d50dd18c839) --- .../apache/activemq/broker/region/Topic.java | 18 +++++-- .../broker/region/TopicSubscription.java | 5 +- .../cursors/StoreDurableSubscriberCursor.java | 24 ++++++++- .../KahaDBPendingMessageCursorTest.java | 54 +++++++++++++++++++ 4 files changed, 94 insertions(+), 7 deletions(-) diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java index 27c5cf132c8..e921c36dd48 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java @@ -17,6 +17,7 @@ package org.apache.activemq.broker.region; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -704,6 +705,11 @@ public boolean isDuplicate(MessageId id) { for (DurableTopicSubscription sub : durableSubscribers.values()) { if (!sub.isActive() || sub.isEnableMessageExpirationOnActiveDurableSubs()) { message.setRegionDestination(this); + // AMQ-9721 - Remove message from the cursor if it exists after + // loading from the store. Store recoverExpired() does not inc + // the ref count so we don't need to decrement here, but if + // the cursor finds its own copy in memory it will dec that ref. + sub.removePending(message); messageExpired(connectionContext, sub, message); } } @@ -894,6 +900,15 @@ public boolean isDuplicate(MessageId ref) { if (isEligibleForExpiration(sub)) { expiredMessages.forEach(message -> { message.setRegionDestination(Topic.this); + try { + // AMQ-9721 - Remove message from the cursor if it exists after + // loading from the store. Store recoverExpired() does not inc + // the ref count so we don't need to decrement here, but if + // the cursor finds its own copy in memory it will dec that ref. + sub.removePending(message); + } catch (IOException e) { + throw new UncheckedIOException(e); + } messageExpired(connectionContext, sub, message); }); } @@ -932,9 +947,6 @@ public void messageExpired(ConnectionContext context, Subscription subs, Message ack.setDestination(destination); ack.setMessageID(reference.getMessageId()); try { - if (subs instanceof DurableTopicSubscription) { - ((DurableTopicSubscription)subs).removePending(reference); - } acknowledge(context, subs, ack, reference); } catch (Exception e) { LOG.error("Failed to remove expired Message from the store ", e); diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/TopicSubscription.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/TopicSubscription.java index 73a51137651..a6cb27f9273 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/TopicSubscription.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/TopicSubscription.java @@ -188,6 +188,9 @@ public void add(MessageReference node) throws Exception { messagesToEvict = oldMessages.length; for (int i = 0; i < messagesToEvict; i++) { MessageReference oldMessage = oldMessages[i]; + // AMQ-9721 - discard no longer removes from matched so remove here + oldMessage.decrementReferenceCount(); + matched.remove(oldMessage); //Expired here is false as we are discarding due to the messageEvictingStrategy discard(oldMessage, false); } @@ -751,8 +754,6 @@ public void onFailure() { private void discard(MessageReference message, boolean expired) { discarding = true; try { - message.decrementReferenceCount(); - matched.remove(message); if (destination != null) { destination.getDestinationStatistics().getDequeues().increment(); if(destination.isAdvancedNetworkStatisticsEnabled() && getContext() != null && getContext().isNetworkConnection()) { diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/StoreDurableSubscriberCursor.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/StoreDurableSubscriberCursor.java index 55a77550e1e..510412b7944 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/StoreDurableSubscriberCursor.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/StoreDurableSubscriberCursor.java @@ -28,6 +28,7 @@ import org.apache.activemq.broker.region.Destination; import org.apache.activemq.broker.region.DurableTopicSubscription; import org.apache.activemq.broker.region.MessageReference; +import org.apache.activemq.broker.region.NullMessageReference; import org.apache.activemq.broker.region.Topic; import org.apache.activemq.command.Message; import org.apache.activemq.usage.SystemUsage; @@ -274,8 +275,27 @@ public synchronized void remove() { @Override public synchronized void remove(MessageReference node) { - for (PendingMessageCursor tsp : storePrefetches) { - tsp.remove(node); + // AMQ-9721 - Check if message is persistent or non-persistent. + // Removing from the non-persistent cursor requires searching the + // entire list if it's paged onto disk which is quite slow, + // so it doesn't make sense to try and remove as it will never + // exist if it's persistent. + + // MessageReference can be a null reference if called from DurableSubscriptionView + // so we do not know if it's persistent and just need to search everything. + if (node instanceof NullMessageReference) { + for (PendingMessageCursor tsp : storePrefetches) { + tsp.remove(node); + } + } else if (node.isPersistent()) { + for (PendingMessageCursor tsp : storePrefetches) { + if (tsp.equals(nonPersistent)) { + continue; + } + tsp.remove(node); + } + } else { + nonPersistent.remove(node); } } diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/KahaDBPendingMessageCursorTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/KahaDBPendingMessageCursorTest.java index 8fd42e275e0..9bba67f723c 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/KahaDBPendingMessageCursorTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/broker/region/cursors/KahaDBPendingMessageCursorTest.java @@ -35,9 +35,12 @@ import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.DurableTopicSubscription; +import org.apache.activemq.broker.region.MessageReference; import org.apache.activemq.broker.region.Topic; +import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQTextMessage; import org.apache.activemq.command.ActiveMQTopic; +import org.apache.activemq.command.MessageAck; import org.apache.activemq.store.MessageStoreSubscriptionStatistics; import org.apache.activemq.store.TopicMessageStore; import org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter; @@ -352,4 +355,55 @@ public boolean isSatisified() throws Exception { } + // Test for AMQ-9721 + @Test + public void testDurableCursorRemoveRefMessageSize() throws Exception { + AtomicLong publishedMessageSize = new AtomicLong(); + + Connection connection = new ActiveMQConnectionFactory(brokerConnectURI).createConnection(); + connection.setClientID("clientId"); + connection.start(); + + // send 100 persistent and non persistent + Topic topic = publishTestMessagesDurable(connection, new String[] {"sub1"}, 100, + publishedMessageSize, DeliveryMode.NON_PERSISTENT); + publishTestMessagesDurable(connection, new String[] {"sub1"}, 100, + publishedMessageSize, DeliveryMode.PERSISTENT); + + SubscriptionKey subKey = new SubscriptionKey("clientId", "sub1"); + + // verify the count and size + verifyPendingStats(topic, subKey, 200, publishedMessageSize.get()); + + // Iterate and remove using the pending cursor to test removal + final DurableTopicSubscription sub = topic.getDurableTopicSubs().get(subKey); + PendingMessageCursor pending = sub.getPending(); + try { + pending.reset(); + while (pending.hasNext()) { + MessageReference node = pending.next(); + node.decrementReferenceCount(); + // test the remove(ref) method which has been updated + // to check persistence type + pending.remove(node); + + // If persistent remove out of the store + if (node.isPersistent()) { + MessageAck ack = new MessageAck(); + ack.setLastMessageId(node.getMessageId()); + ack.setAckType(MessageAck.STANDARD_ACK_TYPE); + ack.setDestination(topic.getActiveMQDestination()); + topic.acknowledge(sub.getContext(), sub, ack, node); + } + } + } finally { + pending.release(); + } + + // verify everything has been cleared correctly, persistent and + // non-persistent + verifyPendingStats(topic, subKey, 0, 0); + // Memory usage should be 0 after removal + assertEquals(0, topic.getMemoryUsage().getUsage()); + } } From 2296dc6455197a84c87dd7079b558742eb109951 Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Thu, 5 Jun 2025 07:13:40 -0400 Subject: [PATCH 12/34] NO-JIRA: Fix flaky DurableSubscriptionHangTestCase This fixes the test by correctly waiting for expiration and greatly speeds things up by reducing how many messages are sent (cherry picked from commit 6581ed7e74cf1ad9ca95aa192a2f6cd05a464a5d) --- .../DurableSubscriptionHangTestCase.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/usecases/DurableSubscriptionHangTestCase.java b/activemq-unit-tests/src/test/java/org/apache/activemq/usecases/DurableSubscriptionHangTestCase.java index bef912a514b..b802a9b5cdf 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/usecases/DurableSubscriptionHangTestCase.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/usecases/DurableSubscriptionHangTestCase.java @@ -30,6 +30,8 @@ import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.policy.PolicyEntry; import org.apache.activemq.broker.region.policy.PolicyMap; +import org.apache.activemq.command.ActiveMQTopic; +import org.apache.activemq.util.Wait; import org.apache.commons.lang.RandomStringUtils; import org.junit.After; import org.junit.Before; @@ -39,6 +41,7 @@ import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class DurableSubscriptionHangTestCase { private static final Logger LOG = LoggerFactory.getLogger(DurableSubscriptionHangTestCase.class); @@ -55,7 +58,7 @@ public void startBroker() throws Exception { brokerService.setBrokerName(brokerName); PolicyMap policyMap = new PolicyMap(); PolicyEntry defaultEntry = new PolicyEntry(); - defaultEntry.setExpireMessagesPeriod(5000); + defaultEntry.setExpireMessagesPeriod(1000); policyMap.setDefaultEntry(defaultEntry); brokerService.setDestinationPolicy(policyMap); brokerService.start(); @@ -67,12 +70,13 @@ public void brokerStop() throws Exception { } @Test - public void testHanging() throws Exception - { + public void testHanging() throws Exception { registerDurableSubscription(); produceExpiredAndOneNonExpiredMessages(); - TimeUnit.SECONDS.sleep(10); // make sure messages are expired - Message message = collectMessagesFromDurableSubscriptionForOneMinute(); + assertTrue(Wait.waitFor(() -> brokerService.getDestination(new ActiveMQTopic(topicName)) + .getDestinationStatistics().getExpired().getCount() == 1000, 30000, 500)); + + Message message = getUnexpiredMessageFromDurableSubscription(); LOG.info("got message:" + message); assertNotNull("Unable to read unexpired message", message); } @@ -84,8 +88,7 @@ private void produceExpiredAndOneNonExpiredMessages() throws JMSException { Topic topic = session.createTopic(topicName); MessageProducer producer = session.createProducer(topic); producer.setTimeToLive(TimeUnit.SECONDS.toMillis(1)); - for(int i=0; i<40000; i++) - { + for(int i = 0; i < 1000; i++) { sendRandomMessage(session, producer); } producer.setTimeToLive(TimeUnit.DAYS.toMillis(1)); @@ -108,8 +111,7 @@ private void registerDurableSubscription() throws JMSException LOG.info("Durable Sub Registered"); } - private Message collectMessagesFromDurableSubscriptionForOneMinute() throws Exception - { + private Message getUnexpiredMessageFromDurableSubscription() throws Exception { ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://" + brokerName); TopicConnection connection = connectionFactory.createTopicConnection(); @@ -119,7 +121,7 @@ private Message collectMessagesFromDurableSubscriptionForOneMinute() throws Exce connection.start(); TopicSubscriber subscriber = topicSession.createDurableSubscriber(topic, durableSubName); LOG.info("About to receive messages"); - Message message = subscriber.receive(120000); + Message message = subscriber.receive(1000); subscriber.close(); connection.close(); LOG.info("collectMessagesFromDurableSubscriptionForOneMinute done"); From d1b7e8ae5f434da480ab66f65e6e0b3a8ff048fc Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Thu, 5 Jun 2025 07:18:53 -0400 Subject: [PATCH 13/34] AMQ-9726 - Fix FilePendingMessageCursor clear() method (#1452) This fixes the clear() method so that when clearing the memory map it will decrement memory usage, and when clearing the disk list it will destroy and reset the list for future writes. (cherry picked from commit b1e8441ca93f0aefa8817c6923cc4741b757da3a) --- .../cursors/FilePendingMessageCursor.java | 15 +++- .../KahaDBFilePendingMessageCursorTest.java | 83 +++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/FilePendingMessageCursor.java b/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/FilePendingMessageCursor.java index 801208c7983..ed51ce8881d 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/FilePendingMessageCursor.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/region/cursors/FilePendingMessageCursor.java @@ -162,8 +162,7 @@ public synchronized void release() { @Override public synchronized void destroy() throws Exception { stop(); - for (Iterator i = memoryList.iterator(); i.hasNext();) { - MessageReference node = i.next(); + for (MessageReference node : memoryList) { node.decrementReferenceCount(); } memoryList.clear(); @@ -365,11 +364,19 @@ public synchronized long messageSize() { */ @Override public synchronized void clear() { + // AMQ-9726 - Iterate over all nodes to decrement the ref count + // to decrement the memory usage tracker + for (MessageReference node : memoryList) { + node.decrementReferenceCount(); + } memoryList.clear(); if (!isDiskListEmpty()) { try { - getDiskList().destroy(); - } catch (IOException e) { + // AMQ-9726 - This method will destroy the list and + // set the reference to null so it will be reset + // for future writes + destroyDiskList(); + } catch (Exception e) { throw new RuntimeException(e); } } diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/plist/KahaDBFilePendingMessageCursorTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/plist/KahaDBFilePendingMessageCursorTest.java index ce4a8ed0e1c..481950e4d04 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/plist/KahaDBFilePendingMessageCursorTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/plist/KahaDBFilePendingMessageCursorTest.java @@ -29,6 +29,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Hiram Chirino @@ -94,5 +96,86 @@ public void testAddRemoveAddIndexSize() throws Exception { assertEquals("expected page usage", initialPageCount -1, pageFile.getPageCount() - pageFile.getFreePageCount() ); } + // Test for AMQ-9726 + @Test + public void testClearCursor() throws Exception { + brokerService = new BrokerService(); + brokerService.setUseJmx(false); + SystemUsage usage = brokerService.getSystemUsage(); + usage.getMemoryUsage().setLimit(1024*150); + Destination dest = new Queue(brokerService, new ActiveMQQueue("Q"), null, new DestinationStatistics(), null); + dest.setMemoryUsage(usage.getMemoryUsage()); + brokerService.start(); + + underTest = new FilePendingMessageCursor(brokerService.getBroker(), "test", false); + underTest.setSystemUsage(usage); + + // Add 10 messages to the cursor in memory + addTestMessages(dest); + + // Verify memory usage was increased and cache is enabled + assertTrue(dest.getMemoryUsage().getUsage() > 0); + assertEquals(10, underTest.size()); + assertTrue(underTest.isCacheEnabled()); + assertEquals(0, dest.getTempUsage().getUsage()); + + // Clear, this will verify memory usage is correctly decremented + // and the memory map is cleared as well. Memory was previously + // incorrectly not being cleared. + underTest.clear(); + assertEquals(0, underTest.size()); + assertEquals(0, dest.getMemoryUsage().getUsage()); + + // Now test the disk cursor + // set the memory usage limit very small so messages will go to + // the disk list and not memory and send 10 more messages + usage.getMemoryUsage().setLimit(1); + addTestMessages(dest); + + // confirm the cache is false and the memory is 0 because + // the messages exist on disk and not in the memory map + // also very temp usage is greater than 0 now + assertFalse(underTest.isCacheEnabled()); + assertEquals(0, dest.getMemoryUsage().getUsage()); + assertTrue(dest.getTempUsage().getUsage() > 0); + assertEquals(10, underTest.size()); + + // Test clearing the disk list shows a size of 0 + underTest.clear(); + assertEquals(0, underTest.size()); + + // Send 10 more messages to verify that we can send again + // to the disk list after clear. Previously clear did not + // correctly destroy/reset the disk cursor so an exception + // was thrown when adding messages again after calling clear() + addTestMessages(dest); + assertFalse(underTest.isCacheEnabled()); + assertEquals(0, dest.getMemoryUsage().getUsage()); + assertTrue(dest.getTempUsage().getUsage() > 0); + assertEquals(10, underTest.size()); + + // one final clear() and reset limit to make sure we can send to + // memory again + underTest.clear(); + usage.getMemoryUsage().setLimit(1024*150); + assertEquals(0, underTest.size()); + assertEquals(0, dest.getMemoryUsage().getUsage()); + + // Verify memory usage was increased and cache is enabled + addTestMessages(dest); + assertTrue(dest.getMemoryUsage().getUsage() > 0); + assertEquals(10, underTest.size()); + assertTrue(underTest.isCacheEnabled()); + } + + private void addTestMessages(Destination dest) throws Exception { + for (int i = 0; i< 10; i++) { + ActiveMQMessage mqMessage = new ActiveMQMessage(); + mqMessage.setMessageId(new MessageId("1:2:3:" + i)); + mqMessage.setMemoryUsage(dest.getMemoryUsage()); + mqMessage.setRegionDestination(dest); + underTest.addMessageLast(new IndirectMessageReference(mqMessage)); + } + } } From 3d2e2a94d311e5ceebd1a0af98ef44c498513e0c Mon Sep 17 00:00:00 2001 From: "Christopher L. Shannon" Date: Wed, 23 Jul 2025 17:10:31 -0400 Subject: [PATCH 14/34] AMQ-9747 - Handle IOExceptionHandler thrown exceptions in KahaDB (#1474) Update the KahaDB scheduled tasks to catch any runtime exceptions thrown by the configured IOExceptionHandler. This will prevent the tasks from being killed and no longer running if the IOExceptionHandler does not shut down the broker. (cherry picked from commit 2eb0d6675008a5e7fd6cbec4a177ab13fde3b8f2) --- .../store/kahadb/MessageDatabase.java | 35 ++++++++-- .../activemq/store/kahadb/KahaDBTest.java | 69 ++++++++++++++++++- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MessageDatabase.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MessageDatabase.java index 00b9bce25b1..c8a9cc91926 100644 --- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MessageDatabase.java +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MessageDatabase.java @@ -448,10 +448,10 @@ public void run() { } } catch (IOException ioe) { LOG.error("Checkpoint failed", ioe); - brokerService.handleIOException(ioe); + handleIOException("CheckpointRunner", ioe); } catch (Throwable e) { LOG.error("Checkpoint failed", e); - brokerService.handleIOException(IOExceptionSupport.create(e)); + handleIOException("CheckpointRunner", IOExceptionSupport.create(e)); } } } @@ -2120,10 +2120,10 @@ public void run() { forwarded = true; } catch (IOException ioe) { LOG.error("Forwarding of acks failed", ioe); - brokerService.handleIOException(ioe); + handleIOException("AckCompactionRunner", ioe); } catch (Throwable e) { LOG.error("Forwarding of acks failed", e); - brokerService.handleIOException(IOExceptionSupport.create(e)); + handleIOException("AckCompactionRunner", IOExceptionSupport.create(e)); } } finally { checkpointLock.readLock().unlock(); @@ -2136,10 +2136,10 @@ public void run() { } } catch (IOException ioe) { LOG.error("Checkpoint failed", ioe); - brokerService.handleIOException(ioe); + handleIOException("AckCompactionRunner", ioe); } catch (Throwable e) { LOG.error("Checkpoint failed", e); - brokerService.handleIOException(IOExceptionSupport.create(e)); + handleIOException("AckCompactionRunner", IOExceptionSupport.create(e)); } } } @@ -4274,4 +4274,27 @@ protected Class resolveClass(ObjectStreamClass desc) throws IOException, Clas } } + + /* + * Execute the configured IOExceptionHandler when an IOException is thrown during + * task execution and handle any runtime exceptions that the handler itself might throw. + * + * By default, the DefaultIOExceptionHandler will stop the broker when handling an IOException, + * however, if DefaultIOExceptionHandler is configured with startStopConnectors to be true + * it will throw a SuppressReplyException and not stop the broker. It's also possible another + * custom implementation of IOExceptionHandler could throw a runtime exception. + * + * This method will now handle and log those runtime exceptions so that the task will not + * die and will continue to execute future iterations if the broker is not shut down. + */ + private void handleIOException(String taskName, IOException ioe) { + try { + brokerService.handleIOException(ioe); + } catch (RuntimeException e) { + LOG.warn("IOException handler threw exception in task {} with " + + "error: {}, continuing.", taskName, + e.getMessage()); + LOG.debug(e.getMessage(), e); + } + } } diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBTest.java index 53306bd7558..8cf84fb0d60 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBTest.java @@ -27,11 +27,15 @@ import javax.jms.MessageProducer; import javax.jms.Session; +import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.command.ActiveMQQueue; +import org.apache.activemq.util.DefaultIOExceptionHandler; +import org.apache.activemq.util.IOExceptionHandler; +import org.apache.activemq.util.Wait; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LogEvent; @@ -236,6 +240,69 @@ public void append(LogEvent event) { assertFalse("Did not replay any records from the journal", didSomeRecovery.get()); } + + /** + * Test the checkpoint runner task continues to run if the configured + * IOExceptionHandler throws a runtime exception while processing + * the IOException it is handling and the broker is not shut down. This + * could happen if using the DefaultIOExceptionHandler and startStopConnectors + * is set to true, or if a user provides their own IOExceptionHandler that + * throws an exception. + */ + public void testCheckpointExceptionKeepRunning() throws Exception { + testCheckpointIOException(true); + } + + /** + * Test the broker shuts down by when DefaultIOExceptionHandler + * handles an IOException thrown by the checkpoint runner task. This is the + * default behavior of the broker if not configured with a custom + * IOExceptionHandler and startStopConnectors is false + */ + public void testCheckpointExceptionShutdown() throws Exception { + testCheckpointIOException(false); + } + + private void testCheckpointIOException(boolean startStopConnectors) throws Exception { + final AtomicInteger iterations = new AtomicInteger(); + // Create a store that always throws an IOException when checkpoint is called + final KahaDBStore kaha = new KahaDBStore() { + @Override + protected void checkpointCleanup(boolean cleanup) throws IOException { + iterations.incrementAndGet(); + throw new IOException("fail"); + } + }; + kaha.setDirectory(new File("target/activemq-data/kahadb")); + kaha.deleteAllMessages(); + // Set the checkpoint interval to be very short so we can quickly + // check number of iterations + kaha.setCheckpointInterval(100); + + BrokerService broker = createBroker(kaha); + DefaultIOExceptionHandler ioExceptionHandler = new DefaultIOExceptionHandler(); + ioExceptionHandler.setStopStartConnectors(startStopConnectors); + broker.setIoExceptionHandler(ioExceptionHandler); + broker.start(); + + try { + if (startStopConnectors) { + // If startStopConnectors is true, the task should continue with future iterations + // as the SuppressReplyException that will be thrown is now handled so just verify + // we see 10 iterations which should happen quickly + assertTrue(Wait.waitFor(() -> iterations.get() == 10, 2000, 100)); + // broker should not be stopped + assertFalse(broker.isStopped()); + } else { + // If startStopConnectors is false, an IOException should shut down the broker + // which is the normal behavior + assertTrue(Wait.waitFor(broker::isStopped, 2000, 100)); + } + } finally { + broker.stop(); + } + } + private void assertExistsAndDelete(File file) { assertTrue(file.exists()); file.delete(); @@ -281,4 +348,4 @@ private String createContent(int i) { return sb.toString(); } -} \ No newline at end of file +} From 4cbfcbda7f300af1a34a8a02fcdc9bca1782c9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sun, 25 May 2025 18:07:27 +0200 Subject: [PATCH 15/34] AMQ-9700: Upgrade to commons-io 2.19.0 (#1433) (cherry picked from commit 98611c21ac67acbe3657b6ca1bb2227af02aa80c) (cherry picked from commit e2c1a1ae1848e856e892a34fcca30b71d9ea2995) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b940d4fb8ca..9a8fdce79ed 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ 3.2.2 1.4.1 2.13.0 - 2.18.0 + 2.19.0 3.14.0 1.3.5 2.12.1 From ca7e911d5a1c411b307d4f30efac5366885b9b57 Mon Sep 17 00:00:00 2001 From: Colm O hEigeartaigh Date: Mon, 25 Aug 2025 15:30:32 +0100 Subject: [PATCH 16/34] AMQ-9759: Updating Commons Lang to 3.18.0 (cherry picked from commit dc7a802fd41b42f146215467687e370e2022f52a) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a8fdce79ed..cf2dea5721e 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ 1.4.1 2.13.0 2.19.0 - 3.14.0 + 3.18.0 1.3.5 2.12.1 1.0 From 2ef99b73d643dce870283d4d2991a2724cf0f4e8 Mon Sep 17 00:00:00 2001 From: Matt Pavlovich Date: Wed, 24 Sep 2025 18:02:12 -0500 Subject: [PATCH 17/34] [AMQ-9773] Fix for only one message being recovered from backup (cherry picked from commit ec633b3d9b0398cbe7814cd6bccf23f6c62fab15) --- .../activemq/store/kahadb/KahaDBStore.java | 21 ++++++++--- .../KahaDBOffsetRecoveryListenerTest.java | 37 +++++++++++++------ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java index b6f77c31658..6274f506cea 100644 --- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java @@ -754,6 +754,14 @@ public void recoverMessages(final MessageRecoveryContext messageRecoveryContext) public void execute(Transaction tx) throws Exception { StoredDestination sd = getStoredDestination(dest, tx); + /* + The endSequenceOffset is used when only endMessageId is requested + there is a disconnect between iterator offset and a destination's + sequence key. + + If a destination has already processed messages, then the sequence key + value is the number of total messages processed through the queue all-time. + */ Long startSequenceOffset = null; Long endSequenceOffset = null; @@ -766,16 +774,15 @@ public void execute(Transaction tx) throws Exception { if(messageRecoveryContext.getEndMessageId() != null && !messageRecoveryContext.getEndMessageId().isBlank()) { endSequenceOffset = Optional.ofNullable(sd.messageIdIndex.get(tx, messageRecoveryContext.getEndMessageId())) .orElse(startSequenceOffset + Long.valueOf(messageRecoveryContext.getMaxMessageCountReturned())); - } else { - endSequenceOffset = startSequenceOffset + Long.valueOf(messageRecoveryContext.getMaxMessageCountReturned()); + messageRecoveryContext.setEndSequenceId(endSequenceOffset); } - if(endSequenceOffset < startSequenceOffset) { + if(endSequenceOffset != null && + endSequenceOffset < startSequenceOffset) { LOG.warn("Invalid offset parameters start:{} end:{}", startSequenceOffset, endSequenceOffset); throw new IllegalStateException("Invalid offset parameters start:" + startSequenceOffset + " end:" + endSequenceOffset); } - messageRecoveryContext.setEndSequenceId(endSequenceOffset); Entry entry = null; recoverRolledBackAcks(destination.getPhysicalName(), sd, tx, messageRecoveryContext.getMaxMessageCountReturned(), messageRecoveryContext); Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName()); @@ -796,7 +803,11 @@ public void execute(Transaction tx) throws Exception { break; } } - sd.orderIndex.stoppedIterating(); + + // The sd.orderIndex uses the destination's cursor + if(!messageRecoveryContext.isUseDedicatedCursor()) { + sd.orderIndex.stoppedIterating(); + } } }); } finally { diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBOffsetRecoveryListenerTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBOffsetRecoveryListenerTest.java index f5df1fb9c82..0adaac052ee 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBOffsetRecoveryListenerTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/store/kahadb/KahaDBOffsetRecoveryListenerTest.java @@ -32,7 +32,9 @@ import org.apache.activemq.store.MessageStore; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,9 +54,17 @@ public class KahaDBOffsetRecoveryListenerTest { protected BrokerService brokerService = null; protected BrokerService restartBrokerService = null; + @Rule + public TestName testName = new TestName(); + + protected final int PRETEST_MSG_COUNT = 17531; + @Before public void beforeEach() throws Exception { - + // Send+Recv a odd number of messages beyond cache sizes + // to confirm the queue's sequence number gets pushed off + sendMessages(PRETEST_MSG_COUNT, testName.getMethodName()); + assertEquals(Integer.valueOf(PRETEST_MSG_COUNT), Integer.valueOf(receiveMessages(testName.getMethodName()))); } @After @@ -68,7 +78,7 @@ protected BrokerService createBroker(KahaDBStore kaha) throws Exception { broker.setUseJmx(false); broker.setPersistenceAdapter(kaha); broker.start(); - broker.waitUntilStarted(10_000l); + broker.waitUntilStarted(10_000L); return broker; } @@ -130,7 +140,7 @@ protected void runOffsetLoopTest(final int sendCount, final int expectedMessageC restartBrokerService = createBroker(createStore(false)); restartBrokerService.start(); - restartBrokerService.waitUntilStarted(30_000l); + restartBrokerService.waitUntilStarted(30_000L); assertEquals(sendCount, receiveMessages(queueName)); restartBrokerService.stop(); @@ -139,42 +149,42 @@ protected void runOffsetLoopTest(final int sendCount, final int expectedMessageC @Test public void testOffsetZero() throws Exception { - runOffsetTest(1_000, 1_000, 0, 1, 1, 0, "TEST.OFFSET.ZERO"); + runOffsetTest(1_000, 1_000, 0, 1, 1, 0, testName.getMethodName()); } @Test public void testOffsetOne() throws Exception { - runOffsetTest(1_000, 1_000, 1, 1, 1, 1, "TEST.OFFSET.ONE"); + runOffsetTest(1_000, 1_000, 1, 1, 1, 1, testName.getMethodName()); } @Test public void testOffsetLastMinusOne() throws Exception { - runOffsetTest(1_000, 1_000, 999, 1, 1, 999, "TEST.OFFSET.LASTMINUSONE"); + runOffsetTest(1_000, 1_000, 999, 1, 1, 999, testName.getMethodName()); } @Test public void testOffsetLast() throws Exception { - runOffsetTest(1_000, 1_000, 1_000, 1, 0, -1, "TEST.OFFSET.LAST"); + runOffsetTest(1_000, 1_000, 1_000, 1, 0, -1, testName.getMethodName()); } @Test public void testOffsetBeyondQueueSizeNoError() throws Exception { - runOffsetTest(1_000, 1_000, 10_000, 1, 0, -1, "TEST.OFFSET.BEYOND"); + runOffsetTest(1_000, 1_000, 10_000, 1, 0, -1, testName.getMethodName()); } @Test public void testOffsetEmptyQueue() throws Exception { - runOffsetTest(0, 0, 10_000, 1, 0, -1, "TEST.OFFSET.EMPTY"); + runOffsetTest(0, 0, 10_000, 1, 0, -1, testName.getMethodName()); } @Test public void testOffsetWalk() throws Exception { - runOffsetLoopTest(10_000, 10_000, 9_000, 200, 200, 9_000, "TEST.OFFSET.WALK", 8, false); + runOffsetLoopTest(10_000, 10_000, 9_000, 200, 200, 9_000, testName.getMethodName(), 8, false); } @Test public void testOffsetRepeat() throws Exception { - runOffsetLoopTest(10_000, 10_000, 7_000, 133, 133, 7_000, "TEST.OFFSET.REPEAT", 10, true); + runOffsetLoopTest(10_000, 10_000, 7_000, 133, 133, 7_000, testName.getMethodName(), 10, true); } private void sendMessages(int count, String queueName) throws JMSException { @@ -198,12 +208,15 @@ private void sendMessages(int count, String queueName) throws JMSException { private int receiveMessages(String queueName) throws JMSException { int rc=0; ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost"); + cf.setWatchTopicAdvisories(false); + Connection connection = cf.createConnection(); + try { connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer messageConsumer = session.createConsumer(new ActiveMQQueue(queueName)); - while ( messageConsumer.receive(1000) !=null ) { + while (messageConsumer.receive(1_000L) != null) { rc++; } return rc; From aeeaa070a1cd444942d45465e94c95a6dcd6ff75 Mon Sep 17 00:00:00 2001 From: Matt Pavlovich Date: Fri, 26 Sep 2025 17:40:20 -0500 Subject: [PATCH 18/34] [AMQ-9773-b] Update code comments for backup only recoverying one message (cherry picked from commit 96c92e4d3cf62b11d1b3ceaafac1f2752cfdc88e) --- .../activemq/store/kahadb/KahaDBStore.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java index 6274f506cea..63273bcce72 100644 --- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/KahaDBStore.java @@ -754,13 +754,24 @@ public void recoverMessages(final MessageRecoveryContext messageRecoveryContext) public void execute(Transaction tx) throws Exception { StoredDestination sd = getStoredDestination(dest, tx); - /* - The endSequenceOffset is used when only endMessageId is requested - there is a disconnect between iterator offset and a destination's - sequence key. - - If a destination has already processed messages, then the sequence key - value is the number of total messages processed through the queue all-time. + /** + * [AMQ-9773] + * + * The order index sequence key value increases for every message since the beginning of the + * creation of the index, so the first message available won't be at sequence key value:0 in + * the index when there were older messages acknowledged. + * + * The MessageOrderCursor _position_ value is relative to the index, so there is a disconnect + * between queue _position_ and index sequence value over time. + * + * The MessageRecoveryContext determines the recovery start position based off the provided + * offset, or the position of the requested startMessageId. If a startMessageId is specified, + * but not found in the index, then the value of 0 is used as a fallback. + * + * The MessageRecoveryContext determines the recovery end position based off of the provided + * endMessageId (if provided), or the maximum recovered message count, or if the + * MessageRecoveryListener signals that no more messages should be recovered + * (ie memory is full). */ Long startSequenceOffset = null; Long endSequenceOffset = null; @@ -804,7 +815,7 @@ public void execute(Transaction tx) throws Exception { } } - // The sd.orderIndex uses the destination's cursor + // [AMQ-9773] The sd.orderIndex uses the destination's cursor if(!messageRecoveryContext.isUseDedicatedCursor()) { sd.orderIndex.stoppedIterating(); } From 1bf66d8596f5e8ff5f156579ef2c700c24f2e6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Fri, 3 Oct 2025 09:34:55 +0200 Subject: [PATCH 19/34] AMQ-9762: Upgrade to Jackson 2.20.0 --- activemq-karaf/src/main/resources/features-core.xml | 2 +- pom.xml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/activemq-karaf/src/main/resources/features-core.xml b/activemq-karaf/src/main/resources/features-core.xml index 197399a067f..7f6573d35b6 100644 --- a/activemq-karaf/src/main/resources/features-core.xml +++ b/activemq-karaf/src/main/resources/features-core.xml @@ -59,7 +59,7 @@ mvn:org.codehaus.jettison/jettison/${jettison-version} mvn:com.fasterxml.jackson.core/jackson-core/${jackson-version} mvn:com.fasterxml.jackson.core/jackson-databind/${jackson-version} - mvn:com.fasterxml.jackson.core/jackson-annotations/${jackson-version} + mvn:com.fasterxml.jackson.core/jackson-annotations/${jackson-annotations-version} diff --git a/pom.xml b/pom.xml index cf2dea5721e..49e150599aa 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,8 @@ 1.2.0.Beta4 2.0.3 3.1.0 - 2.18.3 + 2.20.0 + 2.20 1.9.3 2.3.2_1 9.4.57.v20241219 @@ -718,7 +719,7 @@ com.fasterxml.jackson.core jackson-annotations - ${jackson-version} + ${jackson-annotations-version} com.fasterxml.jackson.core From b5775d198ac6d5ca28066a50173f278a89dd1b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Fri, 3 Oct 2025 15:24:34 +0200 Subject: [PATCH 20/34] AMQ-9717: Upgrade to commons-beanutils 1.11.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 49e150599aa..cd50b5f77b0 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ 1.0-M3-dev 2.25.4 [2.20,4) - 1.10.1 + 1.11.0 3.2.2 1.4.1 2.13.0 From 73d8bd7cbfd4e554234c52191ab9daeabef6504f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sat, 4 Oct 2025 13:04:37 +0200 Subject: [PATCH 21/34] AMQ-9763: Upgrade to commons-io 2.20.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cd50b5f77b0..96bc4cb09cb 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ 3.2.2 1.4.1 2.13.0 - 2.19.0 + 2.20.0 3.18.0 1.3.5 2.12.1 From 41fa2d7423a4823280886a3e64ff02ed30d95e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sat, 4 Oct 2025 14:16:22 +0200 Subject: [PATCH 22/34] AMQ-9701: Upgrade to XBean 4.27 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 96bc4cb09cb..227d67bfb7d 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ 2.4.1 1.1.4c 1.4.21 - 4.26 + 4.27 2.12.2 0.12.0 1.19 From ade46fd8cbc781378dc416245435b640b71c7ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sat, 4 Oct 2025 14:51:15 +0200 Subject: [PATCH 23/34] AMQ-9705: Upgrade to ASM 9.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 227d67bfb7d..b6fa9e300ab 100644 --- a/pom.xml +++ b/pom.xml @@ -489,7 +489,7 @@ org.ow2.asm asm - 9.7.1 + 9.8 From f1c719db51370b4497ad8ce427e691c46b6a3995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sat, 4 Oct 2025 14:56:11 +0200 Subject: [PATCH 24/34] AMQ-9767: Upgrade to jmdns 3.6.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b6fa9e300ab..b771a02fe65 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,7 @@ 2.3.2_1 9.4.57.v20241219 ${jetty9-version} - 3.6.0 + 3.6.2 9.0.100 3.30.2-GA 1.5.4 From a2246e3248a6a1e03fdebbc778a5f3adda9bead9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sat, 4 Oct 2025 15:12:08 +0200 Subject: [PATCH 25/34] Upgrade to Jetty 9.4.58.v20250814 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b771a02fe65..35b2916eb29 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ 2.20 1.9.3 2.3.2_1 - 9.4.57.v20241219 + 9.4.58.v20250814 ${jetty9-version} 3.6.2 9.0.100 From 9f4171ce2bcea386cdee0661b69d5aee5bf777fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sat, 4 Oct 2025 15:46:17 +0200 Subject: [PATCH 26/34] Prepare ActiveMQ 5.19.1 release --- activemq-osgi/src/main/resources/META-INF/spring.schemas | 1 + activemq-spring/src/main/resources/META-INF/spring.schemas | 1 + 2 files changed, 2 insertions(+) diff --git a/activemq-osgi/src/main/resources/META-INF/spring.schemas b/activemq-osgi/src/main/resources/META-INF/spring.schemas index dd0abcad6a6..ea744c73365 100644 --- a/activemq-osgi/src/main/resources/META-INF/spring.schemas +++ b/activemq-osgi/src/main/resources/META-INF/spring.schemas @@ -90,6 +90,7 @@ http\://activemq.apache.org/schema/core/activemq-core-5.18.4.xsd=activemq.xsd http\://activemq.apache.org/schema/core/activemq-core-5.18.5.xsd=activemq.xsd http\://activemq.apache.org/schema/core/activemq-core-5.18.6.xsd=activemq.xsd http\://activemq.apache.org/schema/core/activemq-core-5.19.0.xsd=activemq.xsd +http\://activemq.apache.org/schema/core/activemq-core-5.19.1.xsd=activemq.xsd http\://camel.apache.org/schema/spring/camel-spring.xsd=camel-spring.xsd diff --git a/activemq-spring/src/main/resources/META-INF/spring.schemas b/activemq-spring/src/main/resources/META-INF/spring.schemas index fe9dc7cb758..245cda50cfd 100644 --- a/activemq-spring/src/main/resources/META-INF/spring.schemas +++ b/activemq-spring/src/main/resources/META-INF/spring.schemas @@ -90,6 +90,7 @@ http\://activemq.apache.org/schema/core/activemq-core-5.18.4.xsd=activemq.xsd http\://activemq.apache.org/schema/core/activemq-core-5.18.5.xsd=activemq.xsd http\://activemq.apache.org/schema/core/activemq-core-5.18.6.xsd=activemq.xsd http\://activemq.apache.org/schema/core/activemq-core-5.19.0.xsd=activemq.xsd +http\://activemq.apache.org/schema/core/activemq-core-5.19.1.xsd=activemq.xsd http\://camel.apache.org/schema/osgi/camel-osgi.xsd=camel-osgi.xsd http\://camel.apache.org/schema/spring/camel-spring.xsd=camel-spring.xsd From 6e856394dd60c98676cf902127e75dab3acce2e2 Mon Sep 17 00:00:00 2001 From: Matt Pavlovich Date: Tue, 7 Oct 2025 09:33:18 -0500 Subject: [PATCH 27/34] [AMQ-9780] Add a null guard during checkpointUpdate when kahadb is logging in TRACE mode (#1506) --- .../java/org/apache/activemq/store/kahadb/MessageDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MessageDatabase.java b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MessageDatabase.java index c8a9cc91926..33fead27494 100644 --- a/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MessageDatabase.java +++ b/activemq-kahadb-store/src/main/java/org/apache/activemq/store/kahadb/MessageDatabase.java @@ -1967,7 +1967,7 @@ public void visit(List keys, List values) { final StoredDestination destination = entry.getValue(); final String subscriptionKey = subscription.getKey(); final SequenceSet pendingAcks = destination.ackPositions.get(tx, subscriptionKey); - LOG.trace("sub {} on {} in dataFile {} has pendingCount {}", subscriptionKey, entry.getKey(), dataFileId, pendingAcks.rangeSize()-1); + LOG.trace("sub {} on {} in dataFile {} has pendingCount {}", subscriptionKey, entry.getKey(), dataFileId, (pendingAcks != null ? pendingAcks.rangeSize()-1 : 0)); } gcCandidateSet.remove(dataFileId); } From 88b84285e0992a2127d180fa01119fe512663516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Wed, 8 Oct 2025 05:07:55 -0700 Subject: [PATCH 28/34] AMQ-9781: Upgrade to ASM 9.9 (#1507) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 35b2916eb29..35a1c9d6190 100644 --- a/pom.xml +++ b/pom.xml @@ -489,7 +489,7 @@ org.ow2.asm asm - 9.8 + 9.9 From 242ff9b0db9f8c0d155876ee3a5092364fb5443d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Wed, 8 Oct 2025 05:28:29 -0700 Subject: [PATCH 29/34] [maven-release-plugin] prepare release activemq-5.19.1 --- activemq-all/pom.xml | 2 +- activemq-amqp/pom.xml | 2 +- activemq-blueprint/pom.xml | 2 +- activemq-broker/pom.xml | 2 +- activemq-cf/pom.xml | 2 +- activemq-client-jakarta/pom.xml | 2 +- activemq-client/pom.xml | 2 +- activemq-console/pom.xml | 2 +- activemq-http/pom.xml | 2 +- activemq-jaas/pom.xml | 2 +- activemq-jdbc-store/pom.xml | 2 +- activemq-jms-pool/pom.xml | 2 +- activemq-kahadb-store/pom.xml | 2 +- activemq-karaf-itest/pom.xml | 2 +- activemq-karaf/pom.xml | 2 +- activemq-log4j-appender/pom.xml | 2 +- activemq-mqtt/pom.xml | 2 +- activemq-openwire-generator/pom.xml | 2 +- activemq-openwire-legacy/pom.xml | 2 +- activemq-osgi/pom.xml | 2 +- activemq-partition/pom.xml | 2 +- activemq-pool/pom.xml | 2 +- activemq-ra/pom.xml | 2 +- activemq-rar/pom.xml | 2 +- activemq-run/pom.xml | 2 +- activemq-runtime-config/pom.xml | 2 +- activemq-shiro/pom.xml | 2 +- activemq-spring/pom.xml | 2 +- activemq-stomp/pom.xml | 2 +- activemq-tooling/activemq-junit/pom.xml | 2 +- activemq-tooling/activemq-maven-plugin/pom.xml | 2 +- activemq-tooling/activemq-memtest-maven-plugin/pom.xml | 2 +- activemq-tooling/activemq-perf-maven-plugin/pom.xml | 2 +- activemq-tooling/pom.xml | 2 +- activemq-unit-tests/pom.xml | 2 +- activemq-web-console/pom.xml | 2 +- activemq-web-demo/pom.xml | 2 +- activemq-web/pom.xml | 2 +- assembly/pom.xml | 2 +- pom.xml | 6 +++--- 40 files changed, 42 insertions(+), 42 deletions(-) diff --git a/activemq-all/pom.xml b/activemq-all/pom.xml index 3f65d6e5685..9dad804a189 100644 --- a/activemq-all/pom.xml +++ b/activemq-all/pom.xml @@ -14,7 +14,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-all diff --git a/activemq-amqp/pom.xml b/activemq-amqp/pom.xml index 59bd7a06a86..f23ac67f4a8 100644 --- a/activemq-amqp/pom.xml +++ b/activemq-amqp/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-amqp diff --git a/activemq-blueprint/pom.xml b/activemq-blueprint/pom.xml index 1f548c6a4cd..7be03fed022 100644 --- a/activemq-blueprint/pom.xml +++ b/activemq-blueprint/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-blueprint diff --git a/activemq-broker/pom.xml b/activemq-broker/pom.xml index 3b79ffa5e68..7b8ca1754bc 100644 --- a/activemq-broker/pom.xml +++ b/activemq-broker/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-broker diff --git a/activemq-cf/pom.xml b/activemq-cf/pom.xml index 0412fce7c88..54a4402c687 100644 --- a/activemq-cf/pom.xml +++ b/activemq-cf/pom.xml @@ -24,7 +24,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-cf diff --git a/activemq-client-jakarta/pom.xml b/activemq-client-jakarta/pom.xml index aa201ca0fa1..133dc7d3575 100644 --- a/activemq-client-jakarta/pom.xml +++ b/activemq-client-jakarta/pom.xml @@ -20,7 +20,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-client-jakarta bundle diff --git a/activemq-client/pom.xml b/activemq-client/pom.xml index 0f1337e3c22..e43777600b3 100644 --- a/activemq-client/pom.xml +++ b/activemq-client/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-client diff --git a/activemq-console/pom.xml b/activemq-console/pom.xml index 65af3c78f6e..5f54a7c5b70 100644 --- a/activemq-console/pom.xml +++ b/activemq-console/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-console diff --git a/activemq-http/pom.xml b/activemq-http/pom.xml index a6637535008..7d2f5c8139b 100644 --- a/activemq-http/pom.xml +++ b/activemq-http/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-http diff --git a/activemq-jaas/pom.xml b/activemq-jaas/pom.xml index 11384e506ea..496d5596d53 100644 --- a/activemq-jaas/pom.xml +++ b/activemq-jaas/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-jaas diff --git a/activemq-jdbc-store/pom.xml b/activemq-jdbc-store/pom.xml index 6f5b4b9e9ad..6025a614a69 100644 --- a/activemq-jdbc-store/pom.xml +++ b/activemq-jdbc-store/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-jdbc-store diff --git a/activemq-jms-pool/pom.xml b/activemq-jms-pool/pom.xml index cc3b011f3fa..26fab6840c0 100644 --- a/activemq-jms-pool/pom.xml +++ b/activemq-jms-pool/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-jms-pool diff --git a/activemq-kahadb-store/pom.xml b/activemq-kahadb-store/pom.xml index 1226050c5d5..dde26897d9e 100644 --- a/activemq-kahadb-store/pom.xml +++ b/activemq-kahadb-store/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-kahadb-store diff --git a/activemq-karaf-itest/pom.xml b/activemq-karaf-itest/pom.xml index dcb55592362..170b67f3a76 100644 --- a/activemq-karaf-itest/pom.xml +++ b/activemq-karaf-itest/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-karaf-itest diff --git a/activemq-karaf/pom.xml b/activemq-karaf/pom.xml index 4e1cc4d2c19..4be3bc62adf 100644 --- a/activemq-karaf/pom.xml +++ b/activemq-karaf/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-karaf diff --git a/activemq-log4j-appender/pom.xml b/activemq-log4j-appender/pom.xml index 43e417717e5..ae6be079f8a 100644 --- a/activemq-log4j-appender/pom.xml +++ b/activemq-log4j-appender/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-log4j-appender diff --git a/activemq-mqtt/pom.xml b/activemq-mqtt/pom.xml index 78120c9f92a..d74ecf5e547 100644 --- a/activemq-mqtt/pom.xml +++ b/activemq-mqtt/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-mqtt diff --git a/activemq-openwire-generator/pom.xml b/activemq-openwire-generator/pom.xml index b5ab9e0cf5e..4e47e883f3f 100644 --- a/activemq-openwire-generator/pom.xml +++ b/activemq-openwire-generator/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-openwire-generator diff --git a/activemq-openwire-legacy/pom.xml b/activemq-openwire-legacy/pom.xml index 5bf482ef81f..5023210cca0 100644 --- a/activemq-openwire-legacy/pom.xml +++ b/activemq-openwire-legacy/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-openwire-legacy diff --git a/activemq-osgi/pom.xml b/activemq-osgi/pom.xml index a2c8fe7d602..862b72dc60a 100644 --- a/activemq-osgi/pom.xml +++ b/activemq-osgi/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-osgi diff --git a/activemq-partition/pom.xml b/activemq-partition/pom.xml index 043067d86a1..126fa07185c 100644 --- a/activemq-partition/pom.xml +++ b/activemq-partition/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-partition diff --git a/activemq-pool/pom.xml b/activemq-pool/pom.xml index 0c7e4f3a655..0508aedb0ea 100644 --- a/activemq-pool/pom.xml +++ b/activemq-pool/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-pool diff --git a/activemq-ra/pom.xml b/activemq-ra/pom.xml index 29b64745763..05966a2fc3e 100644 --- a/activemq-ra/pom.xml +++ b/activemq-ra/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-ra diff --git a/activemq-rar/pom.xml b/activemq-rar/pom.xml index b6357f51e82..21bb1a94694 100644 --- a/activemq-rar/pom.xml +++ b/activemq-rar/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-rar diff --git a/activemq-run/pom.xml b/activemq-run/pom.xml index 77e06426e9b..03ca4549b21 100644 --- a/activemq-run/pom.xml +++ b/activemq-run/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-run diff --git a/activemq-runtime-config/pom.xml b/activemq-runtime-config/pom.xml index 20487744eef..c4cd562ac92 100644 --- a/activemq-runtime-config/pom.xml +++ b/activemq-runtime-config/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-runtime-config diff --git a/activemq-shiro/pom.xml b/activemq-shiro/pom.xml index 940cadcc151..23487fb6c58 100644 --- a/activemq-shiro/pom.xml +++ b/activemq-shiro/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-shiro diff --git a/activemq-spring/pom.xml b/activemq-spring/pom.xml index 40d0dc06efe..ff52953be41 100644 --- a/activemq-spring/pom.xml +++ b/activemq-spring/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-spring diff --git a/activemq-stomp/pom.xml b/activemq-stomp/pom.xml index 8740dc6c6c6..8a8b84e2028 100644 --- a/activemq-stomp/pom.xml +++ b/activemq-stomp/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-stomp diff --git a/activemq-tooling/activemq-junit/pom.xml b/activemq-tooling/activemq-junit/pom.xml index 48b906c7e78..16008d84e9c 100644 --- a/activemq-tooling/activemq-junit/pom.xml +++ b/activemq-tooling/activemq-junit/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.1-SNAPSHOT + 5.19.1 activemq-junit diff --git a/activemq-tooling/activemq-maven-plugin/pom.xml b/activemq-tooling/activemq-maven-plugin/pom.xml index 5dc37e467a0..35a7d858c21 100644 --- a/activemq-tooling/activemq-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-maven-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.1-SNAPSHOT + 5.19.1 activemq-maven-plugin diff --git a/activemq-tooling/activemq-memtest-maven-plugin/pom.xml b/activemq-tooling/activemq-memtest-maven-plugin/pom.xml index 61d616ae536..e5b364a7372 100644 --- a/activemq-tooling/activemq-memtest-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-memtest-maven-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.1-SNAPSHOT + 5.19.1 activemq-memtest-maven-plugin diff --git a/activemq-tooling/activemq-perf-maven-plugin/pom.xml b/activemq-tooling/activemq-perf-maven-plugin/pom.xml index 04d09e78b1b..723457a22e0 100644 --- a/activemq-tooling/activemq-perf-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-perf-maven-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.1-SNAPSHOT + 5.19.1 activemq-perf-maven-plugin diff --git a/activemq-tooling/pom.xml b/activemq-tooling/pom.xml index 860e7ab3b33..e7f170bf851 100644 --- a/activemq-tooling/pom.xml +++ b/activemq-tooling/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 org.apache.activemq.tooling diff --git a/activemq-unit-tests/pom.xml b/activemq-unit-tests/pom.xml index ed2667b006a..b88ddc83258 100644 --- a/activemq-unit-tests/pom.xml +++ b/activemq-unit-tests/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-unit-tests diff --git a/activemq-web-console/pom.xml b/activemq-web-console/pom.xml index 42e5437f974..49a32c2dcfd 100644 --- a/activemq-web-console/pom.xml +++ b/activemq-web-console/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-web-console diff --git a/activemq-web-demo/pom.xml b/activemq-web-demo/pom.xml index 5fadbbd0771..289b484443d 100644 --- a/activemq-web-demo/pom.xml +++ b/activemq-web-demo/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-web-demo diff --git a/activemq-web/pom.xml b/activemq-web/pom.xml index b2883ffc3ba..cc88deb82b5 100644 --- a/activemq-web/pom.xml +++ b/activemq-web/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 activemq-web diff --git a/assembly/pom.xml b/assembly/pom.xml index a6a8b5c2ce6..fb4fb135416 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 apache-activemq diff --git a/pom.xml b/pom.xml index 35a1c9d6190..521e3a0b807 100644 --- a/pom.xml +++ b/pom.xml @@ -25,14 +25,14 @@ org.apache.activemq activemq-parent - 5.19.1-SNAPSHOT + 5.19.1 pom ActiveMQ 2005 - 2025-03-07T13:27:22Z + 2025-10-08T12:16:52Z 3.1.4 activemq-${project.version} @@ -248,7 +248,7 @@ scm:git:http://gitbox.apache.org/repos/asf/activemq.git scm:git:https://gitbox.apache.org/repos/asf/activemq.git https://github.com/apache/activemq - main + activemq-5.19.1 From 9c7dac749e282b3d72e68137f4e47d8884bafdaa Mon Sep 17 00:00:00 2001 From: Jonathan Gallimore Date: Thu, 13 Nov 2025 15:26:57 +0000 Subject: [PATCH 30/34] Bump project versions to 5.19.2-TT.1-SNAPSHOT - Updated all tags in pom.xml files - Left SCM and repository settings unchanged --- activemq-all/pom.xml | 2 +- activemq-amqp/pom.xml | 2 +- activemq-blueprint/pom.xml | 2 +- activemq-broker/pom.xml | 2 +- activemq-cf/pom.xml | 2 +- activemq-client-jakarta/pom.xml | 2 +- activemq-client/pom.xml | 2 +- activemq-console/pom.xml | 2 +- activemq-http/pom.xml | 2 +- activemq-jaas/pom.xml | 2 +- activemq-jdbc-store/pom.xml | 2 +- activemq-jms-pool/pom.xml | 2 +- activemq-kahadb-store/pom.xml | 2 +- activemq-karaf-itest/pom.xml | 2 +- activemq-karaf/pom.xml | 2 +- activemq-log4j-appender/pom.xml | 2 +- activemq-mqtt/pom.xml | 2 +- activemq-openwire-generator/pom.xml | 2 +- activemq-openwire-legacy/pom.xml | 2 +- activemq-osgi/pom.xml | 2 +- activemq-partition/pom.xml | 2 +- activemq-pool/pom.xml | 2 +- activemq-ra/pom.xml | 2 +- activemq-rar/pom.xml | 2 +- activemq-run/pom.xml | 2 +- activemq-runtime-config/pom.xml | 2 +- activemq-shiro/pom.xml | 2 +- activemq-spring/pom.xml | 2 +- activemq-stomp/pom.xml | 2 +- activemq-tooling/pom.xml | 2 +- activemq-unit-tests/pom.xml | 2 +- activemq-web-console/pom.xml | 2 +- activemq-web-demo/pom.xml | 2 +- activemq-web/pom.xml | 2 +- assembly/pom.xml | 2 +- 35 files changed, 35 insertions(+), 35 deletions(-) diff --git a/activemq-all/pom.xml b/activemq-all/pom.xml index 9dad804a189..fa111f42d68 100644 --- a/activemq-all/pom.xml +++ b/activemq-all/pom.xml @@ -14,7 +14,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-all diff --git a/activemq-amqp/pom.xml b/activemq-amqp/pom.xml index a2d8d043bc4..82f8299094d 100644 --- a/activemq-amqp/pom.xml +++ b/activemq-amqp/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-amqp diff --git a/activemq-blueprint/pom.xml b/activemq-blueprint/pom.xml index 7be03fed022..3d0939e6c46 100644 --- a/activemq-blueprint/pom.xml +++ b/activemq-blueprint/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-blueprint diff --git a/activemq-broker/pom.xml b/activemq-broker/pom.xml index 7b8ca1754bc..5d65a728031 100644 --- a/activemq-broker/pom.xml +++ b/activemq-broker/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-broker diff --git a/activemq-cf/pom.xml b/activemq-cf/pom.xml index 54a4402c687..d9ab3353a6b 100644 --- a/activemq-cf/pom.xml +++ b/activemq-cf/pom.xml @@ -24,7 +24,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-cf diff --git a/activemq-client-jakarta/pom.xml b/activemq-client-jakarta/pom.xml index 133dc7d3575..50c7872da1c 100644 --- a/activemq-client-jakarta/pom.xml +++ b/activemq-client-jakarta/pom.xml @@ -20,7 +20,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-client-jakarta bundle diff --git a/activemq-client/pom.xml b/activemq-client/pom.xml index 6b10e9d0388..f16060d4f41 100644 --- a/activemq-client/pom.xml +++ b/activemq-client/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-client diff --git a/activemq-console/pom.xml b/activemq-console/pom.xml index 7c85874ed69..3c4ebf431b0 100644 --- a/activemq-console/pom.xml +++ b/activemq-console/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-console diff --git a/activemq-http/pom.xml b/activemq-http/pom.xml index 753a7534dc8..ea61afc49bd 100644 --- a/activemq-http/pom.xml +++ b/activemq-http/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-http diff --git a/activemq-jaas/pom.xml b/activemq-jaas/pom.xml index 496d5596d53..d0ff96c0288 100644 --- a/activemq-jaas/pom.xml +++ b/activemq-jaas/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-jaas diff --git a/activemq-jdbc-store/pom.xml b/activemq-jdbc-store/pom.xml index 6025a614a69..3109d288d48 100644 --- a/activemq-jdbc-store/pom.xml +++ b/activemq-jdbc-store/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-jdbc-store diff --git a/activemq-jms-pool/pom.xml b/activemq-jms-pool/pom.xml index 26fab6840c0..7778769c127 100644 --- a/activemq-jms-pool/pom.xml +++ b/activemq-jms-pool/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-jms-pool diff --git a/activemq-kahadb-store/pom.xml b/activemq-kahadb-store/pom.xml index dea4932b2c1..c32a37b5327 100644 --- a/activemq-kahadb-store/pom.xml +++ b/activemq-kahadb-store/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-kahadb-store diff --git a/activemq-karaf-itest/pom.xml b/activemq-karaf-itest/pom.xml index 170b67f3a76..fb9371bf027 100644 --- a/activemq-karaf-itest/pom.xml +++ b/activemq-karaf-itest/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-karaf-itest diff --git a/activemq-karaf/pom.xml b/activemq-karaf/pom.xml index 4be3bc62adf..6fb9408be5b 100644 --- a/activemq-karaf/pom.xml +++ b/activemq-karaf/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-karaf diff --git a/activemq-log4j-appender/pom.xml b/activemq-log4j-appender/pom.xml index ae6be079f8a..8617d74a677 100644 --- a/activemq-log4j-appender/pom.xml +++ b/activemq-log4j-appender/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-log4j-appender diff --git a/activemq-mqtt/pom.xml b/activemq-mqtt/pom.xml index d0bb05d4cdb..fbf677261b7 100644 --- a/activemq-mqtt/pom.xml +++ b/activemq-mqtt/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-mqtt diff --git a/activemq-openwire-generator/pom.xml b/activemq-openwire-generator/pom.xml index 4e47e883f3f..c58cd0c4fb1 100644 --- a/activemq-openwire-generator/pom.xml +++ b/activemq-openwire-generator/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-openwire-generator diff --git a/activemq-openwire-legacy/pom.xml b/activemq-openwire-legacy/pom.xml index 5023210cca0..5ebd7a941a5 100644 --- a/activemq-openwire-legacy/pom.xml +++ b/activemq-openwire-legacy/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-openwire-legacy diff --git a/activemq-osgi/pom.xml b/activemq-osgi/pom.xml index 862b72dc60a..2c502789aa8 100644 --- a/activemq-osgi/pom.xml +++ b/activemq-osgi/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-osgi diff --git a/activemq-partition/pom.xml b/activemq-partition/pom.xml index 126fa07185c..6cb10713e74 100644 --- a/activemq-partition/pom.xml +++ b/activemq-partition/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-partition diff --git a/activemq-pool/pom.xml b/activemq-pool/pom.xml index 0508aedb0ea..b220a8bb89d 100644 --- a/activemq-pool/pom.xml +++ b/activemq-pool/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-pool diff --git a/activemq-ra/pom.xml b/activemq-ra/pom.xml index 05966a2fc3e..49ea6105687 100644 --- a/activemq-ra/pom.xml +++ b/activemq-ra/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-ra diff --git a/activemq-rar/pom.xml b/activemq-rar/pom.xml index c31daf4bcfe..82e9e39cd2a 100644 --- a/activemq-rar/pom.xml +++ b/activemq-rar/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-rar diff --git a/activemq-run/pom.xml b/activemq-run/pom.xml index 03ca4549b21..e269b0c280a 100644 --- a/activemq-run/pom.xml +++ b/activemq-run/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-run diff --git a/activemq-runtime-config/pom.xml b/activemq-runtime-config/pom.xml index db28b3267b0..6d907f214af 100644 --- a/activemq-runtime-config/pom.xml +++ b/activemq-runtime-config/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-runtime-config diff --git a/activemq-shiro/pom.xml b/activemq-shiro/pom.xml index 23487fb6c58..c816b488995 100644 --- a/activemq-shiro/pom.xml +++ b/activemq-shiro/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-shiro diff --git a/activemq-spring/pom.xml b/activemq-spring/pom.xml index 77a5dad542d..b2719e0d480 100644 --- a/activemq-spring/pom.xml +++ b/activemq-spring/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-spring diff --git a/activemq-stomp/pom.xml b/activemq-stomp/pom.xml index c1e1385f5d7..62f92632e6c 100644 --- a/activemq-stomp/pom.xml +++ b/activemq-stomp/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-stomp diff --git a/activemq-tooling/pom.xml b/activemq-tooling/pom.xml index e7f170bf851..eb1aef6157a 100644 --- a/activemq-tooling/pom.xml +++ b/activemq-tooling/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT org.apache.activemq.tooling diff --git a/activemq-unit-tests/pom.xml b/activemq-unit-tests/pom.xml index fe6ae2cc360..415759227bf 100644 --- a/activemq-unit-tests/pom.xml +++ b/activemq-unit-tests/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-unit-tests diff --git a/activemq-web-console/pom.xml b/activemq-web-console/pom.xml index 49a32c2dcfd..c1fee269624 100644 --- a/activemq-web-console/pom.xml +++ b/activemq-web-console/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-web-console diff --git a/activemq-web-demo/pom.xml b/activemq-web-demo/pom.xml index 289b484443d..5fe703ade64 100644 --- a/activemq-web-demo/pom.xml +++ b/activemq-web-demo/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-web-demo diff --git a/activemq-web/pom.xml b/activemq-web/pom.xml index c617da89612..326c3f6cb83 100644 --- a/activemq-web/pom.xml +++ b/activemq-web/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-web diff --git a/assembly/pom.xml b/assembly/pom.xml index d0d49b323b2..d1f063d31cf 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT apache-activemq From d63ac83015106d9520126e1b824ee95fc231fc7a Mon Sep 17 00:00:00 2001 From: Jonathan Gallimore Date: Thu, 13 Nov 2025 15:27:43 +0000 Subject: [PATCH 31/34] Set SCM to HEAD for snapshot development --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 685671f56ed..2802b56d0e9 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ org.apache.activemq activemq-parent - 5.19.1 + 5.19.2-TT.1-SNAPSHOT pom ActiveMQ 2005 @@ -248,7 +248,7 @@ scm:git:http://gitbox.apache.org/repos/asf/activemq.git scm:git:https://gitbox.apache.org/repos/asf/activemq.git https://github.com/apache/activemq - activemq-5.19.1 + HEAD From b6f7d317d7c3366bb8c73deedd4a6c1a6ac5ada0 Mon Sep 17 00:00:00 2001 From: Jonathan Gallimore Date: Thu, 13 Nov 2025 15:31:10 +0000 Subject: [PATCH 32/34] Set commons-collections version to 3.2.2-TT.1 in root pom --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2802b56d0e9..ba8849e90f8 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 2.25.4 [2.20,4) 1.11.0 - 3.2.2 + 3.2.2-TT.1 1.4.1 2.13.0 2.20.0 From e5b5395162abb33af4bef694f042961e6fc9fd9b Mon Sep 17 00:00:00 2001 From: Jonathan Gallimore Date: Thu, 13 Nov 2025 15:35:18 +0000 Subject: [PATCH 33/34] Fix AI's mistakes --- activemq-tooling/activemq-junit/pom.xml | 2 +- activemq-tooling/activemq-maven-plugin/pom.xml | 2 +- activemq-tooling/activemq-memtest-maven-plugin/pom.xml | 2 +- activemq-tooling/activemq-perf-maven-plugin/pom.xml | 2 +- pom.xml | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/activemq-tooling/activemq-junit/pom.xml b/activemq-tooling/activemq-junit/pom.xml index fc1a251e4cd..9415ef594b2 100644 --- a/activemq-tooling/activemq-junit/pom.xml +++ b/activemq-tooling/activemq-junit/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-junit diff --git a/activemq-tooling/activemq-maven-plugin/pom.xml b/activemq-tooling/activemq-maven-plugin/pom.xml index 35a7d858c21..0faaaff0152 100644 --- a/activemq-tooling/activemq-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-maven-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-maven-plugin diff --git a/activemq-tooling/activemq-memtest-maven-plugin/pom.xml b/activemq-tooling/activemq-memtest-maven-plugin/pom.xml index e5b364a7372..269a802222a 100644 --- a/activemq-tooling/activemq-memtest-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-memtest-maven-plugin/pom.xml @@ -22,7 +22,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-memtest-maven-plugin diff --git a/activemq-tooling/activemq-perf-maven-plugin/pom.xml b/activemq-tooling/activemq-perf-maven-plugin/pom.xml index 723457a22e0..40b335d2b53 100644 --- a/activemq-tooling/activemq-perf-maven-plugin/pom.xml +++ b/activemq-tooling/activemq-perf-maven-plugin/pom.xml @@ -21,7 +21,7 @@ org.apache.activemq.tooling activemq-tooling - 5.19.1 + 5.19.2-TT.1-SNAPSHOT activemq-perf-maven-plugin diff --git a/pom.xml b/pom.xml index ba8849e90f8..9c881a07dde 100644 --- a/pom.xml +++ b/pom.xml @@ -245,9 +245,9 @@ - scm:git:http://gitbox.apache.org/repos/asf/activemq.git - scm:git:https://gitbox.apache.org/repos/asf/activemq.git - https://github.com/apache/activemq + scm:git:git@github.com:tomitribe/activemq.git + scm:git:git@github.com:tomitribe/activemq.git + git@github.com:tomitribe/activemq-git HEAD From 7fe91a82a77833bac972de5b7021245060c57eca Mon Sep 17 00:00:00 2001 From: Jonathan Gallimore Date: Thu, 13 Nov 2025 15:39:07 +0000 Subject: [PATCH 34/34] Add 5.19.2-TT.1 release notes: merged upstream 5.19.1 changes --- assembly/src/release/RELEASE-NOTES-EAP-5.19.x | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assembly/src/release/RELEASE-NOTES-EAP-5.19.x b/assembly/src/release/RELEASE-NOTES-EAP-5.19.x index 29da78ae41f..3b588bfce55 100644 --- a/assembly/src/release/RELEASE-NOTES-EAP-5.19.x +++ b/assembly/src/release/RELEASE-NOTES-EAP-5.19.x @@ -1,5 +1,8 @@ Apache ActiveMQ 5.19.x-TT.x +Changes in ActiveMQ EAP 5.19.2-TT.1 +- Merged upstream Apache ActiveMQ 5.19.1 release changes (broker/store fixes, web console updates, and test improvements) + Changes in ActiveMQ EAP 5.19.1-TT.4 - CVE-2025-41249 Spring Framework Annotation Detection Vulnerability