/*
* 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.igfs;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteFileSystem;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.FileSystemConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.igfs.IgfsGroupDataBlocksKeyMapper;
import org.apache.ignite.igfs.IgfsMode;
import org.apache.ignite.igfs.IgfsOutputStream;
import org.apache.ignite.igfs.IgfsPath;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.IgniteKernal;
import org.apache.ignite.internal.processors.cache.GridCacheAdapter;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
import org.apache.ignite.internal.util.lang.GridAbsPredicateX;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.G;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
import org.apache.ignite.testframework.GridTestUtils;
import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
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;
/**
* Test to check for system pool starvation due to {@link IgfsBlocksMessage}.
*/
public class IgfsBlockMessageSystemPoolStarvationSelfTest extends IgfsCommonAbstractTest {
/** First node name. */
private static final String NODE_1_NAME = "node1";
/** Second node name. */
private static final String NODE_2_NAME = "node2";
/** IGFS name. */
private static final String IGFS_NAME = "test";
/** Key in data caceh we will use to reproduce the issue. */
private static final Integer DATA_KEY = 1;
/** First node. */
private Ignite victim;
/** Second node. */
private Ignite attacker;
/** {@inheritDoc} */
@SuppressWarnings({"unchecked", "ConstantConditions"})
@Override protected void beforeTest() throws Exception {
super.beforeTest();
// Start nodes.
TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder(true);
victim = Ignition.start(config(NODE_1_NAME, ipFinder));
attacker = Ignition.start(config(NODE_2_NAME, ipFinder));
// Check if we selected victim correctly.
if (F.eq(dataCache(victim).affinity().mapKeyToNode(DATA_KEY).id(), attacker.cluster().localNode().id())) {
Ignite tmp = victim;
victim = attacker;
attacker = tmp;
}
}
/** {@inheritDoc} */
@Override protected void afterTest() throws Exception {
G.stopAll(true);
victim = null;
attacker = null;
super.afterTest();
}
/**
* Test starvation.
*
* @throws Exception If failed.
*/
@SuppressWarnings("unchecked")
public void testStarvation() throws Exception {
// 1. Create two IGFS file to make all system threads busy.
CountDownLatch fileWriteLatch = new CountDownLatch(1);
final IgniteInternalFuture fileFut1 = createFileAsync(new IgfsPath("/file1"), fileWriteLatch);
final IgniteInternalFuture fileFut2 = createFileAsync(new IgfsPath("/file2"), fileWriteLatch);
// 2. Start transaction and keep it opened.
final CountDownLatch txStartLatch = new CountDownLatch(1);
final CountDownLatch txCommitLatch = new CountDownLatch(1);
IgniteInternalFuture<Void> txFut = GridTestUtils.runAsync(new Callable<Void>() {
@Override public Void call() throws Exception {
GridCacheAdapter dataCache = dataCache(attacker);
try (GridNearTxLocal tx = dataCache.txStartEx(PESSIMISTIC, REPEATABLE_READ)) {
dataCache.put(DATA_KEY, 0);
txStartLatch.countDown();
txCommitLatch.await();
tx.commit();
}
return null;
}
});
txStartLatch.await();
// 3. Start async operation to drain semaphore permits.
final IgniteInternalFuture putFut = dataCache(victim).putAsync(DATA_KEY, 1);
assert !awaitFuture(putFut);
// 4. Write data to files and ensure we stuck.
fileWriteLatch.countDown();
assert !awaitFuture(fileFut1);
assert !awaitFuture(fileFut2);
// 5. Finish transaction.
txCommitLatch.countDown();
assert awaitFuture(txFut);
// 6. Async put must succeed.
assert awaitFuture(putFut);
// 7. Writes must succeed.
assert awaitFuture(fileFut1);
assert awaitFuture(fileFut2);
}
/**
* Await future completion.
*
* @param fut Future.
* @return {@code True} if future completed.
* @throws Exception If failed.
*/
private static boolean awaitFuture(final IgniteInternalFuture fut) throws Exception {
return GridTestUtils.waitForCondition(new GridAbsPredicateX() {
@Override public boolean applyx() throws IgniteCheckedException {
return fut.isDone();
}
}, 1000);
}
/**
* Create IGFS file asynchronously.
*
* @param path Path.
* @param writeStartLatch Write start latch.
* @return Future.
*/
private IgniteInternalFuture<Void> createFileAsync(final IgfsPath path, final CountDownLatch writeStartLatch) {
return GridTestUtils.runAsync(new Callable<Void>() {
@Override public Void call() throws Exception {
IgniteFileSystem igfs = attacker.fileSystem(IGFS_NAME);
try (IgfsOutputStream out = igfs.create(path, true)) {
writeStartLatch.await();
out.write(new byte[1024]);
out.flush();
}
return null;
}
});
}
/**
* Get data cache for node.
*
* @param node Node.
* @return Data cache.
* @throws Exception If failed.
*/
private GridCacheAdapter dataCache(Ignite node) throws Exception {
return ((IgniteKernal)node).internalCache(((IgniteKernal)node).igfsx(IGFS_NAME).configuration()
.getDataCacheConfiguration().getName());
}
/**
* Create node configuration.
*
* @param name Node name.
* @param ipFinder IpFinder.
* @return Configuration.
* @throws Exception If failed.
*/
private IgniteConfiguration config(String name, TcpDiscoveryVmIpFinder ipFinder) throws Exception {
// Data cache configuration.
CacheConfiguration dataCcfg = new CacheConfiguration(DEFAULT_CACHE_NAME);
dataCcfg.setCacheMode(CacheMode.REPLICATED);
dataCcfg.setAtomicityMode(TRANSACTIONAL);
dataCcfg.setWriteSynchronizationMode(FULL_SYNC);
dataCcfg.setAffinityMapper(new DummyAffinityMapper(1));
dataCcfg.setMaxConcurrentAsyncOperations(1);
// Meta cache configuration.
CacheConfiguration metaCcfg = new CacheConfiguration(DEFAULT_CACHE_NAME);
metaCcfg.setCacheMode(CacheMode.REPLICATED);
metaCcfg.setAtomicityMode(TRANSACTIONAL);
metaCcfg.setWriteSynchronizationMode(FULL_SYNC);
// File system configuration.
FileSystemConfiguration igfsCfg = new FileSystemConfiguration();
igfsCfg.setDefaultMode(IgfsMode.PRIMARY);
igfsCfg.setFragmentizerEnabled(false);
igfsCfg.setBlockSize(1024);
igfsCfg.setDataCacheConfiguration(dataCcfg);
igfsCfg.setMetaCacheConfiguration(metaCcfg);
igfsCfg.setName(IGFS_NAME);
// Ignite configuration.
IgniteConfiguration cfg = getConfiguration(name);
TcpDiscoverySpi discoSpi = new TcpDiscoverySpi();
discoSpi.setIpFinder(ipFinder);
cfg.setDiscoverySpi(discoSpi);
cfg.setFileSystemConfiguration(igfsCfg);
cfg.setLocalHost("127.0.0.1");
cfg.setConnectorConfiguration(null);
cfg.setStripedPoolSize(2);
cfg.setSystemThreadPoolSize(2);
cfg.setRebalanceThreadPoolSize(1);
cfg.setPublicThreadPoolSize(1);
return cfg;
}
/**
* Dimmy affinity mapper.
*/
private static class DummyAffinityMapper extends IgfsGroupDataBlocksKeyMapper {
/** */
private static final long serialVersionUID = 0L;
/** Dummy affinity key. */
private static final Integer KEY = 1;
/**
* Constructor.
*
* @param grpSize Group size.
*/
public DummyAffinityMapper(int grpSize) {
super(grpSize);
}
/** {@inheritDoc} */
@Override public Object affinityKey(Object key) {
return KEY;
}
}
}