This is an automated email from the ASF dual-hosted git repository.
zhouxj pushed a commit to branch support/1.14
in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/support/1.14 by this push:
new e456867c5f GEODE-10290: GII requester should remove departed members
(#7670)
e456867c5f is described below
commit e456867c5fdbe2382158ad874e5adaf46e41e164
Author: Xiaojian Zhou <[email protected]>
AuthorDate: Wed May 18 11:55:15 2022 -0700
GEODE-10290: GII requester should remove departed members (#7670)
(cherry picked from commit 3d6354cb6b182d54531a8103a357f03754cf5165)
---
...PartitionedRegionRestartRebalanceDUnitTest.java | 133 +++++++++++++++++
.../internal/cache/InitialImageOperation.java | 48 ++++--
.../cache/versions/RegionVersionHolder.java | 2 +
.../cache/versions/RegionVersionVector.java | 16 +-
.../internal/cache/InitialImageOperationTest.java | 165 +++++++++++++++++++++
5 files changed, 343 insertions(+), 21 deletions(-)
diff --git
a/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/PartitionedRegionRestartRebalanceDUnitTest.java
b/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/PartitionedRegionRestartRebalanceDUnitTest.java
new file mode 100644
index 0000000000..8f8a37758b
--- /dev/null
+++
b/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/PartitionedRegionRestartRebalanceDUnitTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.geode.internal.cache;
+
+import static org.apache.geode.test.dunit.VM.getVM;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.apache.geode.cache.DataPolicy;
+import org.apache.geode.cache.PartitionAttributesFactory;
+import org.apache.geode.cache.RegionFactory;
+import org.apache.geode.cache.control.RebalanceOperation;
+import org.apache.geode.cache.control.RebalanceResults;
+import org.apache.geode.internal.cache.versions.VersionSource;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.rules.CacheRule;
+import org.apache.geode.test.dunit.rules.DistributedRule;
+
+public class PartitionedRegionRestartRebalanceDUnitTest implements
Serializable {
+ private static final int REDUNDANT_COPIES = 2;
+ private static final int TOTAL_NUM_BUCKETS = 12;
+ private static final Logger logger = LogManager.getLogger();
+
+ private String REGION_NAME = getClass().getSimpleName();;
+ private VM[] datastores;
+
+ @Rule
+ public DistributedRule distributedRule = new DistributedRule();
+
+ @Rule
+ public CacheRule cacheRule = new CacheRule();
+
+ @Before
+ public void setUp() throws Exception {
+ datastores = new VM[4];
+ for (int i = 0; i < datastores.length; i++) {
+ datastores[i] = getVM(i);
+ datastores[i].invoke(() -> cacheRule.createCache());
+ datastores[i].invoke(() -> createRegion());
+ }
+ datastores[0].invoke(() -> feedData());
+ }
+
+ private void createRegion() {
+ PartitionAttributesFactory<String, Integer> paf = new
PartitionAttributesFactory();
+ paf.setRedundantCopies(REDUNDANT_COPIES);
+ paf.setTotalNumBuckets(TOTAL_NUM_BUCKETS);
+
+ RegionFactory<String, Integer> rf =
cacheRule.getCache().createRegionFactory();
+ rf.setDataPolicy(DataPolicy.PARTITION);
+ rf.setPartitionAttributes(paf.create());
+ LocalRegion region = (LocalRegion) rf.create(REGION_NAME);
+ }
+
+ private void feedData() throws InterruptedException {
+ PartitionedRegion pr = (PartitionedRegion)
cacheRule.getCache().getRegion(REGION_NAME);
+ for (int i = 0; i < TOTAL_NUM_BUCKETS * 2; i++) {
+ pr.put(i, "VALUE-" + i);
+ if (i < TOTAL_NUM_BUCKETS) {
+ pr.destroy(i);
+ }
+ }
+
cacheRule.getCache().getTombstoneService().forceBatchExpirationForTests(TOTAL_NUM_BUCKETS);
+ }
+
+ private void rebalance() throws InterruptedException {
+ RebalanceOperation op =
+
cacheRule.getCache().getResourceManager().createRebalanceFactory().start();
+ RebalanceResults results = op.getResults();
+ logger.info("Rebalance total time is " + results.getTotalTime());
+ }
+
+ private void verify() {
+ PartitionedRegion pr = (PartitionedRegion)
cacheRule.getCache().getRegion(REGION_NAME);
+ for (BucketRegion br : pr.getDataStore().getAllLocalBucketRegions()) {
+ Set<VersionSource> departedMemberSet =
br.getVersionVector().getDepartedMembersSet();
+ for (Object key : br.getRegionKeysForIteration()) {
+ RegionEntry entry = br.getRegionEntry(key);
+ departedMemberSet.remove(entry.getVersionStamp().getMemberID());
+ if (departedMemberSet.isEmpty()) {
+ break;
+ }
+ }
+ Map map = br.getVersionVector().getMemberToVersion();
+ for (Object key : br.getVersionVector().getMemberToVersion().keySet()) {
+ logger.info(br.getFullPath() + ":" + key + ":"
+ + br.getVersionVector().getMemberToVersion().get(key));
+ }
+ // The test proved that departedMemberSet is not growing
+
assertThat(departedMemberSet.size()).isLessThanOrEqualTo(datastores.length);
+ }
+ }
+
+ @Test
+ public void restartAndRebalanceShouldNotIncreaseMemberToVersionMap() {
+ for (int i = 0; i < datastores.length * 10; i++) {
+ datastores[i % datastores.length].invoke(() -> {
+ cacheRule.getCache().close();
+ });
+ datastores[(i + 1) % datastores.length].invoke(() -> {
+ rebalance();
+ verify();
+ });
+ datastores[i % datastores.length].invoke(() -> {
+ cacheRule.createCache();
+ createRegion();
+ rebalance();
+ verify();
+ });
+ }
+ }
+}
diff --git
a/geode-core/src/main/java/org/apache/geode/internal/cache/InitialImageOperation.java
b/geode-core/src/main/java/org/apache/geode/internal/cache/InitialImageOperation.java
index 32e5995a68..3131d2b888 100644
---
a/geode-core/src/main/java/org/apache/geode/internal/cache/InitialImageOperation.java
+++
b/geode-core/src/main/java/org/apache/geode/internal/cache/InitialImageOperation.java
@@ -366,7 +366,7 @@ public class InitialImageOperation {
// remote_rvv will be filled with the versions of unfinished keys
// then if recoveredRVV is still newer than the filled remote_rvv, do
fullGII
remote_rvv = received_rvv.getCloneForTransmission();
- keysOfUnfinishedOps = processReceivedRVV(remote_rvv, recoveredRVV);
+ keysOfUnfinishedOps = processReceivedRVV(remote_rvv, recoveredRVV,
received_rvv);
if (internalAfterCalculatedUnfinishedOps != null
&&
internalAfterCalculatedUnfinishedOps.getRegionName().equals(this.region.getName()))
{
internalAfterCalculatedUnfinishedOps.run();
@@ -1057,21 +1057,32 @@ public class InitialImageOperation {
/**
* Compare the received RVV with local RVV and return a set of keys for
unfinished operations.
*
- * @param remoteRVV RVV from provider
+ * @param remoteRVV RVV from provider to be filled with unfinished operations
* @param localRVV RVV recovered from disk
+ * @param receivedRVV original RVV from provider to remove departed members
* @return set for keys of unfinished operations.
*/
- protected Set processReceivedRVV(RegionVersionVector remoteRVV,
RegionVersionVector localRVV) {
+ protected Set<Object> processReceivedRVV(RegionVersionVector remoteRVV,
+ RegionVersionVector localRVV, RegionVersionVector receivedRVV) {
if (remoteRVV == null) {
return null;
}
// calculate keys for unfinished ops
- HashSet keys = new HashSet();
- if (this.region.getDataPolicy().withPersistence()
- && localRVV.isNewerThanOrCanFillExceptionsFor(remoteRVV)) {
- // only search for unfinished keys when localRVV has something newer
- // and the region is persistent region
- Iterator it = this.region.getBestIterator(false);
+ HashSet<Object> keys = new HashSet<>();
+ Set<VersionSource> departedMemberSet = receivedRVV.getDepartedMembersSet();
+ boolean isPersistentRegion = region.getDataPolicy().withPersistence();
+ Set<VersionSource> foundIds;
+ if (!isPersistentRegion) {
+ foundIds = new HashSet<>();
+ } else {
+ foundIds = Collections.emptySet();
+ }
+ if ((isPersistentRegion &&
localRVV.isNewerThanOrCanFillExceptionsFor(remoteRVV))
+ || !departedMemberSet.isEmpty()) {
+ // Only search for unfinished keys when localRVV has something newer
+ // and the region is persistent region.
+ // Search for departed members if region is not persistent region
+ Iterator<RegionEntry> it = region.getBestIterator(false);
int count = 0;
VersionSource<?> myId = this.region.getVersionMember();
while (it.hasNext()) {
@@ -1081,7 +1092,9 @@ public class InitialImageOperation {
if (id == null) {
id = myId;
}
- if (!remoteRVV.contains(id, stamp.getRegionVersion())) {
+ if (!isPersistentRegion) {
+ foundIds.add(id);
+ } else if (!remoteRVV.contains(id, stamp.getRegionVersion())) {
// found an unfinished operation
keys.add(mapEntry.getKey());
remoteRVV.recordVersion(id, stamp.getRegionVersion());
@@ -1104,6 +1117,13 @@ public class InitialImageOperation {
}
}
}
+ if (!departedMemberSet.isEmpty()) {
+ if (localRVV != null) {
+ localRVV.removeOldMembers(foundIds);
+ }
+ receivedRVV.removeOldMembers(foundIds);
+ remoteRVV.removeOldMembers(foundIds);
+ }
return keys;
}
@@ -2074,11 +2094,9 @@ public class InitialImageOperation {
// if this region is destroyed while we are sending data, then abort.
} while (keepGoing && it.hasNext());
- if (foundIds.size() > 0) {
- RegionVersionVector vv = rgn.getVersionVector();
- if (vv != null) {
- vv.removeOldMembers(foundIds);
- }
+ RegionVersionVector vv = rgn.getVersionVector();
+ if (vv != null) {
+ vv.removeOldMembers(foundIds);
}
// return false if we were told to abort
return sentLastChunk;
diff --git
a/geode-core/src/main/java/org/apache/geode/internal/cache/versions/RegionVersionHolder.java
b/geode-core/src/main/java/org/apache/geode/internal/cache/versions/RegionVersionHolder.java
index 6e22f8116e..accfe5f2aa 100755
---
a/geode-core/src/main/java/org/apache/geode/internal/cache/versions/RegionVersionHolder.java
+++
b/geode-core/src/main/java/org/apache/geode/internal/cache/versions/RegionVersionHolder.java
@@ -207,6 +207,8 @@ public class RegionVersionHolder<T> implements Cloneable,
DataSerializable {
sb.append(this.exceptions.toString());
}
sb.append("}");
+ sb.append("id=" + id);
+ sb.append(",departed?" + isDepartedMember);
return sb.toString();
}
diff --git
a/geode-core/src/main/java/org/apache/geode/internal/cache/versions/RegionVersionVector.java
b/geode-core/src/main/java/org/apache/geode/internal/cache/versions/RegionVersionVector.java
index 9fe8649a4c..b99792f818 100644
---
a/geode-core/src/main/java/org/apache/geode/internal/cache/versions/RegionVersionVector.java
+++
b/geode-core/src/main/java/org/apache/geode/internal/cache/versions/RegionVersionVector.java
@@ -192,11 +192,12 @@ public abstract class RegionVersionVector<T extends
VersionSource<?>>
@VisibleForTesting
RegionVersionVector(T ownerId, LocalRegion owner, long version) {
- this.myId = ownerId;
- this.isLiveVector = true;
- this.region = owner;
- this.localExceptions = new RegionVersionHolder<T>(0);
- this.memberToVersion =
+ myId = ownerId;
+ isLiveVector = true;
+ region = owner;
+ localExceptions = new RegionVersionHolder<>(0);
+ localExceptions.id = myId;
+ memberToVersion =
new ConcurrentHashMap<>(INITIAL_CAPACITY, LOAD_FACTOR,
CONCURRENCY_LEVEL);
this.memberToGCVersion =
new ConcurrentHashMap<>(INITIAL_CAPACITY, LOAD_FACTOR,
CONCURRENCY_LEVEL);
@@ -609,7 +610,10 @@ public abstract class RegionVersionVector<T extends
VersionSource<?>>
if (!mbr.equals(this.myId)) {
h = otherHolder.clone();
h.makeReadyForRecording();
- this.memberToVersion.put(mbr, h);
+ if (h.id == null) {
+ h.id = mbr;
+ }
+ memberToVersion.put(mbr, h);
} else {
RegionVersionHolder<T> vh = otherHolder;
long version = vh.version;
diff --git
a/geode-core/src/test/java/org/apache/geode/internal/cache/InitialImageOperationTest.java
b/geode-core/src/test/java/org/apache/geode/internal/cache/InitialImageOperationTest.java
index 61bed57db8..ffad8b416e 100644
---
a/geode-core/src/test/java/org/apache/geode/internal/cache/InitialImageOperationTest.java
+++
b/geode-core/src/test/java/org/apache/geode/internal/cache/InitialImageOperationTest.java
@@ -15,6 +15,7 @@
package org.apache.geode.internal.cache;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -22,14 +23,23 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import java.util.ArrayList;
+import java.util.Iterator;
+
import org.junit.Before;
import org.junit.Test;
import org.apache.geode.cache.CacheClosedException;
+import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.Scope;
import org.apache.geode.distributed.internal.ClusterDistributionManager;
import
org.apache.geode.distributed.internal.membership.InternalDistributedMember;
+import org.apache.geode.internal.cache.persistence.DiskStoreID;
+import org.apache.geode.internal.cache.versions.DiskRegionVersionVector;
+import org.apache.geode.internal.cache.versions.RegionVersionVector;
+import org.apache.geode.internal.cache.versions.VMRegionVersionVector;
import org.apache.geode.internal.cache.versions.VersionSource;
+import org.apache.geode.internal.cache.versions.VersionStamp;
public class InitialImageOperationTest {
@@ -95,4 +105,159 @@ public class InitialImageOperationTest {
verify(distributedRegion,
never()).scheduleSynchronizeForLostMember(lostMember, versionSource,
0);
}
+
+ @Test
+ public void shouldRemoveDepartedMembersFromRVVForNonPersistentRegion() {
+ InternalDistributedMember server1 = new InternalDistributedMember("host1",
101);
+ InternalDistributedMember server2 = new InternalDistributedMember("host2",
102);
+ InternalDistributedMember server3 = new InternalDistributedMember("host3",
103);
+ InternalDistributedMember server4 = new InternalDistributedMember("host4",
104);
+ when(distributedRegion.getDataPolicy()).thenReturn(DataPolicy.REPLICATE);
+ when(distributedRegion.getVersionMember()).thenReturn(server1);
+
+ RegionEntry re1 = mock(RegionEntry.class);
+ RegionEntry re2 = mock(RegionEntry.class);
+ RegionEntry re3 = mock(RegionEntry.class);
+ ArrayList<RegionEntry> entries = new ArrayList<>();
+ entries.add(re1);
+ entries.add(re2);
+ entries.add(re3);
+ Iterator<RegionEntry> iterator = entries.iterator();
+ when(distributedRegion.getBestIterator(false)).thenReturn(iterator);
+ VersionStamp stamp1 = mock(VersionStamp.class);
+ VersionStamp stamp2 = mock(VersionStamp.class);
+ VersionStamp stamp3 = mock(VersionStamp.class);
+ when(re1.getVersionStamp()).thenReturn(stamp1);
+ when(re2.getVersionStamp()).thenReturn(stamp2);
+ when(re3.getVersionStamp()).thenReturn(stamp3);
+ when(stamp1.getMemberID()).thenReturn(server1);
+ when(stamp2.getMemberID()).thenReturn(server2);
+ when(stamp3.getMemberID()).thenReturn(server3);
+
+ RegionMap regionMap = mock(RegionMap.class);
+ InitialImageOperation operation = spy(new
InitialImageOperation(distributedRegion, regionMap));
+
+ RegionVersionVector recoveredRVV = new VMRegionVersionVector(server1);
+ recoveredRVV.recordVersion(server1, 1);
+ recoveredRVV.recordVersion(server2, 1);
+ recoveredRVV.recordVersion(server3, 1);
+ recoveredRVV.recordVersion(server4, 1);
+ recoveredRVV.recordGCVersion(server2, 1);
+ recoveredRVV.recordGCVersion(server3, 1);
+ recoveredRVV.recordGCVersion(server4, 1);
+ recoveredRVV.memberDeparted(null, server3, true);
+ recoveredRVV.memberDeparted(null, server4, true);
+ assertThat(recoveredRVV.isDepartedMember(server3)).isTrue();
+ assertThat(recoveredRVV.isDepartedMember(server4)).isTrue();
+ assertThat(recoveredRVV.getMemberToVersion().size()).isEqualTo(4);
+ assertThat(recoveredRVV.getMemberToGCVersion().size()).isEqualTo(3);
+
+ RegionVersionVector receivedRVV = new VMRegionVersionVector(server2);
+ receivedRVV.recordVersion(server1, 1);
+ receivedRVV.recordVersion(server2, 1);
+ receivedRVV.recordVersion(server2, 2);
+ receivedRVV.recordVersion(server3, 1);
+ receivedRVV.recordVersion(server4, 1);
+ receivedRVV.recordGCVersion(server2, 1);
+ receivedRVV.recordGCVersion(server3, 1);
+ receivedRVV.recordGCVersion(server4, 1);
+ receivedRVV.memberDeparted(null, server3, true);
+ receivedRVV.memberDeparted(null, server4, true);
+ assertThat(receivedRVV.isDepartedMember(server3)).isTrue();
+ assertThat(receivedRVV.isDepartedMember(server4)).isTrue();
+ assertThat(receivedRVV.getMemberToVersion().size()).isEqualTo(4);
+ assertThat(receivedRVV.getMemberToGCVersion().size()).isEqualTo(3);
+
+ RegionVersionVector remoteRVV = receivedRVV.getCloneForTransmission();
+
+ operation.processReceivedRVV(remoteRVV, recoveredRVV, receivedRVV);
+ assertThat(receivedRVV.getMemberToVersion().size()).isEqualTo(3);
+ assertThat(receivedRVV.getMemberToGCVersion().size()).isEqualTo(2);
+ assertThat(recoveredRVV.getMemberToVersion().size()).isEqualTo(3);
+ assertThat(recoveredRVV.getMemberToGCVersion().size()).isEqualTo(2);
+ assertThat(remoteRVV.getMemberToVersion().size()).isEqualTo(3);
+ assertThat(remoteRVV.getMemberToGCVersion().size()).isEqualTo(2);
+
assertThat(recoveredRVV.getMemberToVersion().containsKey(server3)).isTrue();
+
assertThat(recoveredRVV.getMemberToVersion().containsKey(server4)).isFalse();
+
assertThat(recoveredRVV.getMemberToGCVersion().containsKey(server3)).isTrue();
+
assertThat(recoveredRVV.getMemberToGCVersion().containsKey(server4)).isFalse();
+ assertThat(receivedRVV.getMemberToVersion().containsKey(server3)).isTrue();
+
assertThat(receivedRVV.getMemberToVersion().containsKey(server4)).isFalse();
+
assertThat(receivedRVV.getMemberToGCVersion().containsKey(server3)).isTrue();
+
assertThat(receivedRVV.getMemberToGCVersion().containsKey(server4)).isFalse();
+ }
+
+ @Test
+ public void shouldNotRemoveDepartedMembersFromRVVForPersistentRegion() {
+ InternalDistributedMember idm = new InternalDistributedMember("host1",
101);
+ DiskStoreID server1 = new DiskStoreID(0, 0);
+ DiskStoreID server2 = new DiskStoreID(0, 1);
+ DiskStoreID server3 = new DiskStoreID(0, 2);
+ DiskStoreID server4 = new DiskStoreID(0, 3);
+
when(distributedRegion.getDataPolicy()).thenReturn(DataPolicy.PERSISTENT_REPLICATE);
+ when(distributedRegion.getVersionMember()).thenReturn(server1);
+
+ RegionEntry re1 = mock(RegionEntry.class);
+ RegionEntry re2 = mock(RegionEntry.class);
+ RegionEntry re3 = mock(RegionEntry.class);
+ ArrayList<RegionEntry> entries = new ArrayList<>();
+ entries.add(re1);
+ entries.add(re2);
+ entries.add(re3);
+ Iterator<RegionEntry> iterator = entries.iterator();
+ when(distributedRegion.getBestIterator(false)).thenReturn(iterator);
+ VersionStamp stamp1 = mock(VersionStamp.class);
+ VersionStamp stamp2 = mock(VersionStamp.class);
+ VersionStamp stamp3 = mock(VersionStamp.class);
+ when(re1.getVersionStamp()).thenReturn(stamp1);
+ when(re2.getVersionStamp()).thenReturn(stamp2);
+ when(re3.getVersionStamp()).thenReturn(stamp3);
+ when(stamp1.getMemberID()).thenReturn(server1);
+ when(stamp2.getMemberID()).thenReturn(server2);
+ when(stamp3.getMemberID()).thenReturn(server3);
+
+ RegionMap regionMap = mock(RegionMap.class);
+ InitialImageOperation operation = spy(new
InitialImageOperation(distributedRegion, regionMap));
+
+ RegionVersionVector recoveredRVV = new DiskRegionVersionVector(server1);
+ recoveredRVV.recordVersion(server1, 1);
+ recoveredRVV.recordVersion(server2, 1);
+ recoveredRVV.recordVersion(server3, 1);
+ recoveredRVV.recordVersion(server4, 1);
+ recoveredRVV.recordGCVersion(server2, 1);
+ recoveredRVV.recordGCVersion(server3, 1);
+ recoveredRVV.recordGCVersion(server4, 1);
+ recoveredRVV.memberDeparted(null, idm, true);
+ assertThat(recoveredRVV.getMemberToVersion().size()).isEqualTo(4);
+ assertThat(recoveredRVV.getMemberToGCVersion().size()).isEqualTo(3);
+
+ RegionVersionVector receivedRVV = new DiskRegionVersionVector(server2);
+ receivedRVV.recordVersion(server1, 1);
+ receivedRVV.recordVersion(server2, 1);
+ receivedRVV.recordVersion(server2, 2);
+ receivedRVV.recordVersion(server3, 1);
+ receivedRVV.recordVersion(server4, 1);
+ receivedRVV.recordGCVersion(server2, 1);
+ receivedRVV.recordGCVersion(server3, 1);
+ receivedRVV.recordGCVersion(server4, 1);
+ receivedRVV.memberDeparted(null, idm, true);
+ assertThat(receivedRVV.getMemberToVersion().size()).isEqualTo(4);
+ assertThat(receivedRVV.getMemberToGCVersion().size()).isEqualTo(3);
+
+ RegionVersionVector remoteRVV = receivedRVV.getCloneForTransmission();
+ receivedRVV = spy(receivedRVV);
+ recoveredRVV = spy(recoveredRVV);
+ remoteRVV = spy(remoteRVV);
+
+ operation.processReceivedRVV(remoteRVV, recoveredRVV, receivedRVV);
+ assertThat(receivedRVV.getMemberToVersion().size()).isEqualTo(4);
+ assertThat(receivedRVV.getMemberToGCVersion().size()).isEqualTo(3);
+ assertThat(recoveredRVV.getMemberToVersion().size()).isEqualTo(4);
+ assertThat(recoveredRVV.getMemberToGCVersion().size()).isEqualTo(3);
+ assertThat(remoteRVV.getMemberToVersion().size()).isEqualTo(4);
+ assertThat(remoteRVV.getMemberToGCVersion().size()).isEqualTo(3);
+ verify(receivedRVV, never()).removeOldMembers(any());
+ verify(recoveredRVV, never()).removeOldMembers(any());
+ verify(remoteRVV, never()).removeOldMembers(any());
+ }
}