/* * 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.cassandra.gms; import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.dht.Token; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class EndpointStateTest { public volatile VersionedValue.VersionedValueFactory valueFactory = new VersionedValue.VersionedValueFactory(DatabaseDescriptor.getPartitioner()); @Test public void testMultiThreadedReadConsistency() throws InterruptedException { for (int i = 0; i < 500; i++) innerTestMultiThreadedReadConsistency(); } /** * Test that a thread reading values whilst they are updated by another thread will * not see an entry unless it sees the entry previously added as well, even though * we are accessing the map via an iterator backed by the underlying map. This * works because EndpointState copies the map each time values are added. */ private void innerTestMultiThreadedReadConsistency() throws InterruptedException { final Token token = DatabaseDescriptor.getPartitioner().getRandomToken(); final List<Token> tokens = Collections.singletonList(token); final HeartBeatState hb = new HeartBeatState(0); final EndpointState state = new EndpointState(hb); final AtomicInteger numFailures = new AtomicInteger(); Thread t1 = new Thread(new Runnable() { public void run() { state.addApplicationState(ApplicationState.TOKENS, valueFactory.tokens(tokens)); state.addApplicationState(ApplicationState.STATUS, valueFactory.normal(tokens)); } }); Thread t2 = new Thread(new Runnable() { public void run() { for (int i = 0; i < 50; i++) { Map<ApplicationState, VersionedValue> values = new EnumMap<>(ApplicationState.class); for (Map.Entry<ApplicationState, VersionedValue> entry : state.states()) values.put(entry.getKey(), entry.getValue()); if (values.containsKey(ApplicationState.STATUS) && !values.containsKey(ApplicationState.TOKENS)) { numFailures.incrementAndGet(); System.out.println(String.format("Failed: %s", values)); } } } }); t1.start(); t2.start(); t1.join(); t2.join(); assertTrue(numFailures.get() == 0); } @Test public void testMultiThreadWriteConsistency() throws InterruptedException { for (int i = 0; i < 500; i++) innerTestMultiThreadWriteConsistency(); } /** * Test that two threads can update the state map concurrently. */ private void innerTestMultiThreadWriteConsistency() throws InterruptedException { final Token token = DatabaseDescriptor.getPartitioner().getRandomToken(); final List<Token> tokens = Collections.singletonList(token); final String ip = "127.0.0.1"; final UUID hostId = UUID.randomUUID(); final HeartBeatState hb = new HeartBeatState(0); final EndpointState state = new EndpointState(hb); Thread t1 = new Thread(new Runnable() { public void run() { Map<ApplicationState, VersionedValue> states = new EnumMap<>(ApplicationState.class); states.put(ApplicationState.TOKENS, valueFactory.tokens(tokens)); states.put(ApplicationState.STATUS, valueFactory.normal(tokens)); state.addApplicationStates(states); } }); Thread t2 = new Thread(new Runnable() { public void run() { Map<ApplicationState, VersionedValue> states = new EnumMap<>(ApplicationState.class); states.put(ApplicationState.INTERNAL_IP, valueFactory.internalIP(ip)); states.put(ApplicationState.HOST_ID, valueFactory.hostId(hostId)); state.addApplicationStates(states); } }); t1.start(); t2.start(); t1.join(); t2.join(); Set<Map.Entry<ApplicationState, VersionedValue>> states = state.states(); assertEquals(4, states.size()); Map<ApplicationState, VersionedValue> values = new EnumMap<>(ApplicationState.class); for (Map.Entry<ApplicationState, VersionedValue> entry : states) values.put(entry.getKey(), entry.getValue()); assertTrue(values.containsKey(ApplicationState.STATUS)); assertTrue(values.containsKey(ApplicationState.TOKENS)); assertTrue(values.containsKey(ApplicationState.INTERNAL_IP)); assertTrue(values.containsKey(ApplicationState.HOST_ID)); } }