/*
* 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.spi.communication.tcp;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.managers.communication.GridIoMessage;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearUnlockRequest;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteRunnable;
import org.apache.ignite.marshaller.Marshaller;
import org.apache.ignite.marshaller.jdk.JdkMarshaller;
import org.apache.ignite.plugin.extensions.communication.Message;
import org.apache.ignite.spi.communication.CommunicationSpi;
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.GridTestThread;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.cache.CacheRebalanceMode.SYNC;
import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_ASYNC;
/**
* Special cases for GG-2329.
*/
public class GridCacheDhtLockBackupSelfTest extends GridCommonAbstractTest {
/** Ip-finder. */
private static TcpDiscoveryIpFinder ipFinder = new TcpDiscoveryVmIpFinder(true);
/** Communication spi for grid start. */
private CommunicationSpi commSpi;
/** Marshaller used in test. */
private Marshaller marsh = new JdkMarshaller();
/**
*
*/
public GridCacheDhtLockBackupSelfTest() {
super(false /*start grid. */);
}
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
TcpDiscoverySpi disco = new TcpDiscoverySpi();
disco.setIpFinder(ipFinder);
cfg.setDiscoverySpi(disco);
cfg.setCacheConfiguration(cacheConfiguration());
cfg.setMarshaller(marsh);
assert commSpi != null;
cfg.setCommunicationSpi(commSpi);
return cfg;
}
/**
* @return Cache configuration.
*/
protected CacheConfiguration cacheConfiguration() {
CacheConfiguration cacheCfg = defaultCacheConfiguration();
cacheCfg.setCacheMode(CacheMode.PARTITIONED);
cacheCfg.setWriteSynchronizationMode(FULL_ASYNC);
cacheCfg.setRebalanceMode(SYNC);
return cacheCfg;
}
/**
* @throws Exception If test failed.
*/
@SuppressWarnings({"TooBroadScope"})
public void testLock() throws Exception {
final int kv = 1;
Ignite ignite1 = startGridWithSpi(1, new TestCommunicationSpi(GridNearUnlockRequest.class, 1000));
Ignite ignite2 = startGridWithSpi(2, new TestCommunicationSpi(GridNearUnlockRequest.class, 1000));
if (!ignite1.affinity(DEFAULT_CACHE_NAME).mapKeyToNode(kv).id().equals(ignite1.cluster().localNode().id())) {
Ignite tmp = ignite1;
ignite1 = ignite2;
ignite2 = tmp;
}
// Now, grid1 is always primary node for key 1.
final IgniteCache<Integer, String> cache1 = ignite1.cache(DEFAULT_CACHE_NAME);
final IgniteCache<Integer, String> cache2 = ignite2.cache(DEFAULT_CACHE_NAME);
info(">>> Primary: " + ignite1.cluster().localNode().id());
info(">>> Backup: " + ignite2.cluster().localNode().id());
final CountDownLatch l1 = new CountDownLatch(1);
Thread t1 = new GridTestThread(new Callable<Object>() {
@Nullable @Override public Object call() throws Exception {
info("Before lock for key: " + kv);
Lock lock = cache1.lock(kv);
lock.lock();
info("After lock for key: " + kv);
try {
assert cache1.isLocalLocked(kv, false);
assert cache1.isLocalLocked(kv, true);
l1.countDown();
info("Let thread2 proceed.");
cache1.put(kv, Integer.toString(kv));
info("Put " + kv + '=' + Integer.toString(kv) + " key pair into cache.");
}
finally {
Thread.sleep(1000);
lock.unlock();
info("Unlocked key in thread 1: " + kv);
}
assert !cache1.isLocalLocked(kv, true);
return null;
}
});
Thread t2 = new GridTestThread(new Callable<Object>() {
@Nullable @Override public Object call() throws Exception {
info("Waiting for latch1...");
l1.await();
Lock lock = cache2.lock(kv);
lock.lock();
try {
String v = cache2.get(kv);
assert v != null : "Value is null for key: " + kv;
assertEquals(Integer.toString(kv), v);
}
finally {
lock.unlock();
info("Unlocked key in thread 2: " + kv);
}
assert !cache2.isLocalLocked(kv, true);
return null;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
info("Before remove all");
cache1.removeAll();
info("Remove all completed");
if (cache2.size() > 0) {
String failMsg = cache2.toString();
long start = System.currentTimeMillis();
while (cache2.size() > 0)
U.sleep(100);
long clearDuration = System.currentTimeMillis() - start;
assertTrue("Cache on backup is not empty (was cleared in " + clearDuration + "ms): " + failMsg,
clearDuration < 3000);
}
}
/**
* Starts grid with given communication spi set in configuration.
*
* @param idx Grid index.
* @param commSpi Communication spi.
* @return Started grid.
* @throws Exception If grid start failed.
*/
private Ignite startGridWithSpi(int idx, CommunicationSpi commSpi) throws Exception {
this.commSpi = commSpi;
try {
return startGrid(idx);
}
finally {
this.commSpi = null;
}
}
/**
* Test communication spi that delays message sending.
*/
private class TestCommunicationSpi extends TcpCommunicationSpi {
/** Class of delayed messages. */
private Class<?> delayedMsgCls;
/** */
private int delayTime;
/**
* Creates test communication spi.
*
* @param delayedMsgCls Messages of this class will be delayed.
* @param delayTime Time to be delayed.
*/
private TestCommunicationSpi(Class delayedMsgCls, int delayTime) {
this.delayedMsgCls = delayedMsgCls;
this.delayTime = delayTime;
}
/**
* Checks message and awaits when message is allowed to be sent if it is a checked message.
*
* @param obj Message being sent.
* @param srcNodeId Sender node id.
*/
private void checkAwaitMessageType(Message obj, UUID srcNodeId) {
try {
GridIoMessage plainMsg = (GridIoMessage)obj;
Object msg = plainMsg.message();
if (delayedMsgCls.isAssignableFrom(msg.getClass())) {
info(getSpiContext().localNode().id() + " received message from " + srcNodeId);
U.sleep(delayTime);
}
}
catch (IgniteCheckedException e) {
U.error(log, "Cannot process incoming message", e);
}
}
/** {@inheritDoc} */
@Override protected void notifyListener(UUID sndId, Message msg,
IgniteRunnable msgC) {
checkAwaitMessageType(msg, sndId);
super.notifyListener(sndId, msg, msgC);
}
}
}