/* * 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.ignite.internal.processors.cache; import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import javax.cache.event.CacheEntryEvent; import javax.cache.event.CacheEntryUpdatedListener; import javax.cache.expiry.CreatedExpiryPolicy; import javax.cache.expiry.Duration; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteTransactions; import org.apache.ignite.cache.CacheAtomicityMode; import org.apache.ignite.cache.query.ContinuousQuery; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.PA; import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteInClosure; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.apache.ignite.transactions.Transaction; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC; import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL; import static org.apache.ignite.cache.CacheMode.PARTITIONED; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC; import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ; /** * */ public class IgniteCacheExpireAndUpdateConsistencyTest extends GridCommonAbstractTest { /** */ private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true); /** */ private boolean client; /** */ private static final int NODES = 5; /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); ((TcpDiscoverySpi)cfg.getDiscoverySpi()).setIpFinder(IP_FINDER); cfg.setClientMode(client); return cfg; } /** {@inheritDoc} */ @Override protected void beforeTestsStarted() throws Exception { super.beforeTestsStarted(); startGridsMultiThreaded(4); client = true; Ignite client = startGrid(4); assertTrue(client.configuration().isClientMode()); } /** {@inheritDoc} */ @Override protected void afterTestsStopped() throws Exception { stopAllGrids(); super.afterTestsStopped(); } /** * @throws Exception If failed. */ public void testAtomic1() throws Exception { updateAndEventConsistencyTest(cacheConfiguration(ATOMIC, 0)); } /** * @throws Exception If failed. */ public void testAtomic2() throws Exception { updateAndEventConsistencyTest(cacheConfiguration(ATOMIC, 1)); } /** * @throws Exception If failed. */ public void testAtomic3() throws Exception { updateAndEventConsistencyTest(cacheConfiguration(ATOMIC, 2)); } /** * @throws Exception If failed. */ public void testTx1() throws Exception { updateAndEventConsistencyTest(cacheConfiguration(TRANSACTIONAL, 0)); } /** * @throws Exception If failed. */ public void testTx2() throws Exception { updateAndEventConsistencyTest(cacheConfiguration(TRANSACTIONAL, 1)); } /** * @throws Exception If failed. */ public void testTx3() throws Exception { updateAndEventConsistencyTest(cacheConfiguration(TRANSACTIONAL, 2)); } /** * @param ccfg Cache configuration. * @throws Exception If failed. */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") private void updateAndEventConsistencyTest(CacheConfiguration<TestKey, TestValue> ccfg) throws Exception { ignite(0).createCache(ccfg); try { List<ConcurrentMap<TestKey, List<T2<TestValue, TestValue>>>> nodesEvts = new ArrayList<>(); for (int i = 0; i < NODES; i++) { Ignite ignite = ignite(i); IgniteCache<TestKey, TestValue> cache = ignite.cache(ccfg.getName()); ContinuousQuery<TestKey, TestValue> qry = new ContinuousQuery<>(); final ConcurrentMap<TestKey, List<T2<TestValue, TestValue>>> allEvts = new ConcurrentHashMap<>(); qry.setLocalListener(new CacheEntryUpdatedListener<TestKey, TestValue>() { @Override public void onUpdated(Iterable<CacheEntryEvent<? extends TestKey, ? extends TestValue>> evts) { for (CacheEntryEvent<? extends TestKey, ? extends TestValue> e : evts) { List<T2<TestValue, TestValue>> keyEvts = allEvts.get(e.getKey()); if (keyEvts == null) { List<T2<TestValue, TestValue>> old = allEvts.putIfAbsent(e.getKey(), keyEvts = new ArrayList<>()); assertNull(old); } synchronized (keyEvts) { keyEvts.add(new T2<TestValue, TestValue>(e.getValue(), e.getOldValue())); } } } }); cache.query(qry); nodesEvts.add(allEvts); } final AtomicInteger keyVal = new AtomicInteger(); for (int i = 0; i < NODES; i++) { Ignite ignite = ignite(i); log.info("Test with node: " + ignite.name()); updateAndEventConsistencyTest(ignite, ccfg.getName(), keyVal, nodesEvts, false); if (ccfg.getAtomicityMode() == TRANSACTIONAL) updateAndEventConsistencyTest(ignite, ccfg.getName(), keyVal, nodesEvts, true); } } finally { ignite(0).destroyCache(ccfg.getName()); } } /** * @param node Node. * @param cacheName Cache name. * @param keyVal Key counter. * @param nodesEvts Events map. * @param useTx If {@code true} executes update with explicit transaction. * @throws Exception If failed. */ private void updateAndEventConsistencyTest(final Ignite node, String cacheName, final AtomicInteger keyVal, List<ConcurrentMap<TestKey, List<T2<TestValue, TestValue>>>> nodesEvts, final boolean useTx) throws Exception { final ConcurrentMap<TestKey, List<T2<TestValue, TestValue>>> updates = new ConcurrentHashMap<>(); final int THREADS = 5; final int KEYS_PER_THREAD = 100; final IgniteCache<TestKey, TestValue> cache = node.cache(cacheName); final IgniteCache<TestKey, TestValue> expPlcCache = cache.withExpiryPolicy(new CreatedExpiryPolicy(new Duration(SECONDS, 2))); GridTestUtils.runMultiThreaded(new IgniteInClosure<Integer>() { @Override public void apply(Integer idx) { List<TestKey> keys = new ArrayList<>(); for (int i = 0; i < KEYS_PER_THREAD; i++) keys.add(new TestKey(keyVal.incrementAndGet())); for (TestKey key : keys) { expPlcCache.put(key, new TestValue(0)); List<T2<TestValue, TestValue>> keyUpdates = new ArrayList<>(); keyUpdates.add(new T2<>(new TestValue(0), (TestValue)null)); updates.put(key, keyUpdates); } long stopTime = U.currentTimeMillis() + 10_000; int val = 0; Set<TestKey> expired = new HashSet<>(); IgniteTransactions txs = node.transactions(); while (U.currentTimeMillis() < stopTime) { val++; TestValue newVal = new TestValue(val); for (TestKey key : keys) { Transaction tx = useTx ? txs.txStart(PESSIMISTIC, REPEATABLE_READ) : null; TestValue oldVal = cache.getAndPut(key, newVal); if (tx != null) tx.commit(); List<T2<TestValue, TestValue>> keyUpdates = updates.get(key); keyUpdates.add(new T2<>(newVal, oldVal)); if (oldVal == null) expired.add(key); } if (expired.size() == keys.size()) break; } assertEquals(keys.size(), expired.size()); } }, THREADS, "update-thread"); for (ConcurrentMap<TestKey, List<T2<TestValue, TestValue>>> evts : nodesEvts) checkEvents(updates, evts); nodesEvts.clear(); } /** * @param updates Cache update. * @param evts Received events. * @throws Exception If failed. */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") private void checkEvents(ConcurrentMap<TestKey, List<T2<TestValue, TestValue>>> updates, final ConcurrentMap<TestKey, List<T2<TestValue, TestValue>>> evts) throws Exception { for (final TestKey key : updates.keySet()) { final List<T2<TestValue, TestValue>> keyUpdates = updates.get(key); assert(!F.isEmpty(keyUpdates)); GridTestUtils.waitForCondition(new PA() { @Override public boolean apply() { List<T2<TestValue, TestValue>> keyEvts = evts.get(key); if (keyEvts == null) return false; synchronized (keyEvts) { return keyEvts.size() == keyUpdates.size(); } } }, 5000); List<T2<TestValue, TestValue>> keyEvts = evts.get(key); assertNotNull(keyEvts); for (int i = 0; i < keyUpdates.size(); i++) { T2<TestValue, TestValue> update = keyUpdates.get(i); T2<TestValue, TestValue> evt = keyEvts.get(i); assertEquals(update.get1(), evt.get1()); assertEquals(update.get2(), evt.get2()); } } } /** * @param atomicityMode Cache atomicity mode. * @param backups Number of backups. * @return Cache configuration. */ private CacheConfiguration<TestKey, TestValue> cacheConfiguration(CacheAtomicityMode atomicityMode, int backups) { CacheConfiguration<TestKey, TestValue> ccfg = new CacheConfiguration<>(DEFAULT_CACHE_NAME); ccfg.setCacheMode(PARTITIONED); ccfg.setAtomicityMode(atomicityMode); ccfg.setWriteSynchronizationMode(FULL_SYNC); ccfg.setBackups(backups); return ccfg; } /** * */ static class TestKey implements Serializable { /** */ private int key; /** * @param key Key. */ public TestKey(int key) { this.key = key; } /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TestKey testKey = (TestKey)o; return key == testKey.key; } /** {@inheritDoc} */ @Override public int hashCode() { return key; } /** {@inheritDoc} */ @Override public String toString() { return S.toString(TestKey.class, this); } } /** * */ static class TestValue implements Serializable { /** */ private int val; /** * @param val Value. */ public TestValue(int val) { this.val = val; } /** {@inheritDoc} */ @Override public String toString() { return S.toString(TestValue.class, this); } /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TestValue testVal = (TestValue)o; return val == testVal.val; } /** {@inheritDoc} */ @Override public int hashCode() { return val; } } }