/* * 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.util; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.util.GridConcurrentSkipListSet; import org.apache.ignite.internal.util.snaptree.SnapTreeMap; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; /** * TODO write doc */ public class GridIndexFillTest extends GridCommonAbstractTest { /** */ private CopyOnWriteArrayList<Idx> idxs; /** */ private ConcurrentHashMap<Integer, CountDownLatch> keyLocks; @Override protected void beforeTest() throws Exception { super.beforeTest(); idxs = new CopyOnWriteArrayList<>(); idxs.add(new Idx(true)); keyLocks = new ConcurrentHashMap<>(); } /** * @param k Key. */ private CountDownLatch lock(String op, Integer k) { // U.debug(op + " lock: " + k); CountDownLatch latch = new CountDownLatch(1); for(;;) { CountDownLatch l = keyLocks.putIfAbsent(k, latch); if (l == null) return latch; try { l.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } /** * @param k Key. */ private void unlock(Integer k, CountDownLatch latch) { // U.debug("unlock: " + k); assertTrue(keyLocks.remove(k, latch)); latch.countDown(); } private void put(Integer k, Long v) { CountDownLatch l = lock("add", k); for (Idx idx : idxs) idx.add(k, v); unlock(k, l); } private void remove(Integer k) { CountDownLatch l = lock("rm", k); try { Long v = null; for (Idx idx : idxs) { Long v2 = idx.remove(k, v); if (v2 == null) { assert v == null; return; // Nothing to remove. } if (v == null) v = v2; else assert v.equals(v2); } } finally { unlock(k, l); } } public void testSnaptreeParallelBuild() throws Exception { final AtomicBoolean stop = new AtomicBoolean(); IgniteInternalFuture<?> fut = multithreadedAsync(new Callable<Object>() { @Override public Object call() throws Exception { ThreadLocalRandom rnd = ThreadLocalRandom.current(); while (!stop.get()) { int k = rnd.nextInt(100); long v = rnd.nextLong(10); if (rnd.nextBoolean()) put(k, v); else remove(k); } return null; } }, 12, "put"); Thread.sleep(500); Idx newIdx = new Idx(false); idxs.add(newIdx); SnapTreeMap<Integer, Long> snap = idxs.get(0).tree.clone(); for (Map.Entry<Integer, Long> entry : snap.entrySet()) newIdx.addX(entry.getKey(), entry.getValue()); newIdx.finish(); stop.set(true); fut.get(); assertEquals(idxs.get(0).tree, idxs.get(1).tree); } private static class Idx { static int z = 1; private final SnapTreeMap<Integer, Long> tree = new SnapTreeMap<>(); //new ConcurrentSkipListMap<>(); private volatile Rm rm; private final String name = "idx" + z++; public Idx(boolean pk) { if (!pk) rm = new Rm(); } public void add(Integer k, Long v) { // U.debug(name + " add: k" + k + " " + v); Long old = tree.put(k, v); if (old != null) { Rm rm = this.rm; if (rm != null) rm.keys.add(k); } } public void addX(Integer k, Long v) { // U.debug(name + " addX: k" + k + " " + v); assert v != null; assert k != null; // Lock l = rm.lock.writeLock(); // l.lock(); try { if (!rm.keys.contains(k)) { // U.debug(name + " addX-put: k" + k + " " + v); tree.putIfAbsent(k, v); } } finally { // l.unlock(); } } public Long remove(Integer k, Long v) { Rm rm = this.rm; if (rm != null) { assert v != null; // Lock l = rm.lock.readLock(); // l.lock(); try { rm.keys.add(k); Long v2 = tree.remove(k); // U.debug(name + " rm1: k" + k + " " + v + " " + v2); } finally { // l.unlock(); } } else { Long v2 = tree.remove(k); // U.debug(name + " rm2: k" + k + " " + v + " " + v2); if (v == null) v = v2; else assertEquals(v, v2); } return v; } public void finish() { // assertTrue(rm.tree.isEmpty()); rm = null; } } private static class Rm { // private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final GridConcurrentSkipListSet<Integer> keys = new GridConcurrentSkipListSet<>(); //new SnapTreeMap<>(); //new ConcurrentSkipListMap<>(); } }