/* * 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.database; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.configuration.MemoryPolicyConfiguration; import org.apache.ignite.internal.mem.unsafe.UnsafeMemoryProvider; import org.apache.ignite.internal.pagemem.PageIdAllocator; import org.apache.ignite.internal.pagemem.PageMemory; import org.apache.ignite.internal.pagemem.PageUtils; import org.apache.ignite.internal.pagemem.impl.PageMemoryNoStoreImpl; import org.apache.ignite.internal.processors.cache.CacheObject; import org.apache.ignite.internal.processors.cache.CacheObjectContext; import org.apache.ignite.internal.processors.cache.KeyCacheObject; import org.apache.ignite.internal.processors.cache.database.CacheDataRow; import org.apache.ignite.internal.processors.cache.database.MemoryMetricsImpl; import org.apache.ignite.internal.processors.cache.database.MemoryPolicy; import org.apache.ignite.internal.processors.cache.database.evict.NoOpPageEvictionTracker; import org.apache.ignite.internal.processors.cache.database.freelist.FreeList; import org.apache.ignite.internal.processors.cache.database.freelist.FreeListImpl; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; import org.apache.ignite.plugin.extensions.communication.MessageReader; import org.apache.ignite.plugin.extensions.communication.MessageWriter; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.jetbrains.annotations.Nullable; /** * */ public class FreeListImplSelfTest extends GridCommonAbstractTest { /** */ private static final int CPUS = Runtime.getRuntime().availableProcessors(); /** */ private static final long MB = 1024L * 1024L; /** */ private PageMemory pageMem; /** {@inheritDoc} */ @Override protected void afterTest() throws Exception { super.afterTest(); if (pageMem != null) pageMem.stop(); pageMem = null; } /** * @throws Exception if failed. */ public void testInsertDeleteSingleThreaded_1024() throws Exception { checkInsertDeleteSingleThreaded(1024); } /** * @throws Exception if failed. */ public void testInsertDeleteSingleThreaded_2048() throws Exception { checkInsertDeleteSingleThreaded(2048); } /** * @throws Exception if failed. */ public void testInsertDeleteSingleThreaded_4096() throws Exception { checkInsertDeleteSingleThreaded(4096); } /** * @throws Exception if failed. */ public void testInsertDeleteSingleThreaded_8192() throws Exception { checkInsertDeleteSingleThreaded(8192); } /** * @throws Exception if failed. */ public void testInsertDeleteSingleThreaded_16384() throws Exception { checkInsertDeleteSingleThreaded(16384); } /** * @throws Exception if failed. */ public void testInsertDeleteMultiThreaded_1024() throws Exception { checkInsertDeleteMultiThreaded(1024); } /** * @throws Exception if failed. */ public void testInsertDeleteMultiThreaded_2048() throws Exception { checkInsertDeleteMultiThreaded(2048); } /** * @throws Exception if failed. */ public void testInsertDeleteMultiThreaded_4096() throws Exception { checkInsertDeleteMultiThreaded(4096); } /** * @throws Exception if failed. */ public void testInsertDeleteMultiThreaded_8192() throws Exception { checkInsertDeleteMultiThreaded(8192); } /** * @throws Exception if failed. */ public void testInsertDeleteMultiThreaded_16384() throws Exception { checkInsertDeleteMultiThreaded(16384); } /** * @param pageSize Page size. * @throws Exception If failed. */ protected void checkInsertDeleteMultiThreaded(final int pageSize) throws Exception { final FreeList list = createFreeList(pageSize); Random rnd = new Random(); final ConcurrentMap<Long, TestDataRow> stored = new ConcurrentHashMap<>(); for (int i = 0; i < 100; i++) { int keySize = rnd.nextInt(pageSize * 3 / 2) + 10; int valSize = rnd.nextInt(pageSize * 5 / 2) + 10; TestDataRow row = new TestDataRow(keySize, valSize); list.insertDataRow(row); assertTrue(row.link() != 0L); TestDataRow old = stored.put(row.link(), row); assertNull(old); } final AtomicBoolean grow = new AtomicBoolean(true); GridTestUtils.runMultiThreaded(new Callable<Object>() { @Override public Object call() throws Exception { Random rnd = ThreadLocalRandom.current(); for (int i = 0; i < 200_000; i++) { boolean grow0 = grow.get(); if (grow0) { if (stored.size() > 20_000) { if (grow.compareAndSet(true, false)) info("Shrink... [" + stored.size() + ']'); grow0 = false; } } else { if (stored.size() < 1_000) { if (grow.compareAndSet(false, true)) info("Grow... [" + stored.size() + ']'); grow0 = true; } } boolean insert = rnd.nextInt(100) < 70 == grow0; if (insert) { int keySize = rnd.nextInt(pageSize * 3 / 2) + 10; int valSize = rnd.nextInt(pageSize * 3 / 2) + 10; TestDataRow row = new TestDataRow(keySize, valSize); list.insertDataRow(row); assertTrue(row.link() != 0L); TestDataRow old = stored.put(row.link(), row); assertNull(old); } else { while (true) { Iterator<TestDataRow> it = stored.values().iterator(); if (it.hasNext()) { TestDataRow row = it.next(); TestDataRow rmvd = stored.remove(row.link); if (rmvd != null) { list.removeDataRowByLink(row.link); break; } } } } } return null; } }, 8, "runner"); } /** * @throws Exception if failed. */ protected void checkInsertDeleteSingleThreaded(int pageSize) throws Exception { FreeList list = createFreeList(pageSize); Random rnd = new Random(); Map<Long, TestDataRow> stored = new HashMap<>(); for (int i = 0; i < 100; i++) { int keySize = rnd.nextInt(pageSize * 3 / 2) + 10; int valSize = rnd.nextInt(pageSize * 5 / 2) + 10; TestDataRow row = new TestDataRow(keySize, valSize); list.insertDataRow(row); assertTrue(row.link() != 0L); TestDataRow old = stored.put(row.link(), row); assertNull(old); } boolean grow = true; for (int i = 0; i < 1_000_000; i++) { if (grow) { if (stored.size() > 20_000) { grow = false; info("Shrink... [" + stored.size() + ']'); } } else { if (stored.size() < 1_000) { grow = true; info("Grow... [" + stored.size() + ']'); } } boolean insert = rnd.nextInt(100) < 70 == grow; if (insert) { int keySize = rnd.nextInt(pageSize * 3 / 2) + 10; int valSize = rnd.nextInt(pageSize * 3 / 2) + 10; TestDataRow row = new TestDataRow(keySize, valSize); list.insertDataRow(row); assertTrue(row.link() != 0L); TestDataRow old = stored.put(row.link(), row); assertNull(old); } else { Iterator<TestDataRow> it = stored.values().iterator(); if (it.hasNext()) { TestDataRow row = it.next(); TestDataRow rmvd = stored.remove(row.link); assertTrue(rmvd == row); list.removeDataRowByLink(row.link); } } } } /** * @return Page memory. */ protected PageMemory createPageMemory(int pageSize, MemoryPolicyConfiguration plcCfg) throws Exception { PageMemory pageMem = new PageMemoryNoStoreImpl(log, new UnsafeMemoryProvider(log), null, pageSize, plcCfg, new MemoryMetricsImpl(plcCfg), true); pageMem.start(); return pageMem; } /** * @param pageSize Page size. * @return Free list. * @throws Exception If failed. */ protected FreeList createFreeList(int pageSize) throws Exception { MemoryPolicyConfiguration plcCfg = new MemoryPolicyConfiguration().setMaxSize(1024 * MB); pageMem = createPageMemory(pageSize, plcCfg); long metaPageId = pageMem.allocatePage(1, 1, PageIdAllocator.FLAG_DATA); MemoryMetricsImpl metrics = new MemoryMetricsImpl(plcCfg); MemoryPolicy memPlc = new MemoryPolicy(pageMem, plcCfg, metrics, new NoOpPageEvictionTracker()); return new FreeListImpl(1, "freelist", metrics, memPlc, null, null, metaPageId, true); } /** * */ private static class TestDataRow implements CacheDataRow { /** */ private long link; /** */ private TestCacheObject key; /** */ private TestCacheObject val; /** */ private GridCacheVersion ver; /** * @param keySize Key size. * @param valSize Value size. */ private TestDataRow(int keySize, int valSize) { key = new TestCacheObject(keySize); val = new TestCacheObject(valSize); ver = new GridCacheVersion(keySize, valSize, 1); } /** {@inheritDoc} */ @Override public KeyCacheObject key() { return key; } /** {@inheritDoc} */ @Override public void key(KeyCacheObject key) { this.key = (TestCacheObject)key; } /** {@inheritDoc} */ @Override public CacheObject value() { return val; } /** {@inheritDoc} */ @Override public GridCacheVersion version() { return ver; } /** {@inheritDoc} */ @Override public long expireTime() { return 0; } /** {@inheritDoc} */ @Override public int partition() { return 0; } /** {@inheritDoc} */ @Override public long link() { return link; } /** {@inheritDoc} */ @Override public void link(long link) { this.link = link; } /** {@inheritDoc} */ @Override public int hash() { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public int cacheId() { return 0; } } /** * */ private static class TestCacheObject implements KeyCacheObject { /** */ private byte[] data; /** * @param size Object size. */ private TestCacheObject(int size) { data = new byte[size]; Arrays.fill(data, (byte)size); } /** {@inheritDoc} */ @Override public boolean internal() { return false; } /** {@inheritDoc} */ @Override public int partition() { return 0; } /** {@inheritDoc} */ @Override public void partition(int part) { assert false; } /** {@inheritDoc} */ @Override public KeyCacheObject copy(int part) { assert false; return null; } /** {@inheritDoc} */ @Nullable @Override public <T> T value(CacheObjectContext ctx, boolean cpy) { return (T)data; } /** {@inheritDoc} */ @Override public byte[] valueBytes(CacheObjectContext ctx) throws IgniteCheckedException { return data; } /** {@inheritDoc} */ @Override public int valueBytesLength(CacheObjectContext ctx) throws IgniteCheckedException { return data.length; } /** {@inheritDoc} */ @Override public boolean putValue(ByteBuffer buf) throws IgniteCheckedException { buf.put(data); return true; } /** {@inheritDoc} */ @Override public int putValue(long addr) throws IgniteCheckedException { PageUtils.putBytes(addr, 0, data); return data.length; } /** {@inheritDoc} */ @Override public boolean putValue(ByteBuffer buf, int off, int len) throws IgniteCheckedException { buf.put(data, off, len); return true; } /** {@inheritDoc} */ @Override public byte cacheObjectType() { return 42; } /** {@inheritDoc} */ @Override public boolean isPlatformType() { return false; } /** {@inheritDoc} */ @Override public CacheObject prepareForCache(CacheObjectContext ctx) { assert false; return this; } /** {@inheritDoc} */ @Override public void finishUnmarshal(CacheObjectContext ctx, ClassLoader ldr) throws IgniteCheckedException { assert false; } /** {@inheritDoc} */ @Override public void prepareMarshal(CacheObjectContext ctx) throws IgniteCheckedException { assert false; } /** {@inheritDoc} */ @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) { assert false; return false; } /** {@inheritDoc} */ @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) { assert false; return false; } /** {@inheritDoc} */ @Override public short directType() { assert false; return 0; } /** {@inheritDoc} */ @Override public byte fieldsCount() { assert false; return 0; } /** {@inheritDoc} */ @Override public void onAckReceived() { assert false; } } }