You are viewing a plain text version of this content. The canonical link for it is here.
Posted to gitbox@activemq.apache.org by "pradeep85841 (via GitHub)" <gi...@apache.org> on 2026/02/08 15:04:53 UTC

[PR] VMTransport: Defensive copy of messages to prevent mutation [activemq]

pradeep85841 opened a new pull request, #1659:
URL: https://github.com/apache/activemq/pull/1659

   Updated VMTransport.java doDispatch() to create a defensive copy of ActiveMQMessage to prevent shared message mutation.
   
   Verified locally with a helper: original message body remains unchanged and copy is correctly dispatched.
   
   No new files added; only VMTransport.java modified.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jbonofre (via GitHub)" <gi...@apache.org>.
jbonofre commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3885933437

   I think @cshannon has a very good point. The VM purpose is to "bypass" the marshaling (direct JVM communication). It was my first question about the use case. If the issue is about multi-threading/consumer, it's worth to investigate there (and not change the intentional behavior).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jbonofre (via GitHub)" <gi...@apache.org>.
jbonofre commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3883639626

   This is an interesting PR because it's a good way to have the VM transport behavior close to another "remote" transport connector.
   I will take a deeper look, but very interesting, thanks @pradeep85841 !


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "tabish121 (via GitHub)" <gi...@apache.org>.
tabish121 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3910213938

   > @tabish121 - I know you worked on some of the transport stuff back in the day, do you have any thoughts on this issue?
   
   The ActiveMQ Openwire message commands are not written with concurrent access in mind so if that is going on then you will definitely run into trouble.  There are various options in the broker and client that might come into play here and trigger this if messages are being sent via VM Transports and are being accessed by more than one thread. The safest course in any client code that operates on a message outside the thread it was received on it to copy it as the copy is a none mutating call that should allow another thread to perform get / set operations safely.  
   
   The plain JMS consumer reads a message from the VM transport bit is safe because every consumer gets its own copy of the message sourced from the version that the VM transport was handed. Now if your consumer code takes that copy of the message and send it off into more than one thread for say logging and also a send you could see this as something concurrently reading the message while another thread is trying to put it back through a producer could see the state where the content and the text (or Map if using a MapMessage etc) are both null or they might not as none of those fields is volatile.  
   
   TLDR: When in doubt copy first. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "mattrpav (via GitHub)" <gi...@apache.org>.
mattrpav commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3881962164

   The consumer-side may perform the copy, no?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jeanouii (via GitHub)" <gi...@apache.org>.
jeanouii commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3883646921

   @pradeep85841 That's a very good catch. And I think it makes a lot of sense to have this to make sure vm transport behaves the same as any remote protocol. 
   In EJB world, we have also @Local and @Remote interfaces. Some app servers, like Apache TomEE have the same kind of optimization. If you call a @Remote interface within the same JVM, the server will mimic the serialization / deserialization before actually calling the target bean in the JVM. 
   That makes a lot of sense.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3909774098

   @pradeep85841 - Looking more at the tests and the changes, the synchronization keywords don't do anything (at least not for the test). It looks like what really fixes it is the change inside clearUnmarshalledState.
   
   https://github.com/apache/activemq/pull/1659/changes#diff-6cfa7d25bdb07827afcc060c572b2bed07850282c14893a05dd695fc63bbd5ddR159-R163
   
   Normally if that is called the content should be there and not null, but that check handles in case it is null by making sure we marshal it first.
   
   Can you re-try without any synchronization and just that fix to see if it fixes your environment? If we really are copying on dispatch (and we are) and the copy is done by the broker it just doesn't seem like we should really need to add synchronization as the messages are meant to be used by one thread at a time and copied if multiple threads are touching them.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3881943095

   Thanks for the suggestion.
   
   The issue in AMQ-9855 happens inside the vm:// transport dispatch path. The same ActiveMQMessage instance is delivered to multiple consumers in-memory. With tcp://, marshal/unmarshal creates separate instances, so this does not occur.
   
   So this is not about client code mutating a received JMS message, it’s about vm:// sharing the same object reference across consumers, which makes its behaviour inconsistent with other transports.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3888632989

   I’ve cleaned up the branch and rebased against main. The PR now only contains the intended changes:
   
   VMTransport.java (defensive copy in oneway() for AMQ-9855)
   
   The corresponding unit test


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on code in PR #1659:
URL: https://github.com/apache/activemq/pull/1659#discussion_r2802090946


##########
activemq-client/src/main/java/org/apache/activemq/command/ActiveMQTextMessage.java:
##########
@@ -54,9 +54,11 @@ public Message copy() {
         return copy;
     }
 
-    private void copy(ActiveMQTextMessage copy) {
-        super.copy(copy);
-        copy.text = text;
+    protected void copy(ActiveMQTextMessage copy) {

Review Comment:
   @jeanouii That's a fair point regarding the visibility. I initially changed it to protected to follow the pattern of the parent ActiveMQMessage copy logic, but I agree that since there are no current subclasses of ActiveMQTextMessage, it's better to keep it encapsulated.
   
   I'm going to wait for @cshannon's final feedback before pushing any more updates. This way, I can consolidate all requested changes—including reverting this visibility to private—into a single final push to keep the history clean. Thanks for catching that!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] WIP: AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] closed pull request #1659: WIP: AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation
URL: https://github.com/apache/activemq/pull/1659


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3882077041

   The consumer side could defensively copy the message, but that would not solve the root cause. By the time the consumer performs the copy, the same message instance may already have been observed or mutated by another consumer. In that case, the copy would simply duplicate an already-modified state.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jeanouii (via GitHub)" <gi...@apache.org>.
jeanouii commented on code in PR #1659:
URL: https://github.com/apache/activemq/pull/1659#discussion_r2794236552


##########
activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransport.java:
##########
@@ -20,13 +20,16 @@
 import java.io.InterruptedIOException;
 import java.net.URI;
 import java.security.cert.X509Certificate;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.sql.PreparedStatement;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;

Review Comment:
   This is discouraged in the project, even though it's not a blocker



##########
activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransport.java:
##########
@@ -94,6 +97,30 @@ public void oneway(Object command) throws IOException {
                 throw new TransportDisposedIOException("Peer (" + peer.toString() + ") disposed.");
             }
 
+            // Deep copy the message if it is a MessageDispatch
+            Object toSend = command;
+            if (command instanceof MessageDispatch) {
+                MessageDispatch original = (MessageDispatch) command;
+                try {
+                    WireFormat wf = new OpenWireFormat();
+                    ByteSequence data = wf.marshal(original);
+                    toSend = wf.unmarshal(data); // deep copy
+                } catch (IOException e) {
+                    LOG.warn("Failed to deep copy MessageDispatch, sending original", e);
+                    toSend = command;
+                }
+            } else if (command instanceof ActiveMQMessage) {
+                ActiveMQMessage original = (ActiveMQMessage) command;
+                try {
+                    WireFormat wf = new OpenWireFormat();
+                    ByteSequence data = wf.marshal(original);
+                    toSend = (ActiveMQMessage) wf.unmarshal(data);
+                } catch (IOException e) {
+                    LOG.warn("Failed to marshal/unmarshal ActiveMQMessage, sending original", e);

Review Comment:
   Same



##########
activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransport.java:
##########
@@ -124,6 +151,10 @@ public void oneway(Object command) throws IOException {
                     return;
                 }
             }
+
+            // Dispatch to listener
+            dispatch(peer, peer.messageQueue, toSend);

Review Comment:
   Should we have a return after this one to avoid the second dispatch bellow?



##########
activemq-unit-tests/src/test/java/org/apache/activemq/VmTransportBrokerRestartTest.java:
##########


Review Comment:
   This PR should be rebased against apache/main to avoid this class to be here



##########
activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransport.java:
##########
@@ -94,6 +97,30 @@ public void oneway(Object command) throws IOException {
                 throw new TransportDisposedIOException("Peer (" + peer.toString() + ") disposed.");
             }
 
+            // Deep copy the message if it is a MessageDispatch
+            Object toSend = command;
+            if (command instanceof MessageDispatch) {
+                MessageDispatch original = (MessageDispatch) command;
+                try {
+                    WireFormat wf = new OpenWireFormat();
+                    ByteSequence data = wf.marshal(original);
+                    toSend = wf.unmarshal(data); // deep copy
+                } catch (IOException e) {
+                    LOG.warn("Failed to deep copy MessageDispatch, sending original", e);
+                    toSend = command;

Review Comment:
   I'm wondering if it's desire or not to be honest. The goal to me of this PR (and it's great) is to have VM transport behave the same as other remote transport. Benefit being that others in the same JVM can't mutate the message. Great!
   
   Now if we can't serialize/de-serialize to create a deep copy and we still send the original, we might introduce a case where VM does work when remote does not. So I'm tempted to just fail here. What do you think?



##########
activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransport.java:
##########
@@ -68,6 +72,9 @@ public class VMTransport implements Transport, Task {
 
     private volatile int receiveCounter;
 
+    private final List<TransportListener> listeners = new CopyOnWriteArrayList<>();
+    private final ExecutorService executor = Executors.newCachedThreadPool();

Review Comment:
   I'm probably blind or my search/replace does not work properly. Where are the 2 fields used so far?



##########
activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransport.java:
##########
@@ -20,13 +20,16 @@
 import java.io.InterruptedIOException;
 import java.net.URI;
 import java.security.cert.X509Certificate;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.sql.PreparedStatement;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.activemq.command.ShutdownInfo;
+import jakarta.jms.JMSException;
+import org.apache.activemq.command.*;

Review Comment:
   Same here FYI



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jeanouii (via GitHub)" <gi...@apache.org>.
jeanouii commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3885955879

   > In the past I have also noticed some odd behavior with the VM transport when using clients (producers/consumers) that could be a thread safety bug (it is used for bridges though and seems fine there so that is interesting).
   > 
   > However, this proposed fix in this PR doesn't make sense to me at all because the entire point is to NOT need to marshal the message. If you want to marshal the object then you should just use the TCP transport as you lose the benefits.
   > 
   > The VM transport is supposed to already do a deep copy during dispatch to prevent issues with multi-threading. It's possible there is an issue with that, so that's probably where to look for a fix vs adding in marshaling.
   > 
   > Here is where the copying is doing on dispatch in the connection:
   > 
   > https://github.com/apache/activemq/blob/a8fa4b09b54eaca06d106e754796798bd88e21e9/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java#L1906-L1929
   > 
   > I would start there as to investigating if that is the issue
   
   Thanks for the background @cshannon 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] VMTransport: Defensive copy of messages to prevent mutation [activemq]

Posted by "jbonofre (via GitHub)" <gi...@apache.org>.
jbonofre commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3867625211

   OK, let me take a new look. I'm adding the Jira id in the title of the PR to avoid confusion. Thanks.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3917575694

   > The root cause is a state-transition gap. Currently, clearUnmarshalledState() assumes the content bytes are already there and simply wipes the 'live' data (String, Map, etc.). If a background task (like a memory sweep) hits this method before marshaling is finished, the message becomes empty—leading to the data loss I caught.
   
   Where you seeing a background task actually call that method? I've only found it called in [Topic](https://github.com/apache/activemq/blob/e2b8de9e1d5df2fa47d10d94c18d50b05409f74d/activemq-broker/src/main/java/org/apache/activemq/broker/region/Topic.java#L815-L819) and [Queue](https://github.com/apache/activemq/blob/e2b8de9e1d5df2fa47d10d94c18d50b05409f74d/activemq-broker/src/main/java/org/apache/activemq/broker/region/Queue.java#L911-L916) during message send if reduceMemoryFootprint is true and in each case it verifies the data is already marshaled before clearing.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3887076258

   I’ve applied all requested changes: cleaned up imports and unused fields in VMTransport.java, ensured proper deep-copy handling in oneway() so VM transport behaves like other remote transports, added a return to avoid duplicate dispatch, and updated the unit tests to focus on the AMQ-9855 scenario. All changes are tested and ready for review.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3909723905

   > I'm taking a look at this a bit more as something still doesn't add up. The fix here works but I'm not sure why it would be necessary.
   > 
   > I started playing with the unit test and when I debug it I see the message is being copied on dispatch, but all the consumers are receiving the exact same message reference so I am going to see if I can track down why.
   
   Oops neve rmind, I am getting different references (looking at the wrong thing) but I am still checking more into this to see if we can better pinpoint the issue vs just using synchronized. We can add it but it would be nice to know where exactly the issue is with multiple threads touching the message at the same time.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3885519761

   Thanks for the feedback.
   
   I’ve removed the Dockerfile and entrypoint.sh changes from this PR since they belong to the separate non-root Docker work. This PR is now strictly focused on the VM transport change for AMQ-9855.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3903816329

   I have updated to address the feedback from @cshannon and @jeanouii. This revision ensures total thread safety for the VM Transport "defensive copy" logic while adhering to the project's coding standards and memory-management goals.
   
   Key Changes:
   
   Full Synchronization: Per the suggestion to "go all the way," I have applied the synchronized keyword to all state-dependent methods in ActiveMQTextMessage. This includes getText(), setText(), getSize(), clearBody(), and clearUnMarshalledState().
   
   Data Integrity: In clearUnMarshalledState(), I ensured that storeContent() is called (if bytes are missing) before the local text reference is nulled. This prevents the "double-null" scenario where a message could lose its content entirely during a race.
   
   Encapsulation: Reverted the copy(ActiveMQTextMessage) helper method to private visibility.
   
   Stress Test Improvements: * Replaced printStackTrace with SLF4J logging.
   
   Added proper braces {} to all conditional blocks.
   
   Refined the concurrency logic using an ExecutorService and Future validation to simulate high-pressure dispatching across multiple durable and non-durable subscribers.
   
   Optimized the test payload and iteration count to verify the fix under high contention without triggering OutOfMemoryError on restricted heap environments.
   
   Verification: The updated ActiveMQTextMessageStressTest confirms that even when multiple consumers hammer getText() and clearUnMarshalledState() concurrently on messages dispatched via the VM transport, the data remains intact and instances remain independent.
   
   The fix has been verified in original environment and the test suite is now passing with a clean exit.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3923949758

   > To clarify the background task involvement: in a standard serial flow, the Destination handles the guards correctly. However, in my ActiveMQTextMessageStressTest, the race occurs during high-concurrency scenarios where multiple threads interact with the message command.
   > 
   > The failure specifically occurs here: Caused by: java.lang.AssertionError: Text should never be null during stress at line 138
   > 
   > This trace confirms that even though the message was produced with text, the unmarshalled state was cleared out from under the consumer thread before it could be read.
   > 
   > Because clearUnmarshalledState() is public, it can be invoked by internal broker components (like Advisory dispatchers or NIO transport threads) to reduce memory footprint. Since the current implementation doesn't check if content is actually populated before nulling the text, it creates this 'double-null' state.
   > 
   > Hardening the command itself makes it 'safe by default,' protecting data integrity regardless of which internal broker component calls the cleanup
   
   So taking a step back, at this point I'm not sure there's a real problem we need to solve here, or if there is I haven't seen the evidence yet for an actual broker problem to fix because the messages should be copied on dispatch.
   
   A few things to point out:
   
   First, the goal here should not be to make changes to the code to simply make the unit test pass. The unit test is an artificial recreation of an error by manually calling that method outside of normal operation in a multi threaded environment.  As @tabish121 pointed out, those messages were **never** intended to be used by multiple threads so there will be race conditions if 2 threads are operating on the message. That alone makes the test invalid as the messages are inherently not thread safe so a stress test will break it.
   
   Second, simply making this change inside of clearUnmarshalledState() to serialize and marshal the data if not serialized does not make the messages thread safe. There is still a race condition and using that method is only safe from a single threaded environment. You could still get into trouble if calling the method from multiple threads so it doesn't really solve the issue if there are 2 threads touching the message by mistake.
   
   Third, yes the method is public, so in theory someone could certainly write code that would invoke the method and clear the unmarshaled state without marshaling the data first.  However, if a user is going to write code to do that a user is also capable of checking the data has been marshaled before calling that method just like the broker does. If anything, it may make more sense to do a state check and throw an exception inside of clearUnmarshalledState() if the marshaled data is missing as if someone is calling that method they likely are going to be expecting the data to already be marshaled and transparently marshaling would just be hiding an error.
   
   Lastly, this issue and PR were originally opened up because of receiving null message bodies in a real environment but I have yet to see evidence or a demonstration of how this is possible to happen because the messages should be copied and be unique for each consumer. I'm not saying it's impossible to happen but so far receiving null bodies has only been demonstrated through a unit test that is not really a valid test because it creates a race condition scenario that should not be possible in a normal broker operation because those messages should always be copied. 
   
   Originally I was ok adding synchronization because I was thinking there was a spot in the broker where multiple threads might be interacting with the same copy in the VM transport. However, so far that doesn't appear to be the case or at least we haven't found where that is yet as we should be copying the message on dispatch.  If there is a bug to fix that fix would be to locate the spot where multiple threads are interacting with the same copy vs their own copy and fix that. Any client code that is calling clearUnmarshalledState() should make sure to do so on its own copy of the message and should verify it's safe to call that method (ie check it's marshaled first)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jeanouii (via GitHub)" <gi...@apache.org>.
jeanouii commented on code in PR #1659:
URL: https://github.com/apache/activemq/pull/1659#discussion_r2801455608


##########
activemq-client/src/main/java/org/apache/activemq/command/ActiveMQTextMessage.java:
##########
@@ -54,9 +54,11 @@ public Message copy() {
         return copy;
     }
 
-    private void copy(ActiveMQTextMessage copy) {
-        super.copy(copy);
-        copy.text = text;
+    protected void copy(ActiveMQTextMessage copy) {

Review Comment:
   It's a minor detail and question, but Chris would provide a way better feedback for you :-)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3887901525

   It looks like a bunch of binary files were committed to this


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jbonofre (via GitHub)" <gi...@apache.org>.
jbonofre commented on code in PR #1659:
URL: https://github.com/apache/activemq/pull/1659#discussion_r2792596044


##########
assembly/src/docker/entrypoint.sh:
##########
@@ -18,6 +18,8 @@
 # limitations under the License.
 ################################################################################
 
+set -e

Review Comment:
   Same comment here as in `Dockerfile`.



##########
assembly/src/docker/Dockerfile:
##########
@@ -32,14 +32,25 @@ ENV ACTIVEMQ_OPTS $ACTIVEMQ_OPTS_MEMORY -Djava.util.logging.config.file=logging.
 # activemq_dist can point to a directory or a tarball on the local system
 ARG activemq_dist=NOT_SET
 
-COPY entrypoint.sh /usr/local/bin/entrypoint.sh
+RUN groupadd -r activemq && useradd -r -g activemq -m activemq

Review Comment:
   This is unrelated to this change/PR.
   We have another PR for non-root Docker run.
   
   Can you please remove `Dockerfile` and `entrypoint.sh` from this PR ?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "mattrpav (via GitHub)" <gi...@apache.org>.
mattrpav commented on code in PR #1659:
URL: https://github.com/apache/activemq/pull/1659#discussion_r2795059309


##########
activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransport.java:
##########
@@ -94,6 +97,30 @@ public void oneway(Object command) throws IOException {
                 throw new TransportDisposedIOException("Peer (" + peer.toString() + ") disposed.");
             }
 
+            // Deep copy the message if it is a MessageDispatch
+            Object toSend = command;
+            if (command instanceof MessageDispatch) {
+                MessageDispatch original = (MessageDispatch) command;
+                try {
+                    WireFormat wf = new OpenWireFormat();
+                    ByteSequence data = wf.marshal(original);
+                    toSend = wf.unmarshal(data); // deep copy
+                } catch (IOException e) {
+                    LOG.warn("Failed to deep copy MessageDispatch, sending original", e);
+                    toSend = command;

Review Comment:
   command.copy() should suffice here



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on code in PR #1659:
URL: https://github.com/apache/activemq/pull/1659#discussion_r2806195369


##########
activemq-unit-tests/src/test/java/org/apache/activemq/command/ActiveMQTextMessageStressTest.java:
##########
@@ -0,0 +1,176 @@
+/**
+ * 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.command;
+
+import jakarta.jms.Connection;
+import jakarta.jms.MessageConsumer;
+import jakarta.jms.Session;
+import jakarta.jms.Topic;
+import jakarta.jms.MessageProducer;
+import jakarta.jms.TextMessage;
+import jakarta.jms.JMSException;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.broker.BrokerService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertEquals;
+
+public class ActiveMQTextMessageStressTest {
+
+    private BrokerService broker;
+    private Connection connection;
+
+    @Before
+    public void setUp() throws Exception {
+        broker = new BrokerService();
+        broker.setPersistent(false);
+        broker.addConnector("vm://localhost");
+        broker.start();
+
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        connection = cf.createConnection();
+        connection.setClientID("HIGH_CONC_TEST"); // needed for durable subscribers
+        connection.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (connection != null) connection.close();
+        if (broker != null) broker.stop();
+    }
+
+    @Test
+    public void testConcurrentProducersAndConsumers() throws Exception {
+        final int MESSAGE_COUNT = 100;
+        final int PRODUCERS = 5;
+        final int DURABLE_CONSUMERS = 0;
+        final int NON_DURABLE_CONSUMERS = 5;
+
+        // Topic
+        Session tmpSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        Topic topic = tmpSession.createTopic("HIGH_CONC.TOPIC");
+
+        // Consumers
+        List<MessageConsumer> consumers = new ArrayList<>();
+        List<Session> consumerSessions = new ArrayList<>();
+        for (int i = 1; i <= DURABLE_CONSUMERS; i++) {
+            Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            consumers.add(s.createDurableSubscriber(topic, "Durable-" + i));
+            consumerSessions.add(s);
+        }
+        for (int i = 1; i <= NON_DURABLE_CONSUMERS; i++) {
+            Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            consumers.add(s.createConsumer(topic));
+            consumerSessions.add(s);
+        }
+
+        ExecutorService executor = Executors.newFixedThreadPool(PRODUCERS + consumers.size());
+
+        // Produce messages concurrently
+        CountDownLatch producerLatch = new CountDownLatch(PRODUCERS);
+        for (int p = 1; p <= PRODUCERS; p++) {
+            final int producerId = p;
+            executor.submit(() -> {
+                try {
+                    Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+                    MessageProducer producer = s.createProducer(topic);
+                    for (int m = 1; m <= MESSAGE_COUNT; m++) {
+                        TextMessage msg = s.createTextMessage("P" + producerId + "-M" + m);
+                        producer.send(msg);
+                    }
+                } catch (JMSException e) {
+                    e.printStackTrace();

Review Comment:
   I wouldn't use print stacktrace here, the exception should be handled better. We should either fail the test or in this case maybe just log it at debug level if we don't care.



##########
activemq-unit-tests/src/test/java/org/apache/activemq/command/ActiveMQTextMessageStressTest.java:
##########
@@ -0,0 +1,176 @@
+/**
+ * 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.command;
+
+import jakarta.jms.Connection;
+import jakarta.jms.MessageConsumer;
+import jakarta.jms.Session;
+import jakarta.jms.Topic;
+import jakarta.jms.MessageProducer;
+import jakarta.jms.TextMessage;
+import jakarta.jms.JMSException;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.broker.BrokerService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertEquals;
+
+public class ActiveMQTextMessageStressTest {
+
+    private BrokerService broker;
+    private Connection connection;
+
+    @Before
+    public void setUp() throws Exception {
+        broker = new BrokerService();
+        broker.setPersistent(false);
+        broker.addConnector("vm://localhost");
+        broker.start();
+
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        connection = cf.createConnection();
+        connection.setClientID("HIGH_CONC_TEST"); // needed for durable subscribers
+        connection.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (connection != null) connection.close();
+        if (broker != null) broker.stop();
+    }
+
+    @Test
+    public void testConcurrentProducersAndConsumers() throws Exception {
+        final int MESSAGE_COUNT = 100;
+        final int PRODUCERS = 5;
+        final int DURABLE_CONSUMERS = 0;
+        final int NON_DURABLE_CONSUMERS = 5;
+
+        // Topic
+        Session tmpSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        Topic topic = tmpSession.createTopic("HIGH_CONC.TOPIC");
+
+        // Consumers
+        List<MessageConsumer> consumers = new ArrayList<>();
+        List<Session> consumerSessions = new ArrayList<>();
+        for (int i = 1; i <= DURABLE_CONSUMERS; i++) {

Review Comment:
   DURABLE_CONSUMERS is always 0, so this should either be changed or removed.



##########
activemq-client/src/main/java/org/apache/activemq/command/ActiveMQTextMessage.java:
##########
@@ -54,9 +54,11 @@ public Message copy() {
         return copy;
     }
 
-    private void copy(ActiveMQTextMessage copy) {
-        super.copy(copy);
-        copy.text = text;
+    protected void copy(ActiveMQTextMessage copy) {

Review Comment:
   I agree, leaving it private here makes sense unless we need to change it (we can always change it later)



##########
activemq-unit-tests/src/test/java/org/apache/activemq/command/ActiveMQTextMessageStressTest.java:
##########
@@ -0,0 +1,176 @@
+/**
+ * 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.command;
+
+import jakarta.jms.Connection;
+import jakarta.jms.MessageConsumer;
+import jakarta.jms.Session;
+import jakarta.jms.Topic;
+import jakarta.jms.MessageProducer;
+import jakarta.jms.TextMessage;
+import jakarta.jms.JMSException;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.broker.BrokerService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertEquals;
+
+public class ActiveMQTextMessageStressTest {
+
+    private BrokerService broker;
+    private Connection connection;
+
+    @Before
+    public void setUp() throws Exception {
+        broker = new BrokerService();
+        broker.setPersistent(false);
+        broker.addConnector("vm://localhost");
+        broker.start();
+
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        connection = cf.createConnection();
+        connection.setClientID("HIGH_CONC_TEST"); // needed for durable subscribers
+        connection.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (connection != null) connection.close();
+        if (broker != null) broker.stop();
+    }
+
+    @Test
+    public void testConcurrentProducersAndConsumers() throws Exception {
+        final int MESSAGE_COUNT = 100;
+        final int PRODUCERS = 5;
+        final int DURABLE_CONSUMERS = 0;
+        final int NON_DURABLE_CONSUMERS = 5;
+
+        // Topic
+        Session tmpSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        Topic topic = tmpSession.createTopic("HIGH_CONC.TOPIC");
+
+        // Consumers
+        List<MessageConsumer> consumers = new ArrayList<>();
+        List<Session> consumerSessions = new ArrayList<>();
+        for (int i = 1; i <= DURABLE_CONSUMERS; i++) {
+            Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            consumers.add(s.createDurableSubscriber(topic, "Durable-" + i));
+            consumerSessions.add(s);
+        }
+        for (int i = 1; i <= NON_DURABLE_CONSUMERS; i++) {
+            Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            consumers.add(s.createConsumer(topic));
+            consumerSessions.add(s);
+        }
+
+        ExecutorService executor = Executors.newFixedThreadPool(PRODUCERS + consumers.size());
+
+        // Produce messages concurrently
+        CountDownLatch producerLatch = new CountDownLatch(PRODUCERS);
+        for (int p = 1; p <= PRODUCERS; p++) {
+            final int producerId = p;
+            executor.submit(() -> {
+                try {
+                    Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+                    MessageProducer producer = s.createProducer(topic);
+                    for (int m = 1; m <= MESSAGE_COUNT; m++) {
+                        TextMessage msg = s.createTextMessage("P" + producerId + "-M" + m);
+                        producer.send(msg);
+                    }
+                } catch (JMSException e) {
+                    e.printStackTrace();
+                } finally {
+                    producerLatch.countDown();
+                }
+            });
+        }
+
+// Consume messages concurrently
+        List<Future<List<TextMessage>>> consumerFutures = new ArrayList<>();
+        for (MessageConsumer consumer : consumers) {
+            consumerFutures.add(executor.submit(() -> {
+                List<TextMessage> received = new ArrayList<>();
+                for (int i = 0; i < MESSAGE_COUNT * PRODUCERS; i++) {
+                    TextMessage msg = (TextMessage) consumer.receive(5000);
+                    assertNotNull("Consumer should receive a message", msg);
+
+                    // loop to increase the chance of hitting the race condition
+                    // while other consumers are doing the same to their copies.
+                    for (int j = 0; j < 50; j++) {
+                        String txt = msg.getText();
+                        assertNotNull("Text should never be null during stress", txt);
+
+                        // It clears the 'text' field and forces the next getText()
+                        ((org.apache.activemq.command.ActiveMQTextMessage)msg).clearUnMarshalledState();
+                    }
+
+                    received.add(msg);
+                }
+                return received;
+            }));
+        }
+
+// Wait for producers and consumers

Review Comment:
   Indentation here is off and needs to be fixed



##########
activemq-unit-tests/src/test/java/org/apache/activemq/command/ActiveMQTextMessageStressTest.java:
##########
@@ -0,0 +1,176 @@
+/**
+ * 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.command;
+
+import jakarta.jms.Connection;
+import jakarta.jms.MessageConsumer;
+import jakarta.jms.Session;
+import jakarta.jms.Topic;
+import jakarta.jms.MessageProducer;
+import jakarta.jms.TextMessage;
+import jakarta.jms.JMSException;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.broker.BrokerService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertEquals;
+
+public class ActiveMQTextMessageStressTest {
+
+    private BrokerService broker;
+    private Connection connection;
+
+    @Before
+    public void setUp() throws Exception {
+        broker = new BrokerService();
+        broker.setPersistent(false);
+        broker.addConnector("vm://localhost");
+        broker.start();
+
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        connection = cf.createConnection();
+        connection.setClientID("HIGH_CONC_TEST"); // needed for durable subscribers
+        connection.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (connection != null) connection.close();

Review Comment:
   Nitpicking but you should use braces even for one line statements.



##########
activemq-unit-tests/src/test/java/org/apache/activemq/command/ActiveMQTextMessageStressTest.java:
##########
@@ -0,0 +1,176 @@
+/**
+ * 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.command;
+
+import jakarta.jms.Connection;
+import jakarta.jms.MessageConsumer;
+import jakarta.jms.Session;
+import jakarta.jms.Topic;
+import jakarta.jms.MessageProducer;
+import jakarta.jms.TextMessage;
+import jakarta.jms.JMSException;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.broker.BrokerService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertEquals;
+
+public class ActiveMQTextMessageStressTest {
+
+    private BrokerService broker;
+    private Connection connection;
+
+    @Before
+    public void setUp() throws Exception {
+        broker = new BrokerService();
+        broker.setPersistent(false);
+        broker.addConnector("vm://localhost");
+        broker.start();
+
+        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
+        connection = cf.createConnection();
+        connection.setClientID("HIGH_CONC_TEST"); // needed for durable subscribers
+        connection.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (connection != null) connection.close();
+        if (broker != null) broker.stop();
+    }
+
+    @Test
+    public void testConcurrentProducersAndConsumers() throws Exception {
+        final int MESSAGE_COUNT = 100;
+        final int PRODUCERS = 5;
+        final int DURABLE_CONSUMERS = 0;
+        final int NON_DURABLE_CONSUMERS = 5;
+
+        // Topic
+        Session tmpSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+        Topic topic = tmpSession.createTopic("HIGH_CONC.TOPIC");
+
+        // Consumers
+        List<MessageConsumer> consumers = new ArrayList<>();
+        List<Session> consumerSessions = new ArrayList<>();
+        for (int i = 1; i <= DURABLE_CONSUMERS; i++) {
+            Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            consumers.add(s.createDurableSubscriber(topic, "Durable-" + i));
+            consumerSessions.add(s);
+        }
+        for (int i = 1; i <= NON_DURABLE_CONSUMERS; i++) {
+            Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            consumers.add(s.createConsumer(topic));
+            consumerSessions.add(s);
+        }
+
+        ExecutorService executor = Executors.newFixedThreadPool(PRODUCERS + consumers.size());
+
+        // Produce messages concurrently
+        CountDownLatch producerLatch = new CountDownLatch(PRODUCERS);
+        for (int p = 1; p <= PRODUCERS; p++) {
+            final int producerId = p;
+            executor.submit(() -> {
+                try {
+                    Session s = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+                    MessageProducer producer = s.createProducer(topic);
+                    for (int m = 1; m <= MESSAGE_COUNT; m++) {
+                        TextMessage msg = s.createTextMessage("P" + producerId + "-M" + m);
+                        producer.send(msg);
+                    }
+                } catch (JMSException e) {
+                    e.printStackTrace();
+                } finally {
+                    producerLatch.countDown();
+                }
+            });
+        }
+
+// Consume messages concurrently
+        List<Future<List<TextMessage>>> consumerFutures = new ArrayList<>();
+        for (MessageConsumer consumer : consumers) {
+            consumerFutures.add(executor.submit(() -> {
+                List<TextMessage> received = new ArrayList<>();
+                for (int i = 0; i < MESSAGE_COUNT * PRODUCERS; i++) {
+                    TextMessage msg = (TextMessage) consumer.receive(5000);
+                    assertNotNull("Consumer should receive a message", msg);
+
+                    // loop to increase the chance of hitting the race condition
+                    // while other consumers are doing the same to their copies.
+                    for (int j = 0; j < 50; j++) {
+                        String txt = msg.getText();
+                        assertNotNull("Text should never be null during stress", txt);
+
+                        // It clears the 'text' field and forces the next getText()
+                        ((org.apache.activemq.command.ActiveMQTextMessage)msg).clearUnMarshalledState();
+                    }
+
+                    received.add(msg);
+                }
+                return received;
+            }));
+        }
+
+// Wait for producers and consumers
+        producerLatch.await();
+        List<List<TextMessage>> allConsumed = new ArrayList<>();
+        for (Future<List<TextMessage>> f : consumerFutures) {
+            allConsumed.add(f.get(30, TimeUnit.SECONDS));
+        }
+
+// VALIDATION LOGIC

Review Comment:
   Indentation here is off and needs to be fixed



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3910230169

   Thanks for the insights, @tabish121. That context on the VM transport and the non-thread-safe nature of OpenWire commands makes a lot of sense.
   
   It sounds like while 'Copy First' is the required pattern for users, the current clearUnmarshalledState() implementation has a 'window of vulnerability' where it can accidentally wipe data if the timing is off (as seen in my unit test).
   
   By ensuring storeContent() is called before nulling the text field, we’re essentially making the message command 'fail-safe' against that specific state where both fields end up null. I've removed the synchronized keywords to avoid adding overhead to the standard single-threaded path, but kept this logic fix to harden the message against the concurrent scenarios @tabish121 mentioned.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "mattrpav (via GitHub)" <gi...@apache.org>.
mattrpav commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3910692678

   @pradeep85841 do you have advisories enabled, or the setting to include full message content in the advisory? Any other broker plugins? I'm wondering if the access race condition is on the broker side via plugin or other task (like advisory publishing on dispatch).
   
   Please post your full activemq.xml


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3910983956

   i think adding the storeContent() part to the clearUnmarshalledState() is ok but I'm curious where/why we need to do it. The only spot I could find it being called is when reduceMemoryFootprint is true and we are validating that it's marshaled before calling it.
   
   I just want to make sure we fully understand the root cause of your issue so that we can validate the fix is correct.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3912446379

   > @pradeep85841 do you have advisories enabled, or the setting to include full message content in the advisory? Any other broker plugins? I'm wondering if the access race condition is on the broker side via plugin or other task (like advisory publishing on dispatch).
   > 
   > Please post your full activemq.xml
   
   Hi @mattrpav, I’ve attached my activemq.xml below. It’s a pretty standard configuration using nio transport and persistent="false". There are no custom plugins, though advisorySupport is enabled by default. It seems the race condition is likely triggered by the core broker (perhaps the Advisory system or a background memory sweep) interacting with the message during the dispatch loop.
   
   activemq.xml


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3879735789

   Applied a defensive copy in VMTransport.java to ensure each consumer on vm:// topics receives an independent message.
   
   Added VMTransportDefensiveCopyTest.java to reproduce the issue and verify the fix.
   
   Confirms that message bodies are not shared/mutated between consumers.
   
   Addresses the intermittent null/empty body problem reported in AMQ-9855.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3867683842

   Sounds good, thanks.
   Happy to make this configurable  and I’ll wait for next steps.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jbonofre (via GitHub)" <gi...@apache.org>.
jbonofre commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3883643029

   @mattrpav I would appreciate that you don't remove me from the reviewer when I requested it. Thanks.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3894219649

   > Looks good. Synchronized is good enough and it's a perf killer only on vm transport. Other transports would marshall/unmarshall and would use a different instance. On vm, it creates contentions but that's way better than messing up with the data. Using RentrantReadWriteLock would not improve the things because all methods basically mutate under the cover.
   
   Agreed, synchronized here is fine to use and as you pointed out we don't gain anything from a ReadWrite lock as we need a write lock for basically all methods. I wasn't around for the original implementation of classes but it looked like the goal was to avoid synchronization as messages were meant to be copied if used across threads. Unfortunately that pattern breaks with the VM transport as we see due to the nature of dispatching to multiple consumers. I don't think we see this issue with network bridges because there is only one consumer for the VM transports in bridges (the broker itself).
   
   The performance impact for single threaded applications using synchronized that have no contention is pretty negligible and even with contention as we can see correctness is more important. I'd rather the application take a couple extra ms to dispatch all the copies of the message than to receive a null body.
   
   I was looking more at [AMQ-5857](https://issues.apache.org/jira/browse/AMQ-5857) and the discussion to try and remember why we didn't add synchronized back then when trying to clear memory to avoid storing the data twice and OOM errors and i assume we just didn't have good enough tests to show the real issue. Plus, it was like 10 years ago so I was now to ActiveMQ :)
   
   I haven't had time to fully review this yet but will either tomorrow or Monday and will post more feedback.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3900042408

   The only other thing that I keep thinking is if we are going to go ahead and add synchronization here it would be nice to better understand why the issue happens at all. Just thinking about it more there really shouldn't be anything else touching the message when it is copied during dispatch and then each consumer should have its own copy so it makes me wonder if there's something else weird about camel or something that is triggering multiple threads to touch the message at the same time. Clients also have the ability to disable copying during send which could be an issue if set. 
   
   Mostly I just want to make sure that we are solving an issue with the actual broker and not a weird setup that is causing it.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3912506450

   > i think adding the storeContent() part to the clearUnmarshalledState() is ok but I'm curious where/why we need to do it. The only spot I could find it being called is when reduceMemoryFootprint is true and we are validating that it's marshaled before calling it.
   > 
   > I just want to make sure we fully understand the root cause of your issue so that we can validate the fix is correct.
   
   Hi @cshannon,
   
   Great question. After digging into the analysis from @tabish121 and my test results, here is the 'why':
   
   The root cause is a state-transition gap. Currently, clearUnmarshalledState() assumes the content bytes are already there and simply wipes the 'live' data (String, Map, etc.). If a background task (like a memory sweep) hits this method before marshaling is finished, the message becomes empty—leading to the data loss I caught.
   
   By adding a quick storeContent() check inside clearUnmarshalledState(), we make the message 'self-healing.' It ensures that the message protects its own data integrity regardless of the timing or threading context.
   
   I’ve confirmed that this logic fix works perfectly without needing synchronized locks. Since this vulnerability exists across MapMessage, ObjectMessage, and StreamMessage as well, I’d like to apply this hardening to all of them for consistency.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] WIP: AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-4285507889

   This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3909816397

   Under normal circumstances the `isReduceMemoryFootprint()` must be true for the destination in order for the `clearUnmarshalledState()` to be called, and at that point in the broker we should have the marshaled version.
   
   So another test would be to disable `reduceMemoryFootprint` (assuming you are using it) and see if your problem goes away.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3910178854

   Removed synchronized keywords: Unit testing confirmed that the issue was not concurrent access to the same object reference, but rather a state-transition gap during memory reduction.
   
   State Safeguard in clearUnMarshalledState(): Added a check to ensure storeContent() is called if the message only exists in text form before the unmarshaled state is cleared. This prevents a race condition where both text and content could briefly be null.
   
   Idempotency: Since storeContent() internally checks for content == null, this approach is safe even if triggered multiple times, avoiding redundant marshaling overhead.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3909662139

   I'm taking a look at this a bit more as something still doesn't add up. The fix here works but I'm not sure why it would be necessary.
   
   I started playing with the unit test and when I debug it I see the message is being copied on dispatch, but all the consumers are receiving the exact same message reference so I am going to see if I can track down why.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3899845982

   @pradeep85841 - One more thing, did you re-run your original setup with the new fixes here? Did you verify that the synchronization actually fixes your issue in a real data flow? The test of course tries to simulate the issue but we are still not exactly sure where the race is happening (but of course thing it has to do with copying or something on dispatch). Anyways it would just be good to verify your original issue is indeed fixed by this (I think it will be)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3918308200

   To clarify the background task involvement: in a standard serial flow, the Destination handles the guards correctly. However, in my ActiveMQTextMessageStressTest, the race occurs during high-concurrency scenarios where multiple threads interact with the message command.
   
   The failure specifically occurs here: Caused by: java.lang.AssertionError: Text should never be null during stress at line 138
   
   This trace confirms that even though the message was produced with text, the unmarshalled state was cleared out from under the consumer thread before it could be read.
   
   Because clearUnmarshalledState() is public, it can be invoked by internal broker components (like Advisory dispatchers or NIO transport threads) to reduce memory footprint. Since the current implementation doesn't check if content is actually populated before nulling the text, it creates this 'double-null' state.
   
   Hardening the command itself makes it 'safe by default,' protecting data integrity regardless of which internal broker component calls the cleanup


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3891634618

   Thank you for the insight. That makes perfect sense—the issue likely isn't the lack of a copy, but a race condition during the lazy state-toggle within ActiveMQTextMessage when copy() or getText() is called concurrently.
   
   I am moving away from the marshaling approach. I'll test adding synchronization to the state-changing methods in ActiveMQTextMessage to see if that stabilizes the copy() process during VM dispatch. I'll update the PR once I verify this identifies the root cause


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "mattrpav (via GitHub)" <gi...@apache.org>.
mattrpav commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3881242733

   Should this be enabled on the client connection factory? JMS messages are supposed to be immutable. If someone is modifying it post recv, seems like that is on the app-side to ensure the spec is not violated.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3892761848

   I have refactored the fix to address the root cause at the command level rather than the transport level, as suggested.
   
   The Issue: Under high concurrency, ActiveMQTextMessage suffered from a race condition where the internal state "toggle" (moving between the text field and the ByteSequence content) was not atomic. This led to a "double null" state where one thread cleared the unmarshalled text before the other had finished reading the raw content.
   
   The Fix:
   
   Thread Safety: Added synchronized to getText(), setText(), and state-toggling methods in ActiveMQTextMessage.
   
   State Preservation: Updated clearUnMarshalledState() to ensure that the marshalled content is stored before the unmarshalled state is cleared.
   
   Performance: This approach preserves the efficiency of the VM Transport by avoiding a full transport-level marshal, while ensuring data integrity.
   
   Validation: Added ActiveMQTextMessageStressTest.java, which reproduces the null body issue and confirms the fix under high thread contention.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "jeanouii (via GitHub)" <gi...@apache.org>.
jeanouii commented on code in PR #1659:
URL: https://github.com/apache/activemq/pull/1659#discussion_r2801444138


##########
activemq-client/src/main/java/org/apache/activemq/command/ActiveMQTextMessage.java:
##########
@@ -54,9 +54,11 @@ public Message copy() {
         return copy;
     }
 
-    private void copy(ActiveMQTextMessage copy) {
-        super.copy(copy);
-        copy.text = text;
+    protected void copy(ActiveMQTextMessage copy) {

Review Comment:
   The synchronized bellow on this is accurate because the other synchronized methods are also on this. All good
   Wondering why the internal copy(ActiveMQTexteMessage) moved from private to protected?
   I don't see subclasses or I missed it, so no reason to expose our internals
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "mattrpav (via GitHub)" <gi...@apache.org>.
mattrpav commented on code in PR #1659:
URL: https://github.com/apache/activemq/pull/1659#discussion_r2795059309


##########
activemq-broker/src/main/java/org/apache/activemq/transport/vm/VMTransport.java:
##########
@@ -94,6 +97,30 @@ public void oneway(Object command) throws IOException {
                 throw new TransportDisposedIOException("Peer (" + peer.toString() + ") disposed.");
             }
 
+            // Deep copy the message if it is a MessageDispatch
+            Object toSend = command;
+            if (command instanceof MessageDispatch) {
+                MessageDispatch original = (MessageDispatch) command;
+                try {
+                    WireFormat wf = new OpenWireFormat();
+                    ByteSequence data = wf.marshal(original);
+                    toSend = wf.unmarshal(data); // deep copy
+                } catch (IOException e) {
+                    LOG.warn("Failed to deep copy MessageDispatch, sending original", e);
+                    toSend = command;

Review Comment:
   command.copy() should suffice here
   
   edit: Agree, we definitely don't want to instantiate an OpenWireFormat object per-message here. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3891444133

   The latest changes here still do not address this comment I made: https://github.com/apache/activemq/pull/1659#pullrequestreview-3786189384
   
   This is still marshaling which we should not be doing, with the VM transport we should just be using the message copy methods.
   
   BUT, the question is still what is the real issue and why is this happening? As pointed out, **we already copy the message on dispatch to consumers**. 
   
   The test you wrote does fail without your changes but ONLY because you are only comparing the String instances to see if they are the same, which they won't be as you are using OpenWire. However, with calling copy() the String instances are the same as Strings are immutable. If you modified your test to compare instance equality for the TextMessage instances and not just the bodies the test would pass because we copy on dispatch.
   
   My guess is the real issue could be a race condition during the copy or when the body is converted between states. The messages themselves are generally not thread safe which is why we copy on dispatch so each consumer gets their own copy, but some messages can toggle state..they can convert transparently between marshaled body and the in memory usable body (same with message properties). 
   
   @pradeep85841 - Are you only using Text messages?
   
   So for a text message, the message will either store the body as a String or it will store it as a buffer and can switch. My guess is things are breaking during that switch as multiple threads are probably calling some of these methods concurrently:
   https://github.com/apache/activemq/blob/649aa0ae679ec104961408b4a1363ca88d0916bd/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQTextMessage.java#L73-L181
   
   This might apply to other message types as well but this was the most noticeable type because it tries to only store either the String or byte buffer to save space.
   
   Going back a long time ago, I actually made some changes here to try and prevent issues without adding synchronization but it's not perfect: https://issues.apache.org/jira/browse/AMQ-5857
   
   So we just need to figure out the exact issue, if it's the copy() that is being done on dispatch (maybe multiple connections are calling copy at the same time on dispatch) we may just need to add some sort of synchronization there. But we need ot identify the real cause, a good first step might be to just test what happens if you apply the "synchronized" key word on the copy() method or also on the other methods that due mutations like getText(), setText(), storeContent() etc and re-run things to see if the issue is gone to help narrow it down to that.
   
   
   
   
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] VMTransport: Defensive copy of messages to prevent mutation [activemq]

Posted by "mattrpav (via GitHub)" <gi...@apache.org>.
mattrpav commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3867517961

   Have you observed a bug or problem with message mutation?  If so, please provide the scenario and reproducible test case. 
   
   ActiveMQConnectionFactory already has copyMessageOnSend enabled by default.
   
   I need help understanding why (in effect) 2 copies are needed. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] VMTransport: Defensive copy of messages to prevent mutation [activemq]

Posted by "jbonofre (via GitHub)" <gi...@apache.org>.
jbonofre commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3867520246

   @pradeep85841 can you please provide some details here ? Do you have an issue/test case ? It's not obvious to me if it's cosmetic or actual issue.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] VMTransport: Defensive copy of messages to prevent mutation [activemq]

Posted by "pradeep85841 (via GitHub)" <gi...@apache.org>.
pradeep85841 commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3867582565

   This is based on issue with vm:// and topics (AMQ-9855).
   
   With VMTransport the same ActiveMQMessage instance is dispatched to multiple consumers. If one consumer reads or mutates the body (Camel split/processor does this), other consumers can see an empty body. This does not happen over tcp:// because marshal/unmarshal creates a copy.
   
   copyMessageOnSend only applies at the producer and broker boundary. The problem here happens inside the broker during dispatch, so that setting does not help.
   
   The change makes VM transport behave consistently with TCP transport and avoids shared mutable state
   
   Happy to add a test if needed.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "mattrpav (via GitHub)" <gi...@apache.org>.
mattrpav commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3867650547

   This probably needs a config flag that is a corollary to the copyMessageOnSend  
   
   ie. copyMessageOnDispatch 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact



Re: [PR] AMQ-9855: VMTransport, Defensive copy of messages to prevent mutation [activemq]

Posted by "cshannon (via GitHub)" <gi...@apache.org>.
cshannon commented on PR #1659:
URL: https://github.com/apache/activemq/pull/1659#issuecomment-3910073620

   So the message is copied during dispatch inside of ActiveMQConnection. If there are multiple connections then it's possible that the message could be copied more than once at the same time, but the copy doesn't mutate state of the original message so this shouldn't cause the issue. 
   
   So really it comes down to, where (if at all) is the broker touching the message at the same time in 2+ threads that could cause the bug? Or is this just an issue somewhere in the client code etc. The changes in clearUnmarshalledState() to check if the content is null may be worthwhile but in the code we always first check that the content is marshaled first before calling it so not sure that actually helps the real broker. It helps the unit test because that method is called directly.
   
   @tabish121 - I know you worked on some of the transport stuff back in the day, do you have any thoughts on this issue?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: gitbox-unsubscribe@activemq.apache.org
For additional commands, e-mail: gitbox-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact