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

tabish pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/main by this push:
     new d029fd463f ARTEMIS-5139 improve locking on TypedProperties
d029fd463f is described below

commit d029fd463f1f11be2ef8458050fcabf3a353e935
Author: Justin Bertram <[email protected]>
AuthorDate: Mon Feb 3 12:05:28 2025 -0600

    ARTEMIS-5139 improve locking on TypedProperties
    
    Back in 8e40b2d4f4f242271d3dfcda4f9b96d3f94cee1b thread safety was added
    to TypedProperties by synchronizing all relevant methods. This was
    simple and effective but suffers from performance issues in read-heavy
    use-cases.
    
    This commit improves the performance by using a read/write locking
    mechanism so reads can execute concurrently.
    
    No new tests were added since the original commit added tests to verify
    thread safety.
---
 .../artemis/utils/collections/TypedProperties.java | 659 +++++++++++++--------
 .../artemis/core/message/impl/CoreMessage.java     |   5 +-
 2 files changed, 403 insertions(+), 261 deletions(-)

diff --git 
a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/collections/TypedProperties.java
 
b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/collections/TypedProperties.java
index 04937af5c7..e2a367ab36 100644
--- 
a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/collections/TypedProperties.java
+++ 
b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/collections/TypedProperties.java
@@ -25,6 +25,9 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -65,6 +68,7 @@ public class TypedProperties {
    private int size;
 
    private final Predicate<SimpleString> internalPropertyPredicate;
+   private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private boolean internalProperties;
    private final Predicate<SimpleString> amqpPropertyPredicate;
    private boolean amqpProperties;
@@ -84,28 +88,40 @@ public class TypedProperties {
    }
 
    /**
-    *  Return the number of properties
-    * */
-   public synchronized int size() {
-      return properties == null ? 0 : properties.size();
+    * Return the number of properties
+    */
+   public int size() {
+      lock.readLock().lock();
+      try {
+         return properties == null ? 0 : properties.size();
+      } finally {
+         lock.readLock().unlock();
+      }
    }
 
-   public synchronized int getMemoryOffset() {
+   public int getMemoryOffset() {
       // The estimate is basically the encode size + 2 object references for 
each entry in the map
       // Note we don't include the attributes or anything else since they 
already included in the memory estimate
       // of the ServerMessage
-
-      return properties == null ? 0 : size + 2 * DataConstants.SIZE_INT * 
properties.size();
+      lock.readLock().lock();
+      try {
+         return properties == null ? 0 : size + 2 * DataConstants.SIZE_INT * 
properties.size();
+      } finally {
+         lock.readLock().unlock();
+      }
    }
 
    public TypedProperties(final TypedProperties other) {
-      synchronized (other) {
+      other.lock.readLock().lock();
+      try {
          properties = other.properties == null ? null : new 
HashMap<>(other.properties);
          size = other.size;
          internalPropertyPredicate = other.internalPropertyPredicate;
          internalProperties = other.internalProperties;
          amqpPropertyPredicate = other.amqpPropertyPredicate;
          amqpProperties = other.amqpProperties;
+      } finally {
+         other.lock.readLock().unlock();
       }
    }
 
@@ -330,58 +346,89 @@ public class TypedProperties {
       return doRemoveProperty(key);
    }
 
-   public synchronized boolean containsProperty(final SimpleString key) {
-      if (properties == null) {
-         return false;
+   public boolean containsProperty(final SimpleString key) {
+      lock.readLock().lock();
+      try {
+         if (properties == null) {
+            return false;
 
-      } else {
-         return properties.containsKey(key);
+         } else {
+            return properties.containsKey(key);
+         }
+      } finally {
+         lock.readLock().unlock();
       }
    }
-   public synchronized Set<SimpleString> getPropertyNames() {
-      if (properties == null) {
-         return Collections.emptySet();
-      } else {
-         return new HashSet<>(properties.keySet());
+
+   public Set<SimpleString> getPropertyNames() {
+      lock.readLock().lock();
+      try {
+         if (properties == null) {
+            return Collections.emptySet();
+         } else {
+            return new HashSet<>(properties.keySet());
+         }
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
-   public synchronized boolean clearInternalProperties() {
-      return internalProperties && removeInternalProperties();
+   public boolean clearInternalProperties() {
+      lock.writeLock().lock();
+      try {
+         return internalProperties && removeInternalProperties();
+      } finally {
+         lock.writeLock().unlock();
+      }
    }
 
-   public synchronized boolean clearAMQPProperties() {
-      return amqpProperties && removeAMQPProperties();
+   public boolean clearAMQPProperties() {
+      lock.writeLock().lock();
+      try {
+         return amqpProperties && removeAMQPProperties();
+      } finally {
+         lock.writeLock().unlock();
+      }
    }
 
-   private synchronized boolean removeInternalProperties() {
-      if (internalPropertyPredicate == null) {
-         return false;
-      }
-      if (properties == null) {
-         return false;
-      }
-      if (properties.isEmpty()) {
-         return false;
+   private boolean removeInternalProperties() {
+      lock.writeLock().lock();
+      try {
+         if (internalPropertyPredicate == null) {
+            return false;
+         }
+         if (properties == null) {
+            return false;
+         }
+         if (properties.isEmpty()) {
+            return false;
+         }
+         boolean removed = removePredicate(internalPropertyPredicate);
+         internalProperties = false;
+         return removed;
+      } finally {
+         lock.writeLock().unlock();
       }
-      boolean removed = removePredicate(internalPropertyPredicate);
-      internalProperties = false;
-      return removed;
    }
 
-   private synchronized boolean removeAMQPProperties() {
-      if (amqpPropertyPredicate == null) {
-         return false;
-      }
-      if (properties == null) {
-         return false;
-      }
-      if (properties.isEmpty()) {
-         return false;
+   private boolean removeAMQPProperties() {
+      lock.writeLock().lock();
+      try {
+         if (amqpPropertyPredicate == null) {
+            return false;
+         }
+         if (properties == null) {
+            return false;
+         }
+         if (properties.isEmpty()) {
+            return false;
+         }
+         boolean removed = removePredicate(amqpPropertyPredicate);
+         amqpProperties = false;
+         return removed;
+      } finally {
+         lock.writeLock().unlock();
       }
-      boolean removed = removePredicate(amqpPropertyPredicate);
-      amqpProperties = false;
-      return removed;
    }
 
    private boolean removePredicate(Predicate<SimpleString> predicate) {
@@ -402,21 +449,51 @@ public class TypedProperties {
       return removed;
    }
 
-   public synchronized void forEachKey(Consumer<SimpleString> action) {
-      if (properties != null) {
-         properties.keySet().forEach(action::accept);
+   /**
+    * This method is read-only. Do not modify the TypedProperties using the 
Consumer.
+    *
+    * @param action Consumer implementation that should not modify the 
TypedProperties
+    */
+   public void forEachKey(Consumer<SimpleString> action) {
+      lock.readLock().lock();
+      try {
+         if (properties != null) {
+            properties.keySet().forEach(action::accept);
+         }
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
-   public synchronized void forEach(BiConsumer<SimpleString, Object> action) {
-      if (properties != null) {
-         properties.forEach((k, v) -> action.accept(k, v.getValue()));
+   /**
+    * This method is read-only. Do not modify the TypedProperties using the 
BiConsumer.
+    *
+    * @param action BiConsumer implementation that should not modify the 
TypedProperties
+    */
+   public void forEach(BiConsumer<SimpleString, Object> action) {
+      lock.readLock().lock();
+      try {
+         if (properties != null) {
+            properties.forEach((k, v) -> action.accept(k, v.getValue()));
+         }
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
-   private synchronized void forEachInternal(BiConsumer<SimpleString, 
PropertyValue> action) {
-      if (properties != null) {
-         properties.forEach(action::accept);
+   /**
+    * This method is read-only. Do not modify the TypedProperties using the 
BiConsumer.
+    *
+    * @param action BiConsumer implementation that should not modify the 
TypedProperties
+    */
+   private void forEachInternal(BiConsumer<SimpleString, PropertyValue> 
action) {
+      lock.readLock().lock();
+      try {
+         if (properties != null) {
+            properties.forEach(action::accept);
+         }
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
@@ -493,87 +570,91 @@ public class TypedProperties {
       return false;
    }
 
-   public synchronized void decode(final ByteBuf buffer,
-                                   final TypedPropertiesDecoderPools 
keyValuePools) {
-      byte b = buffer.readByte();
-      if (b == DataConstants.NULL) {
-         properties = null;
-         size = 0;
-      } else {
-         int numHeaders = buffer.readInt();
+   public void decode(final ByteBuf buffer, final TypedPropertiesDecoderPools 
keyValuePools) {
+      lock.writeLock().lock();
+      try {
+         byte b = buffer.readByte();
+         if (b == DataConstants.NULL) {
+            properties = null;
+            size = 0;
+         } else {
+            int numHeaders = buffer.readInt();
 
-         //optimize the case of no collisions to avoid any resize (it doubles 
the map size!!!) when load factor is reached
-         properties = new HashMap<>(numHeaders, 1.0f);
-         size = 0;
+            //optimize the case of no collisions to avoid any resize (it 
doubles the map size!!!) when load factor is reached
+            properties = new HashMap<>(numHeaders, 1.0f);
+            size = 0;
 
-         for (int i = 0; i < numHeaders; i++) {
-            final SimpleString key = SimpleString.readSimpleString(buffer, 
keyValuePools == null ? null : keyValuePools.getPropertyKeysPool());
+            for (int i = 0; i < numHeaders; i++) {
+               final SimpleString key = SimpleString.readSimpleString(buffer, 
keyValuePools == null ? null : keyValuePools.getPropertyKeysPool());
 
-            byte type = buffer.readByte();
+               byte type = buffer.readByte();
 
-            PropertyValue val;
+               PropertyValue val;
 
-            switch (type) {
-               case NULL: {
-                  val = NullValue.INSTANCE;
-                  doPutValue(key, val);
-                  break;
-               }
-               case CHAR: {
-                  val = new CharValue(buffer);
-                  doPutValue(key, val);
-                  break;
-               }
-               case BOOLEAN: {
-                  val = BooleanValue.of(buffer.readBoolean());
-                  doPutValue(key, val);
-                  break;
-               }
-               case BYTE: {
-                  val = ByteValue.valueOf(buffer.readByte());
-                  doPutValue(key, val);
-                  break;
-               }
-               case BYTES: {
-                  val = new BytesValue(buffer);
-                  doPutValue(key, val);
-                  break;
-               }
-               case SHORT: {
-                  val = new ShortValue(buffer);
-                  doPutValue(key, val);
-                  break;
-               }
-               case INT: {
-                  val = new IntValue(buffer);
-                  doPutValue(key, val);
-                  break;
-               }
-               case LONG: {
-                  val = new LongValue(buffer);
-                  doPutValue(key, val);
-                  break;
-               }
-               case FLOAT: {
-                  val = new FloatValue(buffer);
-                  doPutValue(key, val);
-                  break;
-               }
-               case DOUBLE: {
-                  val = new DoubleValue(buffer);
-                  doPutValue(key, val);
-                  break;
-               }
-               case STRING: {
-                  val = StringValue.readStringValue(buffer, keyValuePools == 
null ? null : keyValuePools.getPropertyValuesPool());
-                  doPutValue(key, val);
-                  break;
-               }
-               default: {
-                  throw ActiveMQUtilBundle.BUNDLE.invalidType(type);
+               switch (type) {
+                  case NULL: {
+                     val = NullValue.INSTANCE;
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case CHAR: {
+                     val = new CharValue(buffer);
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case BOOLEAN: {
+                     val = BooleanValue.of(buffer.readBoolean());
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case BYTE: {
+                     val = ByteValue.valueOf(buffer.readByte());
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case BYTES: {
+                     val = new BytesValue(buffer);
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case SHORT: {
+                     val = new ShortValue(buffer);
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case INT: {
+                     val = new IntValue(buffer);
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case LONG: {
+                     val = new LongValue(buffer);
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case FLOAT: {
+                     val = new FloatValue(buffer);
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case DOUBLE: {
+                     val = new DoubleValue(buffer);
+                     doPutValue(key, val);
+                     break;
+                  }
+                  case STRING: {
+                     val = StringValue.readStringValue(buffer, keyValuePools 
== null ? null : keyValuePools.getPropertyValuesPool());
+                     doPutValue(key, val);
+                     break;
+                  }
+                  default: {
+                     throw ActiveMQUtilBundle.BUNDLE.invalidType(type);
+                  }
                }
             }
          }
+      } finally {
+         lock.writeLock().unlock();
       }
    }
 
@@ -581,144 +662,179 @@ public class TypedProperties {
       decode(buffer, null);
    }
 
-   public synchronized int encode(final ByteBuf buffer) {
-      final int encodedSize;
-      // it's a trick to not pay the cost of buffer.writeIndex without 
assertions enabled
-      int writerIndex = 0;
-      assert (writerIndex = buffer.writerIndex()) >= 0 : "Always true";
-      if (properties == null || size == 0) {
-         encodedSize = DataConstants.SIZE_BYTE;
-         ensureExactWritable(buffer, encodedSize);
-         buffer.writeByte(DataConstants.NULL);
-      } else {
-         encodedSize = DataConstants.SIZE_BYTE + DataConstants.SIZE_INT + size;
-         ensureExactWritable(buffer, encodedSize);
-         buffer.writeByte(DataConstants.NOT_NULL);
-
-         buffer.writeInt(properties.size());
-
-         //uses internal iteration to allow inlining/loop unrolling
-         properties.forEach((key, value) -> {
-            final byte[] data = key.getData();
-            buffer.writeInt(data.length);
-            buffer.writeBytes(data);
-            value.write(buffer);
-         });
+   public int encode(final ByteBuf buffer) {
+      lock.readLock().lock();
+      try {
+         final int encodedSize;
+         // it's a trick to not pay the cost of buffer.writeIndex without 
assertions enabled
+         int writerIndex = 0;
+         assert (writerIndex = buffer.writerIndex()) >= 0 : "Always true";
+         if (properties == null || size == 0) {
+            encodedSize = DataConstants.SIZE_BYTE;
+            ensureExactWritable(buffer, encodedSize);
+            buffer.writeByte(DataConstants.NULL);
+         } else {
+            encodedSize = DataConstants.SIZE_BYTE + DataConstants.SIZE_INT + 
size;
+            ensureExactWritable(buffer, encodedSize);
+            buffer.writeByte(DataConstants.NOT_NULL);
+
+            buffer.writeInt(properties.size());
+
+            //uses internal iteration to allow inlining/loop unrolling
+            properties.forEach((key, value) -> {
+               final byte[] data = key.getData();
+               buffer.writeInt(data.length);
+               buffer.writeBytes(data);
+               value.write(buffer);
+            });
+         }
+         assert buffer.writerIndex() == (writerIndex + encodedSize) : "Bad 
encode size estimation";
+         return encodedSize;
+      } finally {
+         lock.readLock().unlock();
       }
-      assert buffer.writerIndex() == (writerIndex + encodedSize) : "Bad encode 
size estimation";
-      return encodedSize;
    }
 
-   public synchronized int getEncodeSize() {
-      if (properties == null || size == 0) {
-         return DataConstants.SIZE_BYTE;
-      } else {
-         return DataConstants.SIZE_BYTE + DataConstants.SIZE_INT + size;
+   public int getEncodeSize() {
+      lock.readLock().lock();
+      try {
+         if (properties == null || size == 0) {
+            return DataConstants.SIZE_BYTE;
+         } else {
+            return DataConstants.SIZE_BYTE + DataConstants.SIZE_INT + size;
+         }
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
-   public synchronized void clear() {
-      if (properties != null) {
-         properties.clear();
+   public void clear() {
+      lock.writeLock().lock();
+      try {
+         if (properties != null) {
+            properties.clear();
+         }
+         size = 0;
+      } finally {
+         lock.writeLock().unlock();
       }
-      size = 0;
    }
 
    @Override
-   public synchronized String toString() {
-      StringBuilder sb = new StringBuilder("TypedProperties[");
-
-      if (properties != null) {
-         Iterator<Entry<SimpleString, PropertyValue>> iter = 
properties.entrySet().iterator();
-
-         while (iter.hasNext()) {
-            Entry<SimpleString, PropertyValue> iterItem = iter.next();
-            sb.append(iterItem.getKey() + "=");
-
-            // it seems weird but it's right!!
-            // The first getValue is from the EntrySet
-            // The second is to convert the PropertyValue into the actual value
-            Object theValue = iterItem.getValue().getValue();
-
-            if (theValue == null) {
-               sb.append("NULL-value");
-            } else if (theValue instanceof byte[] bytes) {
-               sb.append("[" + ByteUtil.maxString(ByteUtil.bytesToHex(bytes, 
2), 150) + "]");
-
-               if (iterItem.getKey().toString().startsWith("_AMQ_ROUTE_TO")) {
-                  sb.append(", bytesAsLongs[");
-                  try {
-                     ByteBuffer buff = ByteBuffer.wrap(bytes);
-                     while (buff.hasRemaining()) {
-                        long bindingID = buff.getLong();
-                        sb.append(bindingID);
-                        if (buff.hasRemaining()) {
-                           sb.append(", ");
+   public String toString() {
+      lock.readLock().lock();
+      try {
+         StringBuilder sb = new StringBuilder("TypedProperties[");
+
+         if (properties != null) {
+            Iterator<Entry<SimpleString, PropertyValue>> iter = 
properties.entrySet().iterator();
+
+            while (iter.hasNext()) {
+               Entry<SimpleString, PropertyValue> iterItem = iter.next();
+               sb.append(iterItem.getKey() + "=");
+
+               // it seems weird but it's right!!
+               // The first getValue is from the EntrySet
+               // The second is to convert the PropertyValue into the actual 
value
+               Object theValue = iterItem.getValue().getValue();
+
+               if (theValue == null) {
+                  sb.append("NULL-value");
+               } else if (theValue instanceof byte[] bytes) {
+                  sb.append("[" + 
ByteUtil.maxString(ByteUtil.bytesToHex(bytes, 2), 150) + "]");
+
+                  if 
(iterItem.getKey().toString().startsWith("_AMQ_ROUTE_TO")) {
+                     sb.append(", bytesAsLongs[");
+                     try {
+                        ByteBuffer buff = ByteBuffer.wrap(bytes);
+                        while (buff.hasRemaining()) {
+                           long bindingID = buff.getLong();
+                           sb.append(bindingID);
+                           if (buff.hasRemaining()) {
+                              sb.append(", ");
+                           }
                         }
+                     } catch (Throwable e) {
+                        sb.append("error-converting-longs=" + e.getMessage());
                      }
-                  } catch (Throwable e) {
-                     sb.append("error-converting-longs=" + e.getMessage());
+                     sb.append("]");
                   }
-                  sb.append("]");
+               } else {
+                  sb.append(theValue.toString());
                }
-            } else {
-               sb.append(theValue.toString());
-            }
 
-            if (iter.hasNext()) {
-               sb.append(", ");
+               if (iter.hasNext()) {
+                  sb.append(", ");
+               }
             }
          }
-      }
 
-      return sb.append("]").toString();
+         return sb.append("]").toString();
+      } finally {
+         lock.readLock().unlock();
+      }
    }
 
-   private synchronized void doPutValue(final SimpleString key, final 
PropertyValue value) {
-      if (!internalProperties && internalPropertyPredicate != null && 
internalPropertyPredicate.test(key)) {
-         internalProperties = true;
-      }
+   private void doPutValue(final SimpleString key, final PropertyValue value) {
+      lock.writeLock().lock();
+      try {
+         if (!internalProperties && internalPropertyPredicate != null && 
internalPropertyPredicate.test(key)) {
+            internalProperties = true;
+         }
 
-      if (!amqpProperties && amqpPropertyPredicate != null && 
amqpPropertyPredicate.test(key)) {
-         amqpProperties = true;
-      }
+         if (!amqpProperties && amqpPropertyPredicate != null && 
amqpPropertyPredicate.test(key)) {
+            amqpProperties = true;
+         }
 
-      if (properties == null) {
-         properties = new HashMap<>();
-      }
+         if (properties == null) {
+            properties = new HashMap<>();
+         }
 
-      PropertyValue oldValue = properties.put(key, value);
-      if (oldValue != null) {
-         size += value.encodeSize() - oldValue.encodeSize();
-      } else {
-         size += SimpleString.sizeofString(key) + value.encodeSize();
+         PropertyValue oldValue = properties.put(key, value);
+         if (oldValue != null) {
+            size += value.encodeSize() - oldValue.encodeSize();
+         } else {
+            size += SimpleString.sizeofString(key) + value.encodeSize();
+         }
+      } finally {
+         lock.writeLock().unlock();
       }
    }
 
-   private synchronized Object doRemoveProperty(final SimpleString key) {
-      if (properties == null) {
-         return null;
-      }
+   private Object doRemoveProperty(final SimpleString key) {
+      lock.writeLock().lock();
+      try {
+         if (properties == null) {
+            return null;
+         }
 
-      PropertyValue val = properties.remove(key);
-      if (val == null) {
-         return null;
-      } else {
-         size -= SimpleString.sizeofString(key) + val.encodeSize();
-         return val.getValue();
+         PropertyValue val = properties.remove(key);
+         if (val == null) {
+            return null;
+         } else {
+            size -= SimpleString.sizeofString(key) + val.encodeSize();
+            return val.getValue();
+         }
+      } finally {
+         lock.writeLock().unlock();
       }
    }
 
-   private synchronized Object doGetProperty(final SimpleString key) {
-      if (properties == null) {
-         return null;
-      }
+   private Object doGetProperty(final SimpleString key) {
+      lock.readLock().lock();
+      try {
+         if (properties == null) {
+            return null;
+         }
 
-      PropertyValue val = properties.get(key);
-      if (val == null) {
-         return null;
-      } else {
-         return val.getValue();
+         PropertyValue val = properties.get(key);
+         if (val == null) {
+            return null;
+         } else {
+            return val.getValue();
+         }
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
@@ -1173,43 +1289,66 @@ public class TypedProperties {
       }
    }
 
-   public synchronized boolean isEmpty() {
-      if (properties == null) {
-         return true;
-      } else {
-         return properties.isEmpty();
+   public boolean isEmpty() {
+      lock.readLock().lock();
+      try {
+         if (properties == null) {
+            return true;
+         } else {
+            return properties.isEmpty();
+         }
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
-   public synchronized Set<String> getMapNames() {
-      if (properties == null) {
-         return Collections.emptySet();
-      } else {
-         Set<String> names = new HashSet<>(properties.size());
-         for (SimpleString name : properties.keySet()) {
-            names.add(name.toString());
+   public Set<String> getMapNames() {
+      lock.readLock().lock();
+      try {
+         if (properties == null) {
+            return Collections.emptySet();
+         } else {
+            Set<String> names = new HashSet<>(properties.size());
+            for (SimpleString name : properties.keySet()) {
+               names.add(name.toString());
+            }
+            return names;
          }
-         return names;
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
-   public synchronized Map<String, Object> getMap() {
-      if (properties == null) {
-         return Collections.emptyMap();
-      } else {
-         Map<String, Object> m = new HashMap<>(properties.size());
-         for (Entry<SimpleString, PropertyValue> entry : 
properties.entrySet()) {
-            Object val = entry.getValue().getValue();
-            if (val instanceof SimpleString simpleString) {
-               m.put(entry.getKey().toString(), simpleString.toString());
-            } else {
-               m.put(entry.getKey().toString(), val);
+   public Map<String, Object> getMap() {
+      lock.readLock().lock();
+      try {
+         if (properties == null) {
+            return Collections.emptyMap();
+         } else {
+            Map<String, Object> m = new HashMap<>(properties.size());
+            for (Entry<SimpleString, PropertyValue> entry : 
properties.entrySet()) {
+               Object val = entry.getValue().getValue();
+               if (val instanceof SimpleString simpleString) {
+                  m.put(entry.getKey().toString(), simpleString.toString());
+               } else {
+                  m.put(entry.getKey().toString(), val);
+               }
             }
+            return m;
          }
-         return m;
+      } finally {
+         lock.readLock().unlock();
       }
    }
 
+   public Lock getReadLock() {
+      return lock.readLock();
+   }
+
+   public Lock getWriteLock() {
+      return lock.writeLock();
+   }
+
    /**
     * Helper for MapMessage#setObjectProperty(String, Object)
     *
diff --git 
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java
 
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java
index a6f6d4e2a2..a7ce7775ca 100644
--- 
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java
+++ 
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java
@@ -790,7 +790,8 @@ public class CoreMessage extends RefCountMessage implements 
ICoreMessage {
          Long.BYTES +                                    // expiration
          Long.BYTES +                                    // timestamp
          Byte.BYTES;                                     // priority
-      synchronized (properties) {
+      properties.getReadLock().lock();
+      try {
          final int propertiesEncodeSize = properties.getEncodeSize();
          final int totalEncodedSize = headersSize + propertiesEncodeSize;
          ensureExactWritable(buffer, totalEncodedSize);
@@ -810,6 +811,8 @@ public class CoreMessage extends RefCountMessage implements 
ICoreMessage {
          assert buffer.writerIndex() == initialWriterIndex + headersSize : 
"Bad Headers encode size estimation";
          final int realPropertiesEncodeSize = properties.encode(buffer);
          assert realPropertiesEncodeSize == propertiesEncodeSize : 
"TypedProperties has a wrong encode size estimation or is being modified 
concurrently";
+      } finally {
+         properties.getReadLock().unlock();
       }
    }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information, visit: https://activemq.apache.org/contact


Reply via email to