/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed 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 com.hazelcast.replicatedmap; import com.hazelcast.config.Config; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.nio.serialization.Data; import com.hazelcast.nio.serialization.DataSerializableFactory; import com.hazelcast.nio.serialization.IdentifiedDataSerializable; import com.hazelcast.replicatedmap.impl.ReplicatedMapService; import com.hazelcast.replicatedmap.impl.operation.PutOperation; import com.hazelcast.replicatedmap.impl.operation.ReplicateUpdateOperation; import com.hazelcast.replicatedmap.impl.operation.ReplicatedMapDataSerializerHook; import com.hazelcast.replicatedmap.impl.operation.VersionResponsePair; import com.hazelcast.replicatedmap.impl.record.ReplicatedRecordStore; import com.hazelcast.spi.InternalCompletableFuture; import com.hazelcast.spi.NodeEngine; import com.hazelcast.spi.exception.RetryableHazelcastException; import com.hazelcast.spi.impl.NodeEngineImpl; import com.hazelcast.test.AssertTask; import com.hazelcast.test.HazelcastSerialClassRunner; import com.hazelcast.test.HazelcastTestSupport; import com.hazelcast.test.TestHazelcastInstanceFactory; import com.hazelcast.test.annotation.SlowTest; import org.junit.After; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Random; import static com.hazelcast.util.Preconditions.isNotNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @RunWith(HazelcastSerialClassRunner.class) @Category(SlowTest.class) public class ReplicatedMapReorderedReplicationTest extends HazelcastTestSupport { // if data serializable factory of ReplicatedMapDataSerializerHook is replaced by updateFactory() // during a test, this field stores its original value to be restored on test tear down private DataSerializableFactory replicatedMapDataSerializableFactory; private Field field; @After public void tearDown() throws IllegalAccessException { // if updateFactory() has been executed, field & replicatedMapDataSerializableFactory are populated if (replicatedMapDataSerializableFactory != null && field != null) { // restore original value of ReplicatedMapDataSerializerHook.FACTORY field.set(null, replicatedMapDataSerializableFactory); } } @Test public void testNonConvergingReplicatedMaps() throws Exception { final int nodeCount = 4; final int keyCount = 10000; final int threadCount = 2; updateFactory(); TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(); final Config config = new Config(); final HazelcastInstance[] instances = factory .newInstances(config, nodeCount); warmUpPartitions(instances); final int partitionId = randomPartitionOwnedBy(instances[0]).getPartitionId(); final String mapName = randomMapName(); final NodeEngineImpl nodeEngine = getNodeEngineImpl(instances[0]); final Thread[] threads = new Thread[threadCount]; for (int i = 0; i < threadCount; i++) { final int startIndex = i; threads[i] = new Thread(new Runnable() { @Override public void run() { for (int j = startIndex; j < keyCount; j += threadCount) { put(nodeEngine, mapName, partitionId, j, j); } } }); } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { thread.join(); } final ReplicatedRecordStore[] stores = new ReplicatedRecordStore[nodeCount]; for (int i = 0; i < nodeCount; i++) { ReplicatedMapService service = getNodeEngineImpl(instances[i]).getService(ReplicatedMapService.SERVICE_NAME); service.triggerAntiEntropy(); stores[i] = service.getReplicatedRecordStore(mapName, false, partitionId); } assertTrueEventually(new AssertTask() { @Override public void run() throws Exception { long version = stores[0].getVersion(); for (ReplicatedRecordStore store : stores) { assertEquals(version, store.getVersion()); assertFalse(store.isStale(version)); } } }); for (int i = 0; i < keyCount; i++) { for (ReplicatedRecordStore store : stores) { assertTrue(store.containsKey(i)); } } } private <K, V> V put(final NodeEngine nodeEngine, final String mapName, final int partitionId, K key, V value) { isNotNull(key, "key must not be null!"); isNotNull(value, "value must not be null!"); Data dataKey = nodeEngine.toData(key); Data dataValue = nodeEngine.toData(value); PutOperation putOperation = new PutOperation(mapName, dataKey, dataValue); InternalCompletableFuture<Object> future = nodeEngine.getOperationService() .invokeOnPartition(ReplicatedMapService.SERVICE_NAME, putOperation, partitionId); VersionResponsePair result = (VersionResponsePair) future.join(); return nodeEngine.toObject(result.getResponse()); } private void updateFactory() throws NoSuchFieldException, IllegalAccessException { field = ReplicatedMapDataSerializerHook.class.getDeclaredField("FACTORY"); // remove final modifier from field Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.setAccessible(true); final DataSerializableFactory factory = (DataSerializableFactory) field.get(null); replicatedMapDataSerializableFactory = factory; field.set(null, new TestReplicatedMapDataSerializerFactory(factory)); } private static class TestReplicatedMapDataSerializerFactory implements DataSerializableFactory { private final DataSerializableFactory factory; public TestReplicatedMapDataSerializerFactory(DataSerializableFactory factory) { this.factory = factory; } @Override public IdentifiedDataSerializable create(int typeId) { if (typeId == ReplicatedMapDataSerializerHook.REPLICATE_UPDATE) { return new RetriedReplicateUpdateOperation(); } return factory.create(typeId); } } private static class RetriedReplicateUpdateOperation extends ReplicateUpdateOperation { private static final Random random = new Random(); @Override public void run() throws Exception { if (random.nextInt(10) < 2) { throw new RetryableHazelcastException(); } super.run(); } } }