This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 22f2fbefc78b28efe0a0e34e6cb8b4ba07fb1edc Author: opencode <[email protected]> AuthorDate: Sun Oct 11 13:09:09 2026 +0200 Clamp the backup index to the member snapshot in publishEntryInfo The backup node selection snapshots the members with getMapMembers and then computes the starting index with getNextBackupIndex, which clamps against the current member count rather than the snapshot. A member added between the two steps lets the returned index point beyond the end of the snapshot, and the first array access in the selection loop then threw an array index out of bounds exception. That exception was not caught by put(), which only handles channel exceptions, so it escaped before the entry was stored in the inner map. With the BackupManager, whose session storage is the replicated map, this lost a session that was being created while a node joined the cluster. The selection now falls back to the start of the snapshot when the index is out of its range, mirroring the existing guard in getNextBackupNode. Add a unit test that pins the diverging snapshot and index and drives the selection to completion. --- .../catalina/tribes/tipis/LazyReplicatedMap.java | 8 ++- .../tribes/tipis/TestLazyReplicatedMap.java | 61 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java b/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java index 8780a50564..b1b9bd207f 100644 --- a/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java +++ b/java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java @@ -158,13 +158,19 @@ public class LazyReplicatedMap<K, V> extends AbstractReplicatedMap<K,V> { } Member[] members = getMapMembers(); int firstIdx = getNextBackupIndex(); - int nextIdx = firstIdx; Member[] backup = new Member[0]; // there are no backups if (members.length == 0 || firstIdx == -1) { return backup; } + // A member may have been added between the snapshot of the members and the computation of + // the index, which then points beyond the end of the snapshot. Fall back to its start, as + // getNextBackupNode does. + if (firstIdx >= members.length) { + firstIdx = 0; + } + int nextIdx = firstIdx; boolean success = false; do { diff --git a/test/org/apache/catalina/tribes/tipis/TestLazyReplicatedMap.java b/test/org/apache/catalina/tribes/tipis/TestLazyReplicatedMap.java new file mode 100644 index 0000000000..f6cc37fec2 --- /dev/null +++ b/test/org/apache/catalina/tribes/tipis/TestLazyReplicatedMap.java @@ -0,0 +1,61 @@ +/* + * 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.catalina.tribes.tipis; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.tribes.Member; +import org.apache.catalina.tribes.group.GroupChannel; +import org.apache.catalina.tribes.membership.MemberImpl; + +public class TestLazyReplicatedMap { + + /* + * The backup node selection snapshots the members and computes an index against the current + * members in two steps. A member added in between lets the index point behind the end of the + * snapshot, which aborted the publication with an array index out of bounds exception, losing + * the entry that was being stored. The index must fall back to the start of the snapshot. + */ + @Test + public void testBackupIndexBeyondMemberSnapshotFallsBack() throws Exception { + Member member = new MemberImpl("localhost", 4007, -1); + + // A member added between the two steps of the selection, shown directly: one member in + // the snapshot, an index computed for a member list that has meanwhile grown + LazyReplicatedMap<String,String> map = new LazyReplicatedMap<String,String>( + (key, value) -> { + // Not made primary here + }, new GroupChannel(), 1000, "TestLazyReplicatedMap", null) { + + @Override + public Member[] getMapMembers() { + return new Member[] { member }; + } + + @Override + public int getNextBackupIndex() { + return 1; + } + }; + + // The channel is not started, so the publication to the backup fails as a channel + // exception that the selection contains, while the entry survives to be stored locally + Member[] backup = map.publishEntryInfo("key", "value"); + Assert.assertEquals(0, backup.length); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
