/* * 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.datastructures; import java.util.ArrayList; import java.util.Collection; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteQueue; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CollectionConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.util.tostring.GridToStringExclude; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.lang.IgniteCallable; import org.apache.ignite.resources.IgniteInstanceResource; import org.apache.ignite.testframework.GridTestUtils; /** * Queue multi node tests. */ public abstract class GridCacheQueueMultiNodeAbstractSelfTest extends IgniteCollectionAbstractTest { /** */ private static final int GRID_CNT = 4; /** */ private static final int RETRIES = 20; /** */ protected static final int QUEUE_CAPACITY = 100_000; /** */ private static AtomicInteger cntr = new AtomicInteger(); /** */ private static final int ITEMS_CNT = 50; /** {@inheritDoc} */ @Override protected int gridCount() { return GRID_CNT; } /** {@inheritDoc} */ @SuppressWarnings("BusyWait") @Override protected void beforeTest() throws Exception { super.beforeTest(); boolean success = false; for (int i = 0; i < 5; i++) { success = true; for (Ignite g : G.allGrids()) { if (g.cluster().nodes().size() != GRID_CNT) { info("Grid has incorrect nodes count [igniteInstanceName=" + g.name() + ", nodesCnt=" + g.cluster().nodes().size() + ']'); success = false; break; } } if (!success && i != 4) Thread.sleep(1000); } assert success; info("Topology is consistent."); } /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); cfg.setPublicThreadPoolSize(RETRIES * 2); cfg.setSystemThreadPoolSize(RETRIES * 2); cfg.setConnectorConfiguration(null); return cfg; } /** * @throws Exception If failed. */ public void testPut() throws Exception { String queueName = UUID.randomUUID().toString(); IgniteQueue<Integer> queue = grid(0).queue(queueName, QUEUE_CAPACITY, config(false)); assertTrue(queue.isEmpty()); grid(0).compute().broadcast(new PutJob(queueName, RETRIES)); assertEquals(GRID_CNT * RETRIES, queue.size()); } /** * @throws Exception If failed. */ public void testPutPollCollocated() throws Exception { try { final String queueName = UUID.randomUUID().toString(); info("Queue name: " + queueName); grid(0).queue(queueName, 5, config(true)); final CountDownLatch latch = new CountDownLatch(1); final Ignite g = startGrid(GRID_CNT + 1); IgniteInternalFuture<Object> fut1 = GridTestUtils.runAsync(new Callable<Object>() { @Override public Object call() throws Exception { info(">>> Executing put callable [node=" + g.cluster().localNode().id() + ", thread=" + Thread.currentThread().getName() + ']'); IgniteQueue<Integer> q = g.queue(queueName, 5, config(true)); assertTrue(q.isEmpty()); for (int i = 0; i < ITEMS_CNT; i++) { if (i == q.capacity()) { info(">>> Opening latch..."); latch.countDown(); } put(q, i); } info(">>> Finished put callable on node: " + g.cluster().localNode().id()); return null; } }); latch.await(); final Ignite g1 = startGrid(GRID_CNT + 2); IgniteInternalFuture<Object> fut2 = GridTestUtils.runAsync(new Callable<Object>() { @SuppressWarnings("BusyWait") @Override public Object call() throws Exception { try { info(">>> Executing poll callable [node=" + g1.cluster().localNode().id() + ", thread=" + Thread.currentThread().getName() + ']'); IgniteQueue<Integer> q = g1.queue(queueName, 5, config(true)); int cnt = 0; int nullCnt = 0; do { Integer i = q.poll(); info("Polled value: " + i); if (i != null) { cnt++; nullCnt = 0; } else { if (nullCnt == 3) throw new Exception("Failed to poll non-null value within 3 attempts."); nullCnt++; Thread.sleep(1000); } } while (cnt < ITEMS_CNT); info("Finished poll callable on node: " + g1.cluster().localNode().id()); return null; } finally { info("Poll callable finished."); } } }); fut1.get(); fut2.get(); grid(0).queue(queueName, 0, null).close(); } finally { stopGrid(GRID_CNT + 1); stopGrid(GRID_CNT + 2); checkTopology(GRID_CNT); awaitPartitionMapExchange(); } } /** * @throws Exception If failed. */ @SuppressWarnings("unchecked") public void testAddAll() throws Exception { try { String queueName = UUID.randomUUID().toString(); info("Queue name: " + queueName); IgniteQueue<Integer> queue = grid(0).queue(queueName, QUEUE_CAPACITY, config(false)); assertTrue(queue.isEmpty()); grid(0).compute().call(new AddAllJob(queueName, RETRIES)); assertEquals(GRID_CNT * RETRIES, queue.size()); queue.clear(5); assertEquals(0, queue.size()); queue.close(); } catch (Throwable t) { error("Failure in test: " + t); } } /** * @throws Exception If failed. */ public void testGet() throws Exception { String queueName = UUID.randomUUID().toString(); info("Queue name: " + queueName); IgniteQueue<String> queue = grid(0).queue(queueName, QUEUE_CAPACITY, config(false)); assertTrue(queue.isEmpty()); String val = UUID.randomUUID().toString(); queue.put(val); grid(0).compute().call(new GetJob(queueName, config(false), RETRIES, val)); assertEquals(1, queue.size()); queue.close(); } /** * @throws Exception If failed. */ public void testPutTake() throws Exception { String queueName = UUID.randomUUID().toString(); info("Queue name: " + queueName); IgniteQueue<Integer> queue = grid(0).queue(queueName, QUEUE_CAPACITY, config(false)); assertTrue(queue.isEmpty()); grid(0).compute().broadcast(new PutTakeJob(queueName, RETRIES)); assertEquals(0, queue.size()); queue.close(); } /** * @throws Exception If failed. */ public void testAddMultinode() throws Exception { testAddMultinode(true); testAddMultinode(false); } /** * @param collocated Collocation flag. * @throws Exception If failed. */ private void testAddMultinode(final boolean collocated) throws Exception { final String queueName = UUID.randomUUID().toString(); info("Queue name: " + queueName + ", collocated: " + collocated); try { Collection<IgniteInternalFuture> futs = new ArrayList<>(); final int THREADS_PER_NODE = 3; final int ITEMS_PER_THREAD = 1000; for (int i = 0; i < GRID_CNT; i++) { final int idx = i; futs.add(GridTestUtils.runMultiThreadedAsync(new Callable<Void>() { @Override public Void call() throws Exception { CollectionConfiguration colCfg = config(collocated); IgniteQueue<Integer> queue = grid(idx).queue(queueName, 0, colCfg); for (int i = 0; i < ITEMS_PER_THREAD; i++) assertTrue(queue.add(i)); return null; } }, THREADS_PER_NODE, "testPutMultiNode")); } for (IgniteInternalFuture fut : futs) fut.get(); IgniteQueue<Integer> queue = grid(0).queue(queueName, 0, null); assertEquals(THREADS_PER_NODE * ITEMS_PER_THREAD * GRID_CNT, queue.size()); int[] items = new int[ITEMS_PER_THREAD]; Integer item; while ((item = queue.poll()) != null) items[item]++; for (int i = 0; i < ITEMS_PER_THREAD; i++) assertEquals(THREADS_PER_NODE * GRID_CNT, items[i]); } finally { grid(0).queue(queueName, 0, null).close(); } } /** * @throws Exception If failed. */ public void testAddPollMultinode() throws Exception { testAddPollMultinode(true); testAddPollMultinode(false); } /** * @param collocated Collocation flag. * @throws Exception If failed. */ private void testAddPollMultinode(final boolean collocated) throws Exception { final String queueName = UUID.randomUUID().toString(); info("Queue name: " + queueName + ", collocated: " + collocated); try { Collection<IgniteInternalFuture> putFuts = new ArrayList<>(); Collection<IgniteInternalFuture> pollFuts = new ArrayList<>(); final int PUT_THREADS_PER_NODE = 3; final int POLL_THREADS_PER_NODE = 2; final int ITEMS_PER_THREAD = 1000; final AtomicBoolean stopPoll = new AtomicBoolean(); Collection<int[]> pollData = new ArrayList<>(); for (int i = 0; i < GRID_CNT; i++) { final int idx = i; putFuts.add(GridTestUtils.runMultiThreadedAsync(new Callable<Void>() { @Override public Void call() throws Exception { CollectionConfiguration colCfg = config(collocated); IgniteQueue<Integer> queue = grid(idx).queue(queueName, 0, colCfg); for (int i = 0; i < ITEMS_PER_THREAD; i++) assertTrue(queue.add(i)); return null; } }, PUT_THREADS_PER_NODE, "testAddPollMultinode")); for (int j = 0; j < POLL_THREADS_PER_NODE; j++) { final int[] items = new int[ITEMS_PER_THREAD]; pollData.add(items); pollFuts.add(GridTestUtils.runAsync(new Callable<Void>() { @Override public Void call() throws Exception { CollectionConfiguration colCfg = config(collocated); IgniteQueue<Integer> queue = grid(idx).queue(queueName, 0, colCfg); while (!stopPoll.get()) { Integer val = queue.poll(); if (val != null) items[val]++; } return null; } })); } } for (IgniteInternalFuture fut : putFuts) fut.get(); stopPoll.set(true); for (IgniteInternalFuture fut : pollFuts) fut.get(); CollectionConfiguration colCfg = config(collocated); IgniteQueue<Integer> queue = grid(0).queue(queueName, 0, colCfg); int[] resItems = new int[ITEMS_PER_THREAD]; Integer item; while ((item = queue.poll()) != null) resItems[item]++; for (int[] items : pollData) { for (int i = 0; i < ITEMS_PER_THREAD; i++) resItems[i] += items[i]; } for (int i = 0; i < ITEMS_PER_THREAD; i++) assertEquals(PUT_THREADS_PER_NODE * GRID_CNT, resItems[i]); assertTrue(queue.isEmpty()); } finally { grid(0).queue(queueName, 0, null).close(); } } /** * @throws Exception If failed. */ public void testIterator() throws Exception { final String queueName = UUID.randomUUID().toString(); info("Queue name: " + queueName); try (IgniteQueue<Integer> queue = grid(0).queue(queueName, QUEUE_CAPACITY, config(false))) { assertTrue(queue.isEmpty()); grid(0).compute().broadcast(new AddAllJob(queueName, RETRIES)); assertEquals(GRID_CNT * RETRIES, queue.size()); Collection<ClusterNode> nodes = grid(0).cluster().nodes(); for (ClusterNode node : nodes) { Collection<Integer> queueElements = compute(grid(0).cluster().forNode(node)).call( new IgniteCallable<Collection<Integer>>() { @IgniteInstanceResource private Ignite grid; /** {@inheritDoc} */ @Override public Collection<Integer> call() throws Exception { Collection<Integer> values = new ArrayList<>(); grid.log().info("Running job [node=" + grid.cluster().localNode().id() + "]"); IgniteQueue<Integer> locQueue = grid.queue(queueName, 0, null); grid.log().info("Queue size " + locQueue.size()); for (Integer element : locQueue) values.add(element); grid.log().info("Returning: " + values); return values; } } ); assertTrue(F.eqOrdered(queue, queueElements)); } } } /** * @throws Exception If failed. */ public void testSerialization() throws Exception { // Random queue name. String queueName = UUID.randomUUID().toString(); final IgniteQueue<Integer> queue = grid(0).queue(queueName, 0, config(false)); assertNotNull(queue); try { for (int i = 0; i < 10; i++) queue.add(i); Collection<Integer> c = grid(0).compute().broadcast(new QueueJob(queue)); assertEquals(GRID_CNT, c.size()); for (Integer size : c) assertEquals((Integer)10, size); } finally { queue.close(); } } /** * @param q Queue. * @param v Value. */ private static <T> void put(IgniteQueue<T> q, T v) { X.println("Putting value: " + v); q.put(v); X.println("Done putting value: " + v); } /** * Tests queue serialization. */ private static class QueueJob implements IgniteCallable<Integer> { /** */ private IgniteQueue<Integer> queue; /** * @param queue Queue. */ private QueueJob(IgniteQueue<Integer> queue) { this.queue = queue; } /** {@inheritDoc} */ @Override public Integer call() throws Exception { assertNotNull(queue); return queue.size(); } } /** * Test job putting data to queue. */ protected static class PutJob implements IgniteCallable<Integer> { /** */ @GridToStringExclude @IgniteInstanceResource private Ignite ignite; /** Queue name. */ private final String queueName; /** */ private final int retries; /** * @param queueName Queue name. * @param retries Number of operations. */ PutJob(String queueName, int retries) { this.queueName = queueName; this.retries = retries; } /** {@inheritDoc} */ @Override public Integer call() throws IgniteCheckedException { assertNotNull(ignite); ignite.log().info("Running job [node=" + ignite.cluster().localNode().id() + ", job=" + this + "]"); IgniteQueue<Integer> queue = ignite.queue(queueName, 0, null); assertNotNull(queue); for (int i = 0; i < retries; i++) queue.put(cntr.getAndIncrement()); return queue.size(); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(PutJob.class, this); } } /** * Test job putting data to queue. */ protected static class AddAllJob implements IgniteCallable<Integer> { /** */ @GridToStringExclude @IgniteInstanceResource private Ignite ignite; /** Queue name. */ private final String queueName; /** */ private final int size; /** * @param queueName Queue name. * @param size Number of add items. */ AddAllJob(String queueName, int size) { this.queueName = queueName; this.size = size; } /** {@inheritDoc} */ @Override public Integer call() throws IgniteCheckedException { assertNotNull(ignite); ignite.log().info("Running job [node=" + ignite.cluster().localNode().id() + ", job=" + this + "]"); IgniteQueue<Integer> queue = ignite.queue(queueName, 0, null); assertNotNull(queue); Collection<Integer> items = new ArrayList<>(); for (int i = 0; i < size; i++) items.add(cntr.getAndIncrement()); queue.addAll(items); return queue.size(); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(AddAllJob.class, this); } } /** * Test job putting data to queue. */ protected static class GetJob implements IgniteCallable<Integer> { /** */ @GridToStringExclude @IgniteInstanceResource private Ignite ignite; /** Queue name. */ private final String queueName; /** */ private final int retries; /** */ private final String expVal; /** */ private final CollectionConfiguration colCfg; /** * @param queueName Queue name. * @param colCfg Collection configuration. * @param retries Number of operations. * @param expVal Expected value. */ GetJob(String queueName, CollectionConfiguration colCfg, int retries, String expVal) { this.queueName = queueName; this.colCfg = colCfg; this.retries = retries; this.expVal = expVal; } /** {@inheritDoc} */ @Override public Integer call() throws IgniteCheckedException { assertNotNull(ignite); ignite.log().info("Running job [node=" + ignite.cluster().localNode().id() + ", job=" + this + "]"); IgniteQueue<String> queue = ignite.queue(queueName, QUEUE_CAPACITY, colCfg); assertNotNull(queue); assertEquals(1, queue.size()); for (int i = 0; i < retries; i++) { assertEquals(expVal, queue.peek()); assertEquals(expVal, queue.element()); } return queue.size(); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(GetJob.class, this); } } /** * Test job putting and taking data to/from queue. */ protected static class PutTakeJob implements IgniteCallable<Integer> { /** */ @GridToStringExclude @IgniteInstanceResource private Ignite ignite; /** Queue name. */ private final String queueName; /** */ private final int retries; /** * @param queueName Queue name. * @param retries Number of operations. */ PutTakeJob(String queueName, int retries) { this.queueName = queueName; this.retries = retries; } /** {@inheritDoc} */ @Override public Integer call() throws IgniteCheckedException { assertNotNull(ignite); ignite.log().info("Running job [node=" + ignite.cluster().localNode().id() + ", job=" + this + ']'); IgniteQueue<Integer> queue = ignite.queue(queueName, 0, null); assertNotNull(queue); for (int i = 0; i < retries; i++) { queue.put(i); assertNotNull(queue.take()); } return queue.size(); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(PutTakeJob.class, this); } } }