This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new 824222789f Review "Skip the removal notification for keys that can't
be serialized"
824222789f is described below
commit 824222789f70c0fe5807b3daa890bc8f9c180041
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Oct 23 12:06:05 2026 +0100
Review "Skip the removal notification for keys that can't be serialized"
Expand the serialization checks
Co-authored by: CoPilot / GPT-5.6 Sol
Co-authored by: Claude / Sonnet 5.5
---
.../tribes/tipis/AbstractReplicatedMap.java | 90 ++++++++++++----
.../catalina/tribes/tipis/LazyReplicatedMap.java | 16 ++-
.../catalina/tribes/tipis/LocalStrings.properties | 1 +
.../catalina/tribes/tipis/ReplicatedMap.java | 28 +++--
.../tribes/tipis/TestAbstractReplicatedMap.java | 114 +++++++++++++++++++++
5 files changed, 218 insertions(+), 31 deletions(-)
diff --git a/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
b/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
index 341331cb03..202af7d12d 100644
--- a/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
+++ b/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
@@ -595,8 +595,11 @@ public abstract class AbstractReplicatedMap<K, V>
rentry.lock();
try {
// construct a diff message
- msg = new MapMessage(mapContextName,
getReplicateMessageType(), true, (Serializable) entry.getKey(),
- null, rentry.getDiff(), entry.getPrimary(),
entry.getBackupNodes());
+ msg = createMapMessage(getReplicateMessageType(), true,
(Serializable) entry.getKey(), null,
+ rentry.getDiff(), entry.getPrimary(),
entry.getBackupNodes());
+ if (msg == null) {
+ return;
+ }
rentry.resetDiff();
} catch (IOException ioe) {
log.error(sm.getString("abstractReplicatedMap.unable.diffObject"), ioe);
@@ -606,13 +609,19 @@ public abstract class AbstractReplicatedMap<K, V>
}
if (msg == null && complete) {
// construct a complete
- msg = new MapMessage(mapContextName,
getReplicateMessageType(), false, (Serializable) entry.getKey(),
+ msg = createMapMessage(getReplicateMessageType(), false,
(Serializable) entry.getKey(),
(Serializable) entry.getValue(), null,
entry.getPrimary(), entry.getBackupNodes());
+ if (msg == null) {
+ return;
+ }
}
if (msg == null) {
// construct an access message
- msg = new MapMessage(mapContextName, MapMessage.MSG_ACCESS,
false, (Serializable) entry.getKey(), null,
+ msg = createMapMessage(MapMessage.MSG_ACCESS, false,
(Serializable) entry.getKey(), null,
null, entry.getPrimary(), entry.getBackupNodes());
+ if (msg == null) {
+ return;
+ }
}
try {
if (channel != null && entry.getBackupNodes() != null &&
entry.getBackupNodes().length > 0) {
@@ -728,7 +737,12 @@ public abstract class AbstractReplicatedMap<K, V>
if (entry == null || (!entry.isSerializable())) {
return null;
}
- mapmsg.setValue((Serializable) entry.getValue());
+ try {
+ mapmsg.setValue((Serializable) entry.getValue());
+ } catch (RuntimeException x) {
+
log.error(sm.getString("abstractReplicatedMap.unable.serialize",
entry.getKey()), x);
+ return null;
+ }
return mapmsg;
}
@@ -748,10 +762,12 @@ public abstract class AbstractReplicatedMap<K, V>
// map content blocks every other state operation for a long time
otherwise
ArrayList<MapMessage> list = new ArrayList<>(snapshot.size());
for (MapState<K,V> entry : snapshot) {
- MapMessage me = new MapMessage(mapContextName, copy ?
MapMessage.MSG_COPY : MapMessage.MSG_PROXY,
- false, (Serializable) entry.key, copy ? (Serializable)
entry.value : null, null, entry.primary,
+ MapMessage me = createMapMessage(copy ? MapMessage.MSG_COPY :
MapMessage.MSG_PROXY, false,
+ (Serializable) entry.key, copy ? (Serializable)
entry.value : null, null, entry.primary,
entry.backupNodes);
- list.add(me);
+ if (me != null) {
+ list.add(me);
+ }
}
mapmsg.setValue(list);
return mapmsg;
@@ -1278,8 +1294,11 @@ public abstract class AbstractReplicatedMap<K, V>
// Skip the notification for a key that cannot be serialized, as
the message
// construction would fail on it, like the replication of put does
if (getMapMembers().length > 0 && notify && entry != null && key
instanceof Serializable) {
- MapMessage msg = new MapMessage(getMapContextName(),
MapMessage.MSG_REMOVE, false, (Serializable) key,
- null, null, null, null);
+ MapMessage msg =
+ createMapMessage(MapMessage.MSG_REMOVE, false,
(Serializable) key, null, null, null, null);
+ if (msg == null) {
+ return entry.getValue();
+ }
getChannel().send(getMapMembers(), msg,
getChannelSendOptions());
}
} catch (ChannelException x) {
@@ -1319,8 +1338,11 @@ public abstract class AbstractReplicatedMap<K, V>
backup = publishEntryInfo(key, entry.getValue());
} else if (entry.isProxy()) {
// make sure we don't retrieve from ourselves
- msg = new MapMessage(getMapContextName(),
MapMessage.MSG_RETRIEVE_BACKUP, false, (Serializable) key,
- null, null, null, null);
+ msg = createMapMessage(MapMessage.MSG_RETRIEVE_BACKUP,
false, (Serializable) key, null, null, null,
+ null);
+ if (msg == null) {
+ return null;
+ }
Response[] resp =
getRpcChannel().send(entry.getBackupNodes(), msg, RpcChannel.FIRST_REPLY,
getChannelSendOptions(), getRpcTimeout());
if (resp == null || resp.length == 0 ||
resp[0].getMessage() == null) {
@@ -1336,15 +1358,21 @@ public abstract class AbstractReplicatedMap<K, V>
}
// notify member
- msg = new MapMessage(getMapContextName(),
MapMessage.MSG_NOTIFY_MAPMEMBER, false,
- (Serializable) entry.getKey(), null, null,
channel.getLocalMember(false), backup);
+ msg = createMapMessage(MapMessage.MSG_NOTIFY_MAPMEMBER,
false, (Serializable) entry.getKey(), null,
+ null, channel.getLocalMember(false), backup);
+ if (msg == null) {
+ return null;
+ }
if (backup != null && backup.length > 0) {
getChannel().send(backup, msg,
getChannelSendOptions());
}
// invalidate the previous primary
- msg = new MapMessage(getMapContextName(),
MapMessage.MSG_PROXY, false, (Serializable) key, null,
- null, channel.getLocalMember(false), backup);
+ msg = createMapMessage(MapMessage.MSG_PROXY, false,
(Serializable) key, null, null,
+ channel.getLocalMember(false), backup);
+ if (msg == null) {
+ return null;
+ }
Member[] dest = getMapMembersExcl(backup);
if (dest != null && dest.length > 0) {
getChannel().send(dest, msg, getChannelSendOptions());
@@ -1355,8 +1383,11 @@ public abstract class AbstractReplicatedMap<K, V>
} else if (entry.isCopy()) {
backup = getMapMembers();
if (backup.length > 0) {
- msg = new MapMessage(getMapContextName(),
MapMessage.MSG_NOTIFY_MAPMEMBER, false,
- (Serializable) key, null, null,
channel.getLocalMember(false), backup);
+ msg =
createMapMessage(MapMessage.MSG_NOTIFY_MAPMEMBER, false, (Serializable) key,
null, null,
+ channel.getLocalMember(false), backup);
+ if (msg == null) {
+ return null;
+ }
getChannel().send(backup, msg,
getChannelSendOptions());
}
}
@@ -2283,6 +2314,29 @@ public abstract class AbstractReplicatedMap<K, V>
return mapContextName;
}
+ /**
+ * Creates a map message, or returns {@code null} when its key or value
cannot be serialized.
+ *
+ * @param msgtype The message type
+ * @param diff Whether this is a diff message
+ * @param key The key
+ * @param value The value
+ * @param diffvalue The serialized diff value
+ * @param primary The primary member
+ * @param nodes The backup members
+ *
+ * @return The map message, or {@code null} if serialization fails
+ */
+ protected MapMessage createMapMessage(int msgtype, boolean diff,
Serializable key, Serializable value,
+ byte[] diffvalue, Member primary, Member[] nodes) {
+ try {
+ return new MapMessage(mapContextName, msgtype, diff, key, value,
diffvalue, primary, nodes);
+ } catch (RuntimeException x) {
+ log.error(sm.getString("abstractReplicatedMap.unable.serialize",
key), x);
+ return null;
+ }
+ }
+
/**
* Gets the RPC channel.
*
diff --git a/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
b/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
index 102eaf6317..c80de55ef1 100644
--- a/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
+++ b/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
@@ -181,11 +181,14 @@ public class LazyReplicatedMap<K, V> extends
AbstractReplicatedMap<K,V> {
continue;
}
MapMessage msg;
+ Member[] tmpBackup = wrap(next);
+ msg = createMapMessage(MapMessage.MSG_BACKUP, false,
(Serializable) key, (Serializable) value, null,
+ channel.getLocalMember(false), tmpBackup);
+ if (msg == null) {
+ return new Member[0];
+ }
try {
- Member[] tmpBackup = wrap(next);
// publish the backup data to one node
- msg = new MapMessage(getMapContextName(),
MapMessage.MSG_BACKUP, false, (Serializable) key,
- (Serializable) value, null,
channel.getLocalMember(false), tmpBackup);
if (log.isTraceEnabled()) {
log.trace("Publishing backup data:" + msg + " to: " +
next.getName());
}
@@ -204,8 +207,11 @@ public class LazyReplicatedMap<K, V> extends
AbstractReplicatedMap<K,V> {
// publish the data out to all nodes
Member[] proxies = excludeFromSet(backup, getMapMembers());
if (proxies.length > 0) {
- msg = new MapMessage(getMapContextName(),
MapMessage.MSG_PROXY, false, (Serializable) key, null,
- null, channel.getLocalMember(false), backup);
+ msg = createMapMessage(MapMessage.MSG_PROXY, false,
(Serializable) key, null, null,
+ channel.getLocalMember(false), backup);
+ if (msg == null) {
+ return backup;
+ }
if (log.isTraceEnabled()) {
log.trace("Publishing proxy data:" + msg + " to: " +
Arrays.toNameString(proxies));
}
diff --git a/java/org/apache/catalina/tribes/tipis/LocalStrings.properties
b/java/org/apache/catalina/tribes/tipis/LocalStrings.properties
index acc249d3e0..04c6c1208d 100644
--- a/java/org/apache/catalina/tribes/tipis/LocalStrings.properties
+++ b/java/org/apache/catalina/tribes/tipis/LocalStrings.properties
@@ -41,6 +41,7 @@ abstractReplicatedMap.unable.relocate=Unable to relocate
[{0}] to a new backup n
abstractReplicatedMap.unable.remove=Unable to replicate out data for an
AbstractReplicatedMap.remove operation
abstractReplicatedMap.unable.replicate=Unable to replicate data.
abstractReplicatedMap.unable.retrieve=Unable to retrieve remote object for
key:[{0}]
+abstractReplicatedMap.unable.serialize=Unable to serialize key:[{0}] or its
value, the entry is only kept on this node
abstractReplicatedMap.unable.transferState=Unable to transfer
AbstractReplicatedMap state.
abstractReplicatedMap.unableApply.diff=Unable to apply diff to key:[{0}]
abstractReplicatedMap.unableSelect.backup=Unable to select backup node.
diff --git a/java/org/apache/catalina/tribes/tipis/ReplicatedMap.java
b/java/org/apache/catalina/tribes/tipis/ReplicatedMap.java
index 3ed7e30a4e..d72927d0e9 100644
--- a/java/org/apache/catalina/tribes/tipis/ReplicatedMap.java
+++ b/java/org/apache/catalina/tribes/tipis/ReplicatedMap.java
@@ -150,11 +150,14 @@ public class ReplicatedMap<K, V> extends
AbstractReplicatedMap<K,V> {
return backup;
}
+ MapMessage msg = createMapMessage(MapMessage.MSG_COPY, false,
(Serializable) key, (Serializable) value, null,
+ channel.getLocalMember(false), backup);
+ if (msg == null) {
+ return new Member[0];
+ }
+
try {
// publish the data out to all nodes
- MapMessage msg = new MapMessage(getMapContextName(),
MapMessage.MSG_COPY, false, (Serializable) key,
- (Serializable) value, null, channel.getLocalMember(false),
backup);
-
getChannel().send(backup, msg, getChannelSendOptions());
} catch (ChannelException e) {
FaultyMember[] faultyMembers = e.getFaultyMembers();
@@ -198,16 +201,21 @@ public class ReplicatedMap<K, V> extends
AbstractReplicatedMap<K,V> {
long start = System.currentTimeMillis();
for (Entry<K,MapEntry<K,V>> e : innerMap.entrySet()) {
MapEntry<K,V> entry = innerMap.get(e.getKey());
- if (entry == null) {
+ if (entry == null || !entry.isSerializable()) {
+ // An entry that cannot be serialized is only kept on this
node, so there is nothing to relocate
continue;
}
if (entry.isPrimary()) {
try {
Member[] backup = getMapMembers();
if (backup.length > 0) {
- MapMessage msg = new MapMessage(getMapContextName(),
MapMessage.MSG_NOTIFY_MAPMEMBER, false,
+ MapMessage msg =
createMapMessage(MapMessage.MSG_NOTIFY_MAPMEMBER, false,
(Serializable) entry.getKey(), null, null,
channel.getLocalMember(false), backup);
- getChannel().send(backup, msg,
getChannelSendOptions());
+ if (msg != null) {
+ getChannel().send(backup, msg,
getChannelSendOptions());
+ } else {
+ backup = new Member[0];
+ }
}
entry.setBackupNodes(backup);
entry.setPrimary(channel.getLocalMember(false));
@@ -228,9 +236,13 @@ public class ReplicatedMap<K, V> extends
AbstractReplicatedMap<K,V> {
entry.setCopy(false);
Member[] backup = getMapMembers();
if (backup.length > 0) {
- MapMessage msg = new MapMessage(getMapContextName(),
MapMessage.MSG_NOTIFY_MAPMEMBER, false,
+ MapMessage msg =
createMapMessage(MapMessage.MSG_NOTIFY_MAPMEMBER, false,
(Serializable) entry.getKey(), null, null,
channel.getLocalMember(false), backup);
- getChannel().send(backup, msg,
getChannelSendOptions());
+ if (msg != null) {
+ getChannel().send(backup, msg,
getChannelSendOptions());
+ } else {
+ backup = new Member[0];
+ }
}
entry.setBackupNodes(backup);
if (mapOwner != null) {
diff --git
a/test/org/apache/catalina/tribes/tipis/TestAbstractReplicatedMap.java
b/test/org/apache/catalina/tribes/tipis/TestAbstractReplicatedMap.java
index c11f38fd16..4bd792a239 100644
--- a/test/org/apache/catalina/tribes/tipis/TestAbstractReplicatedMap.java
+++ b/test/org/apache/catalina/tribes/tipis/TestAbstractReplicatedMap.java
@@ -450,4 +450,118 @@ public class TestAbstractReplicatedMap {
}
return -1;
}
+
+ /*
+ * A key may implement Serializable and still fail to serialize, which the
message construction
+ * reports as an unchecked exception. That must not abort the removal
either.
+ */
+ @Test
+ public void testRemoveUnserializableKeyDoesNotThrow() throws Exception {
+ TestChannel channel = new TestChannel();
+ LazyReplicatedMap<Object,String> map = new
LazyReplicatedMap<Object,String>(
+ (key, value) -> {
+ // Not made primary here
+ }, channel, 1000, "TestAbstractReplicatedMap", null);
+ map.memberAlive(new MemberImpl("localhost", 4019, -1));
+
+ // The put keeps the entry on this node, as it cannot be published to
a backup
+ Object key = new FailingKey();
+ map.put(key, "value");
+ Assert.assertEquals(0, channel.sendCalls.get());
+
+ Assert.assertEquals("value", map.remove(key));
+ Assert.assertNull(map.getInternal(key));
+ Assert.assertEquals(0, channel.sendCalls.get());
+ }
+
+ @Test
+ public void testPutUnserializableKeyReplicatedMapKeepsEntryLocal() throws
Exception {
+ TestChannel channel = new TestChannel();
+ ReplicatedMap<Object,String> map = new ReplicatedMap<Object,String>(
+ (key, value) -> {
+ // Not made primary here
+ }, channel, 1000, "TestAbstractReplicatedMap", null);
+ Member first = new MemberImpl("localhost", 4020, -1);
+ Member second = new MemberImpl("localhost", 4021, -1);
+ map.memberAlive(first);
+ map.memberAlive(second);
+
+ Object key = new FailingKey();
+ map.put(key, "value");
+
+ Assert.assertEquals("value", map.get(key));
+ Assert.assertEquals(0, channel.sendCalls.get());
+ map.memberDisappeared(first);
+ Assert.assertEquals("value", map.get(key));
+ Assert.assertEquals(0, channel.sendCalls.get());
+ Assert.assertEquals("value", map.remove(key));
+ }
+
+ @Test
+ public void testMemberDisappearedSkipsNonSerializableEntry() throws
Exception {
+ TestChannel channel = new TestChannel();
+ ReplicatedMap<Object,String> map = new ReplicatedMap<Object,String>(
+ (key, value) -> {
+ // Not made primary here
+ }, channel, 1000, "TestAbstractReplicatedMap", null);
+ Member first = new MemberImpl("localhost", 4024, -1);
+ Member second = new MemberImpl("localhost", 4025, -1);
+ map.memberAlive(first);
+ map.memberAlive(second);
+
+ Object key = new Object();
+ map.put(key, "value");
+ map.put("good", "value");
+
+ map.memberDisappeared(first);
+
+ Assert.assertEquals("value", map.get(key));
+ Assert.assertEquals("value", map.get("good"));
+ }
+
+ @Test
+ public void testStateRequestSkipsUnserializableEntry() throws Exception {
+ LazyReplicatedMap<Object,String> map = new
LazyReplicatedMap<Object,String>(
+ (key, value) -> {
+ // Not made primary here
+ }, new TestChannel(), 1000, "TestAbstractReplicatedMap", null);
+ map.put("good", "value");
+ map.put(new FailingKey(), "local");
+
+ MapMessage stateRequest = new
MapMessage("TestAbstractReplicatedMap".getBytes(StandardCharsets.UTF_8),
+ MapMessage.MSG_STATE_COPY, false, null, null, null, null,
null);
+ Serializable reply = map.replyRequest(stateRequest, new
MemberImpl("localhost", 4022, -1));
+
+ Assert.assertTrue(reply instanceof MapMessage);
+ Object value = ((MapMessage) reply).getValue();
+ Assert.assertTrue(value instanceof ArrayList);
+ ArrayList<?> state = (ArrayList<?>) value;
+ Assert.assertEquals(1, state.size());
+ Assert.assertEquals("good", ((MapMessage) state.get(0)).getKey());
+ }
+
+ @Test
+ public void testBackupRequestSkipsUnserializableValue() throws Exception {
+ LazyReplicatedMap<String,Object> map = new
LazyReplicatedMap<String,Object>(
+ (key, value) -> {
+ // Not made primary here
+ }, new TestChannel(), 1000, "TestAbstractReplicatedMap", null);
+ map.put("key", new FailingKey());
+
+ MapMessage backupRequest = new
MapMessage("TestAbstractReplicatedMap".getBytes(StandardCharsets.UTF_8),
+ MapMessage.MSG_RETRIEVE_BACKUP, false, "key", null, null,
null, null);
+
+ Assert.assertNull(map.replyRequest(backupRequest, new
MemberImpl("localhost", 4023, -1)));
+ }
+
+ private static final class FailingKey implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Serial
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ throw new IOException("Cannot be serialized");
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]