/*
* 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.query.continuous;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.FactoryBuilder.SingletonFactory;
import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
import javax.cache.event.CacheEntryCreatedListener;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.CacheEntryEventFilter;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.CacheWriteSynchronizationMode;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
import org.apache.ignite.internal.util.future.IgniteFinishedFutureImpl;
import org.apache.ignite.internal.util.future.IgniteFutureImpl;
import org.apache.ignite.internal.util.typedef.PA;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteFuture;
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 static java.util.concurrent.Executors.newSingleThreadExecutor;
import static java.util.concurrent.TimeUnit.MINUTES;
import static javax.cache.configuration.FactoryBuilder.factoryOf;
/**
*
*/
@SuppressWarnings("unchecked")
public class GridCacheContinuousQueryConcurrentTest extends GridCommonAbstractTest {
/** */
private static TcpDiscoveryIpFinder ipFinder = new TcpDiscoveryVmIpFinder(true);
/** */
private static final int NODES = 2;
/** {@inheritDoc} */
@Override protected void beforeTest() throws Exception {
super.beforeTest();
startGridsMultiThreaded(NODES);
}
/** {@inheritDoc} */
@Override protected void afterTest() throws Exception {
stopAllGrids();
super.afterTest();
}
/** {@inheritDoc} */
@Override protected void afterTestsStopped() throws Exception {
stopAllGrids();
super.afterTestsStopped();
}
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
((TcpDiscoverySpi)cfg.getDiscoverySpi()).setIpFinder(ipFinder);
cfg.setPeerClassLoadingEnabled(false);
if (igniteInstanceName.endsWith(String.valueOf(NODES)))
cfg.setClientMode(ThreadLocalRandom.current().nextBoolean());
return cfg;
}
/**
* @throws Exception If failed.
*/
public void testReplicatedTx() throws Exception {
testRegistration(cacheConfiguration(CacheMode.REPLICATED, CacheAtomicityMode.TRANSACTIONAL, 1));
}
/**
* @throws Exception If failed.
*/
public void testRestartReplicated() throws Exception {
testRestartRegistration(cacheConfiguration(CacheMode.REPLICATED, CacheAtomicityMode.ATOMIC, 2));
}
/**
* @throws Exception If failed.
*/
public void testRestartPartition() throws Exception {
testRestartRegistration(cacheConfiguration(CacheMode.PARTITIONED, CacheAtomicityMode.ATOMIC, 2));
}
/**
* @throws Exception If failed.
*/
public void testRestartPartitionTx() throws Exception {
testRestartRegistration(cacheConfiguration(CacheMode.PARTITIONED, CacheAtomicityMode.TRANSACTIONAL, 2));
}
/**
* @throws Exception If failed.
*/
public void testReplicatedAtomic() throws Exception {
testRegistration(cacheConfiguration(CacheMode.REPLICATED, CacheAtomicityMode.ATOMIC, 2));
}
/**
* @throws Exception If failed.
*/
public void testPartitionTx() throws Exception {
testRegistration(cacheConfiguration(CacheMode.PARTITIONED, CacheAtomicityMode.TRANSACTIONAL, 2));
}
/**
* @throws Exception If failed.
*/
public void testPartitionAtomic() throws Exception {
testRegistration(cacheConfiguration(CacheMode.PARTITIONED, CacheAtomicityMode.ATOMIC, 2));
}
/**
* @param ccfg Cache configuration.
* @throws Exception If failed.
*/
private void testRegistration(CacheConfiguration ccfg) throws Exception {
ExecutorService execSrv = newSingleThreadExecutor();
try {
final IgniteCache<Integer, String> cache = grid(0).getOrCreateCache(ccfg);
for (int i = 0; i < 10; i++) {
log.info("Start iteration: " + i);
final int i0 = i;
final AtomicBoolean stop = new AtomicBoolean(false);
final CountDownLatch latch = new CountDownLatch(1);
final int conQryCnt = 50;
Future<List<IgniteFuture<String>>> fut = execSrv.submit(
new Callable<List<IgniteFuture<String>>>() {
@Override public List<IgniteFuture<String>> call() throws Exception {
int cnt = 0;
List<IgniteFuture<String>> futures = new ArrayList<>();
while (!stop.get()) {
futures.add(waitForKey(i0, cache, cnt));
if (log.isDebugEnabled())
log.debug("Started cont query count: " + cnt);
if (++cnt >= conQryCnt)
latch.countDown();
}
return futures;
}
});
assert U.await(latch, 1, MINUTES);
cache.put(i, "v");
stop.set(true);
List<IgniteFuture<String>> contQries = fut.get();
for (IgniteFuture<String> contQry : contQries)
contQry.get(2, TimeUnit.SECONDS);
}
}
finally {
execSrv.shutdownNow();
grid(0).destroyCache(ccfg.getName());
}
}
/**
* @param ccfg Cache configuration.
* @throws Exception If failed.
*/
private void testRestartRegistration(CacheConfiguration ccfg) throws Exception {
ExecutorService execSrv = newSingleThreadExecutor();
final AtomicBoolean stopRes = new AtomicBoolean(false);
IgniteInternalFuture<?> restartFut = null;
try {
final IgniteCache<Integer, String> cache = grid(0).getOrCreateCache(ccfg);
restartFut = GridTestUtils.runAsync(new Callable<Void>() {
@Override public Void call() throws Exception {
while (!stopRes.get()) {
startGrid(NODES);
assert GridTestUtils.waitForCondition(new PA() {
@Override public boolean apply() {
return grid(0).cluster().nodes().size() == NODES + 1;
}
}, 5000L);
Thread.sleep(300);
stopGrid(NODES);
assert GridTestUtils.waitForCondition(new PA() {
@Override public boolean apply() {
return grid(0).cluster().nodes().size() == NODES;
}
}, 5000L);
Thread.sleep(300);
}
return null;
}
});
U.sleep(100);
for (int i = 0; i < 10; i++) {
log.info("Start iteration: " + i);
final int i0 = i;
final AtomicBoolean stop = new AtomicBoolean(false);
final CountDownLatch latch = new CountDownLatch(1);
final int conQryCnt = 50;
Future<List<IgniteFuture<String>>> fut = execSrv.submit(
new Callable<List<IgniteFuture<String>>>() {
@Override public List<IgniteFuture<String>> call() throws Exception {
int cnt = 0;
List<IgniteFuture<String>> futures = new ArrayList<>();
while (!stop.get()) {
futures.add(waitForKey(i0, cache, cnt));
if (log.isDebugEnabled())
log.debug("Started cont query count: " + cnt);
if (++cnt >= conQryCnt)
latch.countDown();
}
return futures;
}
});
latch.await();
cache.put(i, "v");
assertEquals("v", cache.get(i));
stop.set(true);
List<IgniteFuture<String>> contQries = fut.get();
for (IgniteFuture<String> contQry : contQries)
contQry.get(5, TimeUnit.SECONDS);
}
}
finally {
execSrv.shutdownNow();
grid(0).destroyCache(ccfg.getName());
if (restartFut != null) {
stopRes.set(true);
restartFut.get();
stopGrid(NODES);
}
}
}
/**
* @param key Key
* @param cache Cache.
* @param id ID.
* @return Future.
*/
private IgniteFuture<String> waitForKey(Integer key, final IgniteCache<Integer, String> cache, final int id) {
String v = cache.get(key);
// From now on, all futures will be completed immediately (since the key has been
// inserted).
if (v != null)
return new IgniteFinishedFutureImpl<>("immediately");
final IgniteFuture<String> promise = new IgniteFutureImpl<>(new GridFutureAdapter<String>());
final CacheEntryListenerConfiguration<Integer, String> cfg =
createCacheListener(key, promise, id);
promise.listen(new IgniteInClosure<IgniteFuture<String>>() {
@Override public void apply(IgniteFuture<String> fut) {
GridTestUtils.runAsync(new Callable<Object>() {
@Override public Object call() throws Exception {
cache.deregisterCacheEntryListener(cfg);
return null;
}
});
}
});
// Start listening.
// Assumption: When the call returns, the listener is guaranteed to have been registered.
cache.registerCacheEntryListener(cfg);
// Now must check the cache again, to make sure that we didn't miss the key insert while we
// were busy setting up the cache listener.
// Check asynchronously.
// Complete the promise if the key was inserted concurrently.
cache.getAsync(key).listen(new IgniteInClosure<IgniteFuture<String>>() {
@Override public void apply(IgniteFuture<String> f) {
String val = f.get();
if (val != null) {
log.info("Completed by get: " + id);
(((GridFutureAdapter)((IgniteFutureImpl)promise).internalFuture())).onDone("by get");
}
}
});
return promise;
}
/**
* @param key Key.
* @param res Result.
* @param id Listener ID.
* @return Listener
*/
private CacheEntryListenerConfiguration<Integer, String> createCacheListener(
Integer key,
IgniteFuture<String> res,
int id) {
return new MutableCacheEntryListenerConfiguration<>(
factoryOf(new CacheListener(res, id)),
new SingletonFactory<>(new KeyEventFilter(key, id)), false, true);
}
/**
* @param cacheMode Cache mode.
* @param atomicMode Atomicy mode.
* @param backups Backups.
* @return Cache configuration.
*/
private CacheConfiguration<Integer, String> cacheConfiguration(CacheMode cacheMode,
CacheAtomicityMode atomicMode, int backups) {
CacheConfiguration<Integer, String> cfg = new CacheConfiguration<>("test-" + cacheMode + atomicMode + backups);
cfg.setCacheMode(cacheMode);
cfg.setAtomicityMode(atomicMode);
cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
cfg.setBackups(backups);
cfg.setReadFromBackup(false);
return cfg;
}
/**
*
*/
private static class CacheListener implements CacheEntryCreatedListener<Integer, String>, Serializable {
/** */
final IgniteFuture<String> res;
/** */
private final int id;
/**
* @param res Result.
* @param id ID.
*/
CacheListener(IgniteFuture<String> res, int id) {
this.res = res;
this.id = id;
}
/** {@inheritDoc} */
@Override public void onCreated(Iterable<CacheEntryEvent<? extends Integer, ? extends String>> evts) {
(((GridFutureAdapter)((IgniteFutureImpl)res).internalFuture())).onDone("by listener");
}
}
/**
*
*/
private static class KeyEventFilter implements CacheEntryEventFilter<Integer, String>, Serializable {
/** */
private static final long serialVersionUID = 42L;
/** */
private final Object key;
/** */
private final int id;
/**
* @param key Key.
* @param id ID.
*/
KeyEventFilter(Object key, int id) {
this.key = key;
this.id = id;
}
/** {@inheritDoc} */
@Override public boolean evaluate(CacheEntryEvent<? extends Integer, ? extends String> e) {
return e.getKey().equals(key);
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
return this == o || !(o == null || getClass() != o.getClass())
&& key.equals(((KeyEventFilter) o).key);
}
/** {@inheritDoc} */
@Override public int hashCode() {
return key.hashCode();
}
}
}