From: <tho...@us...> - 2011-02-08 14:57:39
|
Revision: 4182 http://bigdata.svn.sourceforge.net/bigdata/?rev=4182&view=rev Author: thompsonbry Date: 2011-02-08 14:57:32 +0000 (Tue, 08 Feb 2011) Log Message: ----------- BT/MC lint dialog Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/AllocationContext.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/IMemoryManager.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/ISectorManager.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/MemoryManager.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/SectorAllocator.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/sector/TestMemoryManager.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/MemoryManagerResourceError.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/AllocationContext.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/AllocationContext.java 2011-02-07 20:30:43 UTC (rev 4181) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/AllocationContext.java 2011-02-08 14:57:32 UTC (rev 4182) @@ -28,40 +28,49 @@ import java.util.HashSet; /** - * The AllocationContext is used to maintain a handle on allocations made - * within some specific environment (context). + * The {@link AllocationContext} is used to maintain a handle on allocations + * made within some specific environment (context). * * In this way, clearing a context will return all allocations to the more * general pool. * - * There are two obvious implementaiton strategies: - * 1) Retaining set of addresses allocated - * 2) Retaining a copy of the allocated bits + * There are two obvious implementation strategies: + * <ol> + * <li>Retaining set of addresses allocated</li> + * <li>Retaining a copy of the allocated bits</li> + * </ol> * - * If it was not for the BLOB implementations which require length - * data to manage the freeing of an allocation, it would be efficient to - * maintain copies of the allocation bits. This remoains an option for the - * future but requires a relatively complex callback protocol. + * If it was not for the BLOB implementations which require length data to + * manage the freeing of an allocation, it would be efficient to maintain copies + * of the allocation bits. This remains an option for the future but requires a + * relatively complex callback protocol. * * For this reason, the initial implementation maintains a set of allocated * addresses. * * @author Martyn Cutcher - * */ public class AllocationContext implements IMemoryManager { - final IMemoryManager m_parent; + private final IMemoryManager m_parent; - SectorAllocation m_head = null; + private HashSet<Long> m_addresses = new HashSet<Long>(); - HashSet<Long> m_addresses = new HashSet<Long>(); - - public AllocationContext(IMemoryManager parent) { + public AllocationContext(final IMemoryManager parent) { + + if(parent == null) + throw new IllegalArgumentException(); + m_parent = parent; + } + synchronized public long allocate(final ByteBuffer data) { + + if (data == null) + throw new IllegalArgumentException(); + final long addr = m_parent.allocate(data); // getSectorAllocation(addr).allocate(addr); @@ -70,10 +79,7 @@ return addr; } - /** - * The main reason for the AllocationContext is to be - * able to atomically release the associated allocations - */ + synchronized public void clear() { for (Long addr : m_addresses) { m_parent.free(addr); @@ -82,6 +88,7 @@ m_addresses.clear(); } + synchronized public void free(final long addr) { // getSectorAllocation(addr).free(addr); m_addresses.remove(Long.valueOf(addr)); @@ -89,62 +96,80 @@ m_parent.free(addr); } - public ByteBuffer[] get(long addr) { + synchronized + public ByteBuffer[] get(final long addr) { return m_parent.get(addr); } - public AllocationContext createAllocationContext() { + public IMemoryManager createAllocationContext() { return new AllocationContext(this); } - - private int segmentID(final long addr) { - final int rwaddr = MemoryManager.getAllocationAddress(addr); - - return SectorAllocator.getSectorIndex(rwaddr); - } - - private int segmentOffset(final long addr) { - final int rwaddr = MemoryManager.getAllocationAddress(addr); - - return SectorAllocator.getSectorOffset(rwaddr); - } - - SectorAllocation getSectorAllocation(final long addr) { - final int index = segmentID(addr); - if (m_head == null) { - m_head = new SectorAllocation(index); - } - SectorAllocation sa = m_head; - while (sa.m_index != index) { - if (sa.m_next == null) { - sa.m_next = new SectorAllocation(index); - } - sa = sa.m_next; - } - - return sa; - } - - class SectorAllocation { - final int m_index; - final int[] m_bits = new int[SectorAllocator.NUM_ENTRIES]; - SectorAllocation m_next = null; - - SectorAllocation(final int index) { - m_index = index; - } - public void allocate(long addr) { - assert !SectorAllocator.tstBit(m_bits, segmentOffset(addr)); - - SectorAllocator.setBit(m_bits, segmentOffset(addr)); - } +// private SectorAllocation m_head = null; +// +// /** +// * Return the index of {@link SectorAllocator} for the given address. +// * +// * @param addr +// * The given address. +// * @return The index of the {@link SectorAllocator} containing that address. +// */ +// private int segmentID(final long addr) { +// final int rwaddr = MemoryManager.getAllocationAddress(addr); +// +// return SectorAllocator.getSectorIndex(rwaddr); +// } +// +// /** +// * Return the bit offset into the bit map of {@link SectorAllocator} for the +// * given address. +// * +// * @param addr +// * The given address. +// * @return +// */ +// private int segmentOffset(final long addr) { +// final int rwaddr = MemoryManager.getAllocationAddress(addr); +// +// return SectorAllocator.getSectorOffset(rwaddr); +// } +// +// SectorAllocation getSectorAllocation(final long addr) { +// final int index = segmentID(addr); +// if (m_head == null) { +// m_head = new SectorAllocation(index); +// } +// SectorAllocation sa = m_head; +// while (sa.m_index != index) { +// if (sa.m_next == null) { +// sa.m_next = new SectorAllocation(index); +// } +// sa = sa.m_next; +// } +// +// return sa; +// } +// +// class SectorAllocation { +// final int m_index; +// final int[] m_bits = new int[SectorAllocator.NUM_ENTRIES]; +// SectorAllocation m_next = null; +// +// SectorAllocation(final int index) { +// m_index = index; +// } +// +// public void allocate(long addr) { +// assert !SectorAllocator.tstBit(m_bits, segmentOffset(addr)); +// +// SectorAllocator.setBit(m_bits, segmentOffset(addr)); +// } +// +// public void free(long addr) { +// assert SectorAllocator.tstBit(m_bits, segmentOffset(addr)); +// +// SectorAllocator.clrBit(m_bits, segmentOffset(addr)); +// } +// } - public void free(long addr) { - assert SectorAllocator.tstBit(m_bits, segmentOffset(addr)); - - SectorAllocator.clrBit(m_bits, segmentOffset(addr)); - } - } - } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/IMemoryManager.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/IMemoryManager.java 2011-02-07 20:30:43 UTC (rev 4181) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/IMemoryManager.java 2011-02-08 14:57:32 UTC (rev 4182) @@ -26,18 +26,33 @@ import java.nio.ByteBuffer; +/** + * Abstraction for managing data in {@link ByteBuffer}s. Typically those buffers + * will be allocated on the native process heap. + * + * @author martyncutcher + */ public interface IMemoryManager { + /** * Allocates space on the backing resource and copies the provided data. * - * @param data - will be copied to the backing resource + * @param data + * The data will be copied to the backing resource + * * @return the address to be passed to the get method to retrieve the data + * + * @throws IllegalArgumentException + * if the argument is <code>null</code>. + * + * FIXME Replace with allocate(int nbytes):ByteBuffer[] so the + * caller can do zero copy NIO. */ public long allocate(ByteBuffer data); - + /** - * The ByteBuffer[] return enables the handling of blobs that span more - * than a single slot, without the need to create an intermediate ByteBuffer. + * The ByteBuffer[] return enables the handling of blobs that span more than + * a single slot, without the need to create an intermediate ByteBuffer. * * This will support transfers directly to other direct ByteBuffers, for * example for network IO. @@ -45,13 +60,18 @@ * Using ByteBuffer:put the returned array can be efficiently copied to * another ByteBuffer: * + * <pre> * ByteBuffer mybb; * ByteBuffer[] bufs = get(addr); * for (ByteBuffer b : bufs) { - * mybb.put(b); + * mybb.put(b); * } + * </pre> * - * @param addr previouslt returned by allocate + * @param addr + * An address previously returned by + * {@link #allocate(ByteBuffer)}. + * * @return array of ByteBuffers */ public ByteBuffer[] get(long addr); @@ -67,9 +87,11 @@ * Clears all current allocations */ public void clear(); - + /** - * Clears all current allocations + * Create a child allocation context within which the caller may make and + * release allocations. */ - public AllocationContext createAllocationContext(); + public IMemoryManager createAllocationContext(); + } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/ISectorManager.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/ISectorManager.java 2011-02-07 20:30:43 UTC (rev 4181) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/ISectorManager.java 2011-02-08 14:57:32 UTC (rev 4182) @@ -25,55 +25,60 @@ package com.bigdata.rwstore.sector; /** - * The SectorManager defines the contract required to manage a set of - * SectorAllocators. + * The {@link ISectorManager} defines the contract required to manage a set of + * {@link SectorAllocator}s. * - * The SectorManager is passed to the SectorAllocator constructors and they - * will callback to manage their free list availability, and to trim the - * allocated storage if required. + * The {@link ISectorManager} is passed to the {@link SectorAllocator} + * constructors and they will callback to manage their free list availability, + * and to trim the allocated storage if required. * * @author Martyn Cutcher - * */ public interface ISectorManager { /** - * This request is made when the sectorAllocator no longer has a full set - * of block allocations available. + * This request is made when the sectorAllocator no longer has a full set of + * block allocations available. * * The allocator will issue this callback to help the SectorManager manage * an effective freelist of available allocators. * - * @param sectorAllocator to be removed + * @param sectorAllocator + * to be removed */ void removeFromFreeList(SectorAllocator sectorAllocator); /** - * When suficient alocations have been freed for recycling that a threshold - * of availability of reached for all block sizes, then the allocator - * calls back to the SectorManager to signal it is available to be returned - * to the free list. + * When sufficient allocations have been freed for recycling that a + * threshold of availability of reached for all block sizes, then the + * allocator calls back to the SectorManager to signal it is available to be + * returned to the free list. * - * @param sectorAllocator to be added + * @param sectorAllocator + * to be added */ void addToFreeList(SectorAllocator sectorAllocator); - + /** * When a sector is first created, it will remain at the head of the free * list until one of two conditions has been reached: + * <ol> * - * 1) The allocation has been saturated - * 2) The bit space has been filled + * <li>The allocation has been saturated.</li> + * <li>The bit space has been filled. + * <li> + * </ol> * - * In the case of (2), then it is possible that significant allocation - * space cannot be utilised - which will happen if the average allocation - * is less than 1K. In this situation, the sector can be trimmed and the - * space made available to the next sector. + * In the case of (2), then it is possible that significant allocation space + * cannot be utilized - which will happen if the average allocation is less + * than 1K. In this situation, the sector can be trimmed and the space made + * available to the next sector. * * trimSector will only be called in this condition - on the first occasion * that the allocator is removed from the freeList. * - * @param trim - the amount by which the sector allocation can be reduced + * @param trim + * - the amount by which the sector allocation can be reduced */ void trimSector(long trim, SectorAllocator sector); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/MemoryManager.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/MemoryManager.java 2011-02-07 20:30:43 UTC (rev 4181) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/MemoryManager.java 2011-02-08 14:57:32 UTC (rev 4182) @@ -31,49 +31,50 @@ import org.apache.log4j.Logger; import com.bigdata.io.DirectBufferPool; -import com.bigdata.io.IByteArraySlice; /** - * The MemoryManager manages an off-heap Direct Buffer. It uses the new - * SectorAllocator to allocate slots within the address range. + * The MemoryManager manages an off-heap Direct {@link ByteBuffer}. It uses the + * new SectorAllocator to allocate slots within the address range. * - * The interface is designed to support efficient transfer between NIO - * buffers. + * The interface is designed to support efficient transfer between NIO buffers. * * The most complex aspect of the implementation is the BLOB representation, - * requiring a mapping across multiple allocation slots. This is managed - * using recursive calls in the main three methods: allocate, free and get. + * requiring a mapping across multiple allocation slots. This is managed using + * recursive calls in the main three methods: allocate, free and get. * * @author Martyn Cutcher - * */ public class MemoryManager implements IMemoryManager, ISectorManager { - protected static final Logger log = Logger + private static final Logger log = Logger .getLogger(MemoryManager.class); - final ByteBuffer m_resource; + private final ByteBuffer m_resource; final private ReentrantLock m_allocationLock = new ReentrantLock(); int m_allocation = 0; - final int m_sectorSize; - final int m_maxResource; + private final int m_sectorSize; + private final int m_maxResource; - final ArrayList<SectorAllocator> m_sectors = new ArrayList<SectorAllocator>(); - final ArrayList<SectorAllocator> m_free = new ArrayList<SectorAllocator>(); + private final ArrayList<SectorAllocator> m_sectors = new ArrayList<SectorAllocator>(); + private final ArrayList<SectorAllocator> m_free = new ArrayList<SectorAllocator>(); public MemoryManager(final int maxResource, final int sectorSize) { +// m_resource = DirectBufferPool.INSTANCE.acquire(); m_resource = ByteBuffer.allocateDirect(maxResource); m_sectorSize = sectorSize; m_maxResource = maxResource; } - - public class MemoryManagerResourceError extends RuntimeException { - protected MemoryManagerResourceError() {} + + protected void finalize() throws Throwable { + // release to pool. + DirectBufferPool.INSTANCE.release(m_resource); } public long allocate(final ByteBuffer data) { + if (data == null) + throw new IllegalArgumentException(); m_allocationLock.lock(); try { final int size = data.remaining(); @@ -142,28 +143,9 @@ } } - /** - * The ByteBuffer[] return enables the handling of blobs that span more - * than a single slot, without the need to create an intermediate ByteBuffer. - * - * This will support transfers directly to other direct ByteBuffers, for - * example for network IO. - * - * Using ByteBuffer:put the returned array can be efficiently copied to - * another ByteBuffer: - * - * ByteBuffer mybb; - * ByteBuffer[] bufs = get(addr); - * for (ByteBuffer b : bufs) { - * mybb.put(b); - * } - * - * @param addr - * @return - */ public ByteBuffer[] get(final long addr) { - int rwaddr = getAllocationAddress(addr); - int size = getAllocationSize(addr); + final int rwaddr = getAllocationAddress(addr); + final int size = getAllocationSize(addr); if (size <= SectorAllocator.BLOB_SIZE) { return new ByteBuffer[] { getBuffer(rwaddr, size) }; @@ -219,7 +201,7 @@ return ret; } - private SectorAllocator getSector(int rwaddr) { + private SectorAllocator getSector(final int rwaddr) { final int index = SectorAllocator.getSectorIndex(rwaddr); if (index >= m_sectors.size()) throw new IllegalStateException("Address: " + rwaddr + " yields index: " + index + " >= sector:size(): " + m_sectors.size()); @@ -231,7 +213,7 @@ return (int) (addr >> 32L); } - static int getAllocationSize(long addr) { + static int getAllocationSize(final long addr) { return (int) (addr & 0xFFFFL); } @@ -281,20 +263,25 @@ } public void clear() { - m_sectors.clear(); - m_free.clear(); - m_allocation = 0; + m_allocationLock.lock(); + try { + m_sectors.clear(); + m_free.clear(); + m_allocation = 0; + } finally { + m_allocationLock.unlock(); + } } - public void releaseResources() throws InterruptedException { - DirectBufferPool.INSTANCE.release(m_resource); - } +// public void releaseResources() throws InterruptedException { +// DirectBufferPool.INSTANCE.release(m_resource); +// } public void addToFreeList(final SectorAllocator sector) { m_free.add(sector); } - public void removeFromFreeList(SectorAllocator sector) { + public void removeFromFreeList(final SectorAllocator sector) { assert m_free.get(0) == sector; m_free.remove(sector); @@ -306,7 +293,7 @@ m_allocation -= trim; } - public AllocationContext createAllocationContext() { + public IMemoryManager createAllocationContext() { return new AllocationContext(this); } Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/MemoryManagerResourceError.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/MemoryManagerResourceError.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/MemoryManagerResourceError.java 2011-02-08 14:57:32 UTC (rev 4182) @@ -0,0 +1,13 @@ +/** + * + */ +package com.bigdata.rwstore.sector; + +public class MemoryManagerResourceError extends RuntimeException { + /** + * + */ + private static final long serialVersionUID = 1L; + + protected MemoryManagerResourceError() {} +} \ No newline at end of file Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/SectorAllocator.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/SectorAllocator.java 2011-02-07 20:30:43 UTC (rev 4181) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/SectorAllocator.java 2011-02-08 14:57:32 UTC (rev 4182) @@ -31,23 +31,22 @@ import org.apache.log4j.Logger; -import com.bigdata.io.DirectBufferPool; import com.bigdata.rwstore.FixedOutputStream; import com.bigdata.rwstore.IWriteCacheManager; /** * The SectorAllocator is designed as an alternative the the standard RWStore * FixedAllocators. - * + * <p> * The idea of the SectorAllocator is to efficiently contain within a single * region as dense a usage as possible. Since a SectorAllocator is able to * allocate a full range of slot sizes, it should be able to service several - * thousand allocations and maximise disk locality on write. - * + * thousand allocations and maximize disk locality on write. + * <p> * Furthermore, it presents an option to be synced with the backing store - * similarly to a MappedFile, in which case a single write for the entire * sector could be made for update. - * + * <p> * What we do not want is to run out of bits and to leave significant unused * space in the sector. This could happen if we primarily allocated small * slots - say on average 512 bytes. In this case, the maximum 1636 entries @@ -63,20 +62,19 @@ * sectors. Implying a maximum addressable store file of 64M * 64K, * = 4TB of full sectors. If the average sector only requires 32M, then the * total store would be reduced appropriately. - * + * <p> * The maximum theoretical storage is yielded by MAX_INT * AVG_SLOT_SIZE, so * 2GB * 2K (avg) would equate to the optimal maximum addressable allocations * and file size. An AVG of > 2K yields fewer allocations and an AVG of < 2K * a reduced file size. * * TODO: add parameterisation of META_SIZE for exploitation by MemoryManager. - * TODO: cache block starts in m_addresses to simplify/optimise bit2Offset * * @author Martyn Cutcher - * */ public class SectorAllocator { - protected static final Logger log = Logger + + private static final Logger log = Logger .getLogger(SectorAllocator.class); static final int getBitMask(int bits) { @@ -86,28 +84,29 @@ return ret; } - static final int SECTOR_INDEX_BITS = 16; - static final int SECTOR_OFFSET_BITS = 32-SECTOR_INDEX_BITS; - static final int SECTOR_OFFSET_MASK = getBitMask(SECTOR_OFFSET_BITS); + + private static final int SECTOR_INDEX_BITS = 16; + private static final int SECTOR_OFFSET_BITS = 32-SECTOR_INDEX_BITS; + private static final int SECTOR_OFFSET_MASK = getBitMask(SECTOR_OFFSET_BITS); - static final int META_SIZE = 8192; // 8K + private static final int META_SIZE = 8192; // 8K - final static int SECTOR_SIZE = 64 * 1024 * 1024; // 10M - final static int NUM_ENTRIES = (META_SIZE - 12) / (4 + 1); // 8K - index - address / (4 + 1) bits plus tag - final int[] BIT_MASKS = {0x1, 0x3, 0x7, 0xF, 0xFF, 0xFFFF, 0xFFFFFFFF}; +// private final static int SECTOR_SIZE = 64 * 1024 * 1024; // 10M + private final static int NUM_ENTRIES = (META_SIZE - 12) / (4 + 1); // 8K - index - address / (4 + 1) bits plus tag +// private final int[] BIT_MASKS = {0x1, 0x3, 0x7, 0xF, 0xFF, 0xFFFF, 0xFFFFFFFF}; final static int BLOB_SIZE = 4096; - final static int BLOB_CHAIN_OFFSET = BLOB_SIZE - 4; - final static int[] ALLOC_SIZES = {64, 128, 256, 512, 1024, 2048, BLOB_SIZE}; - final static int[] ALLOC_BITS = {32, 32, 32, 32, 32, 32, 32}; - int m_index; - long m_sectorAddress; - int m_maxSectorSize; - byte[] m_tags = new byte[NUM_ENTRIES]; - int[] m_bits = new int[NUM_ENTRIES]; // 128 - sectorAddress(1) - m_tags(4) +// private final static int BLOB_CHAIN_OFFSET = BLOB_SIZE - 4; + private final static int[] ALLOC_SIZES = {64, 128, 256, 512, 1024, 2048, BLOB_SIZE}; + private final static int[] ALLOC_BITS = {32, 32, 32, 32, 32, 32, 32}; + private int m_index; + private long m_sectorAddress; + private int m_maxSectorSize; + private final byte[] m_tags = new byte[NUM_ENTRIES]; + private final int[] m_bits = new int[NUM_ENTRIES]; // 128 - sectorAddress(1) - m_tags(4) - int[] m_transientbits = new int[NUM_ENTRIES]; - int[] m_commitbits = new int[NUM_ENTRIES]; - int[] m_addresses = new int[NUM_ENTRIES]; + private int[] m_transientbits = new int[NUM_ENTRIES]; + private int[] m_commitbits = new int[NUM_ENTRIES]; + private final int[] m_addresses = new int[NUM_ENTRIES]; // maintain count against each alloc size, this provides ready access to be // able to check the minimum number of bits for all tag sizes. No @@ -118,17 +117,18 @@ // only the total number of bits, but the average number of bits for the // tag, dividing the numebr of free bits by the total (number of blocks) // for each tag. - int[] m_free = new int[ALLOC_SIZES.length]; - int[] m_total = new int[ALLOC_SIZES.length]; - int[] m_allocations = new int[ALLOC_SIZES.length]; - int[] m_recycles = new int[ALLOC_SIZES.length]; + final private int[] m_free = new int[ALLOC_SIZES.length]; + final private int[] m_total = new int[ALLOC_SIZES.length]; + final private int[] m_allocations = new int[ALLOC_SIZES.length]; + final private int[] m_recycles = new int[ALLOC_SIZES.length]; - final ISectorManager m_store; - boolean m_onFreeList = false; - private long m_diskAddr; + private final ISectorManager m_store; private final IWriteCacheManager m_writes; - public SectorAllocator(ISectorManager store, IWriteCacheManager writes) { + private boolean m_onFreeList = false; + private long m_diskAddr; + + public SectorAllocator(final ISectorManager store, final IWriteCacheManager writes) { m_store = store; m_writes = writes; } @@ -217,7 +217,7 @@ int allocated = 0; for (int i = 0; i < m_tags.length; i++) { if (m_tags[i] == -1) { - int block = this.ALLOC_SIZES[tag] * 32; + int block = SectorAllocator.ALLOC_SIZES[tag] * 32; if ((allocated + block) <= m_maxSectorSize) { m_tags[i] = tag; m_free[tag] += 32; @@ -553,15 +553,15 @@ m_store.addToFreeList(this); } - public static int getSectorIndex(int rwaddr) { + public static int getSectorIndex(final int rwaddr) { return (-rwaddr) >> SECTOR_OFFSET_BITS; } - public static int getSectorOffset(int rwaddr) { + public static int getSectorOffset(final int rwaddr) { return (-rwaddr) & SECTOR_OFFSET_MASK; } - public static int getBlobBlockCount(int size) { + public static int getBlobBlockCount(final int size) { final int nblocks = (size + BLOB_SIZE - 1) / BLOB_SIZE; return nblocks; Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/sector/TestMemoryManager.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/sector/TestMemoryManager.java 2011-02-07 20:30:43 UTC (rev 4181) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/sector/TestMemoryManager.java 2011-02-08 14:57:32 UTC (rev 4182) @@ -10,7 +10,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import com.bigdata.rwstore.sector.MemoryManager.MemoryManagerResourceError; import com.bigdata.util.concurrent.DaemonThreadFactory; import junit.framework.TestCase; @@ -84,7 +83,7 @@ public void testAllocationContexts() { installMemoryManager(); - AllocationContext context = manager.createAllocationContext(); + final IMemoryManager context = manager.createAllocationContext(); for (int i = 0; i < 500; i++) { doStressAllocations(context, false, 5000, 5 + r.nextInt(3000)); context.clear(); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-02-08 17:50:51
|
Revision: 4184 http://bigdata.svn.sourceforge.net/bigdata/?rev=4184&view=rev Author: thompsonbry Date: 2011-02-08 17:50:40 +0000 (Tue, 08 Feb 2011) Log Message: ----------- Added support for "at-once" operator evaluation to the QueryEngine. This is only supported for ChunkedRunningQuery (and its subclass, FederatedRunningQuery), but that is the more efficient evaluation strategy. This is not yet integrated with the NIO buffers used in scale-out so all data is materialized on the Java heap for the moment and "blocked" evaluation of operators based on a memory threshold is not yet supported. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpBase.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpContext.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpContextBase.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/QuoteOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/AbstractRunningQuery.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/BOpStats.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/ChunkedRunningQuery.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/IRunningQuery.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/PipelineUtility.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/RunState.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StandaloneChainedRunningQuery.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StartOpMessage.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/fed/AllocationContextKey.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/MemorySortOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/SparqlBindingSetComparatorOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/engine/MockRunningQuery.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/engine/TestAll.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/engine/TestQueryEngine_Slice.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/engine/TestRunState.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/solutions/TestMemorySortOp.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/engine/TestQueryEngine_SortOp.java Removed Paths: ------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineType.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/engine/TestQueryEngine2.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpBase.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpBase.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpBase.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -301,7 +301,15 @@ return a; } - /** deep copy the arguments. */ + /** + * Deep copy the arguments. + * + * @todo As long as we stick to the immutable semantics for bops, we can + * just make a shallow copy of the arguments in the "copy" constructor + * and then modify them within the specific operator constructor + * before returning control to the caller. This would result in less + * heap churn. + */ static protected BOp[] deepCopy(final BOp[] a) { if (a == NOARGS) { // fast path for zero arity operators. Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpContext.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpContext.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpContext.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -32,12 +32,9 @@ import com.bigdata.bop.engine.BOpStats; import com.bigdata.bop.engine.IChunkMessage; import com.bigdata.bop.engine.IRunningQuery; -import com.bigdata.btree.ILocalBTreeView; -import com.bigdata.journal.IIndexManager; import com.bigdata.relation.accesspath.IAccessPath; import com.bigdata.relation.accesspath.IAsynchronousIterator; import com.bigdata.relation.accesspath.IBlockingBuffer; -import com.bigdata.service.IBigdataFederation; /** * The evaluation context for the operator (NOT serializable). @@ -137,56 +134,40 @@ return sink2; } - /** - * - * @param fed - * The {@link IBigdataFederation} IFF the operator is being - * evaluated on an {@link IBigdataFederation}. When evaluating - * operations against an {@link IBigdataFederation}, this - * reference provides access to the scale-out view of the indices - * and to other bigdata services. - * @param indexManager - * The <strong>local</strong> {@link IIndexManager}. Query - * evaluation occurs against the local indices. In scale-out, - * query evaluation proceeds shard wise and this - * {@link IIndexManager} MUST be able to read on the - * {@link ILocalBTreeView}. - * @param readTimestamp - * The timestamp or transaction identifier against which the - * query is reading. - * @param writeTimestamp - * The timestamp or transaction identifier against which the - * query is writing. - * @param partitionId - * The index partition identifier -or- <code>-1</code> if the - * index is not sharded. - * @param stats - * The object used to collect statistics about the evaluation of - * this operator. - * @param source - * Where to read the data to be consumed by the operator. - * @param sink - * Where to write the output of the operator. - * @param sink2 - * Alternative sink for the output of the operator (optional). - * This is used by things like SPARQL optional joins to route - * failed joins outside of the join group. - * - * @throws IllegalArgumentException - * if the <i>stats</i> is <code>null</code> - * @throws IllegalArgumentException - * if the <i>source</i> is <code>null</code> (use an empty - * source if the source will be ignored). - * @throws IllegalArgumentException - * if the <i>sink</i> is <code>null</code> - * - * @todo modify to accept {@link IChunkMessage} or an interface available - * from getChunk() on {@link IChunkMessage} which provides us with - * flexible mechanisms for accessing the chunk data. - * <p> - * When doing that, modify to automatically track the {@link BOpStats} - * as the <i>source</i> is consumed. - */ + /** + * + * @param runningQuery + * The {@link IRunningQuery}. + * @param partitionId + * The index partition identifier -or- <code>-1</code> if the + * index is not sharded. + * @param stats + * The object used to collect statistics about the evaluation of + * this operator. + * @param source + * Where to read the data to be consumed by the operator. + * @param sink + * Where to write the output of the operator. + * @param sink2 + * Alternative sink for the output of the operator (optional). + * This is used by things like SPARQL optional joins to route + * failed joins outside of the join group. + * + * @throws IllegalArgumentException + * if the <i>stats</i> is <code>null</code> + * @throws IllegalArgumentException + * if the <i>source</i> is <code>null</code> (use an empty + * source if the source will be ignored). + * @throws IllegalArgumentException + * if the <i>sink</i> is <code>null</code> + * + * @todo modify to accept {@link IChunkMessage} or an interface available + * from getChunk() on {@link IChunkMessage} which provides us with + * flexible mechanisms for accessing the chunk data. + * <p> + * When doing that, modify to automatically track the {@link BOpStats} + * as the <i>source</i> is consumed. + */ public BOpContext(final IRunningQuery runningQuery,final int partitionId, final BOpStats stats, final IAsynchronousIterator<E[]> source, final IBlockingBuffer<E[]> sink, final IBlockingBuffer<E[]> sink2) { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpContextBase.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpContextBase.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpContextBase.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -42,9 +42,6 @@ /** * Base class for the bigdata operation evaluation context (NOT serializable). - * - * @param <E> - * The generic type of the objects processed by the operator. */ public class BOpContextBase { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -661,7 +661,7 @@ static public IBindingSet[] toArray(final Iterator<IBindingSet[]> itr, final BOpStats stats) { - final List<IBindingSet[]> list = new LinkedList<IBindingSet[]>(); + final List<IBindingSet[]> list = new LinkedList<IBindingSet[]>(); int nchunks = 0, nelements = 0; { @@ -676,8 +676,6 @@ nelements += a.length; - list.add(a); - } stats.chunksIn.add(nchunks); @@ -699,19 +697,27 @@ final IBindingSet[] a = new IBindingSet[nelements]; - final Iterator<IBindingSet[]> itr2 = list.iterator(); - - while (itr2.hasNext()) { - - final IBindingSet[] t = itr2.next(); - - System.arraycopy(t/* src */, 0/* srcPos */, a/* dest */, - n/* destPos */, t.length/* length */); - - n += t.length; - - } + final Iterator<IBindingSet[]> itr2 = list.iterator(); + while (itr2.hasNext()) { + + final IBindingSet[] t = itr2.next(); + try { + System.arraycopy(t/* src */, 0/* srcPos */, a/* dest */, + n/* destPos */, t.length/* length */); + } catch (IndexOutOfBoundsException ex) { + // Provide some more detail in the stack trace. + final IndexOutOfBoundsException ex2 = new IndexOutOfBoundsException( + "t.length=" + t.length + ", a.length=" + a.length + + ", n=" + n); + ex2.initCause(ex); + throw ex2; + } + + n += t.length; + + } + return a; } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineOp.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineOp.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -27,12 +27,18 @@ package com.bigdata.bop; +import java.nio.ByteBuffer; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; +import org.apache.log4j.Logger; + import com.bigdata.bop.engine.BOpStats; +import com.bigdata.bop.engine.ChunkedRunningQuery; import com.bigdata.bop.engine.QueryEngine; +import com.bigdata.bop.solutions.SliceOp; +import com.bigdata.relation.accesspath.IAsynchronousIterator; /** * Abstract base class for pipeline operators where the data moving along the @@ -55,6 +61,9 @@ */ private static final long serialVersionUID = 1L; + private final static transient Logger log = Logger + .getLogger(PipelineOp.class); + public interface Annotations extends BOp.Annotations, BufferAnnotations { /** @@ -91,6 +100,63 @@ boolean DEFAULT_SHARED_STATE = false; + /** + * Annotation may be used to indicate operators which are not thread + * safe (default {@value #DEFAULT_THREAD_SAFE}). Concurrent invocations + * of the evaluation task will not be scheduled for a given shard for an + * operator which is not thread safe. + * + * @todo Unit tests for {@link ChunkedRunningQuery} to verify that it + * eventually schedules operator tasks which were deferred to + * prevent concurrent evaluation. + */ + String THREAD_SAFE = PipelineOp.class.getName() + ".threadSafe"; + + boolean DEFAULT_THREAD_SAFE = true; + + /** + * Annotation used to mark pipelined (aka vectored) operators. When + * <code>false</code> the operator will use either "at-once" or + * "blocked" evaluation depending on how it buffers its data for + * evaluation. + */ + String PIPELINED = PipelineOp.class.getName() + ".pipelined"; + + boolean DEFAULT_PIPELINED = true; + + /** + * For non-{@link #PIPELINED} operators, this non-negative value + * specifies the maximum #of bytes which the operator may buffer on the + * native heap before evaluation of the operator is triggered -or- ZERO + * (0) if the operator buffers the data on the Java heap (default + * {@value #DEFAULT_MAX_MEMORY}). When non-zero, the #of bytes specified + * should be a multiple of 4k. For a shared operation, the value is the + * maximum #of bytes which may be buffered per shard. + * <p> + * Operator "at-once" evaluation will be used if either (a) the operator + * is buffering data on the Java heap; or (b) the operator is buffering + * data on the native heap and the amount of buffered data does not + * exceed the specified value for {@link #MAX_MEMORY}. For convenience, + * the value {@link Integer#MAX_VALUE} may be specified to indicate that + * "at-once" evaluation is required. + * <p> + * When data are buffered on the Java heap, "at-once" evaluation is + * implied and the data will be made available to the operator as a + * single {@link IAsynchronousIterator} when the operator is invoked. + * <p> + * When {@link #MAX_MEMORY} is positive, data are marshaled in + * {@link ByteBuffer}s and the operator will be invoked once either (a) + * its memory threshold for the buffered data has been exceeded; or (b) + * no predecessor of the operator is running (or can be triggered) -and- + * all inputs for the operator have been materialized on this node. Note + * that some operators DO NOT support multiple pass evaluation + * semantics. Such operators MUST throw an exception if the value of + * this annotation could result in multiple evaluation passes. + */ + String MAX_MEMORY = PipelineOp.class.getName() + ".maxMemory"; + + int DEFAULT_MAX_MEMORY = 0; + // /** // * Annotation used to mark a set of (non-optional) joins which may be // * freely reordered by the query optimizer in order to minimize the @@ -224,17 +290,52 @@ } + /** + * Return <code>true</code> if the operator is pipelined (versus using + * "at-once" or blocked evaluation as discussed below). + * <dl> + * <dt>Pipelined</dt> + * <dd>Pipelined operators stream chunks of intermediate results from one + * operator to the next using producer / consumer pattern. Each time a set + * of intermediate results is available for a pipelined operator, it is + * evaluated against those inputs producing another set of intermediate + * results for its target operator(s). Pipelined operators may be evaluated + * many times during a given query and often have excellent parallelism due + * to the concurrent evaluation of the different operators on different sets + * of intermediate results.</dd> + * <dt>At-Once</dt> + * <dd> + * An "at-once" operator will run exactly once and must wait for all of its + * inputs to be assembled before it runs. There are some operations for + * which "at-once" evaluation is always required, such as ORDER_BY. Other + * operations MAY use operator-at-once evaluation in order to benefit from a + * combination of more efficient IO patterns and simpler design. At-once + * operators may either buffer their data on the Java heap (which is not + * scalable due to the heap pressure exerted on the garbage collector) or + * buffer their data on the native heap (which does scale).</dd> + * <dt>Blocked</dt> + * <dd>Blocked operators buffer large amounts of data on the native heap and + * run each time they exceed some threshold #of bytes of buffered data. A + * blocked operator is basically an "at-once" operator which buffers its + * data on the native heap and which can be evaluated in multiple passes. + * For example, a hash join could use a blocked operator design while an + * ORDER_BY operator can not. By deferring their evaluation until some + * threshold amount of data has been materialized, they may be evaluated + * once or more than once, depending on the data scale, but still retain + * many of the benefits of "at-once" evaluation in terms of IO patterns. + * Whether or not an operator can be used as a "blocked" operator is a + * matter of the underlying operator implementation.</dd> + * </dl> + * + * @see Annotations#PIPELINED + * @see Annotations#MAX_MEMORY + */ + public boolean isPipelined() { + return getProperty(PipelineOp.Annotations.PIPELINED, + PipelineOp.Annotations.DEFAULT_PIPELINED); + } + /** - * Return the {@link PipelineType} of the operator (default - * {@link PipelineType#Vectored}). - */ - public PipelineType getPipelineType() { - - return PipelineType.Vectored; - - } - - /** * Return <code>true</code> iff {@link #newStats()} must be shared across * all invocations of {@link #eval(BOpContext)} for this operator for a * given query. @@ -247,12 +348,21 @@ Annotations.DEFAULT_SHARED_STATE); } - - /** - * Return a new object which can be used to collect statistics on the - * operator evaluation (this may be overridden to return a more specific - * class depending on the operator). - */ + + /** + * Return a new object which can be used to collect statistics on the + * operator evaluation. This may be overridden to return a more specific + * class depending on the operator. + * <p> + * Some operators may use this to share state across multiple invocations of + * the operator within a given query (e.g., {@link SliceOp}). Another + * mechanism for sharing state is to use the same named allocation context + * for the memory manager across the operator invocation instances. + * <p> + * Operator life cycle events support pre-/post-operator behaviors. Such + * events can be used to processed buffered solutions accumulated within + * some shared state across multiple operator invocations. + */ public BOpStats newStats() { return new BOpStats(); @@ -283,7 +393,7 @@ // getChunkTimeout(), Annotations.chunkTimeoutUnit, stats); // // } - + /** * Return a {@link FutureTask} which computes the operator against the * evaluation context. The caller is responsible for executing the @@ -302,5 +412,31 @@ * return the ForkJoinTask. */ abstract public FutureTask<Void> eval(BOpContext<IBindingSet> context); - + + /** + * Hook to setup any resources associated with the operator (temporary + * files, memory manager allocation contexts, etc.). This hook is invoked + * exactly once and before any instance task for the operator is evaluated. + */ + public void setUp() throws Exception { + + if (log.isTraceEnabled()) + log.trace("bopId=" + getId()); + + } + + /** + * Hook to tear down any resources associated with the operator (temporary + * files, memory manager allocation contexts, etc.). This hook is invoked + * exactly once no later than when the query is cancelled. If the operator + * is known to be done executing, then this hook will be invoked at that + * time. + */ + public void tearDown() throws Exception { + + if (log.isTraceEnabled()) + log.trace("bopId=" + getId()); + + } + } Deleted: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineType.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineType.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineType.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -1,68 +0,0 @@ -/** - -Copyright (C) SYSTAP, LLC 2006-2010. All rights reserved. - -Contact: - SYSTAP, LLC - 4501 Tower Road - Greensboro, NC 27410 - lic...@bi... - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ -/* - * Created on Sep 21, 2010 - */ - -package com.bigdata.bop; - -/** - * Return the type of pipelining supported by an operator. - * <p> - * Note: bigdata does not support tuple-at-a-time processing. Only vectored and - * operator-at-a-time processing. Tuple at a time processing is generally very - * inefficient. - * - * @author <a href="mailto:tho...@us...">Bryan Thompson</a> - * @version $Id$ - */ -public enum PipelineType { - - /** - * Vectored operators stream chunks of intermediate results from one - * operator to the next using producer / consumer pattern. Each time a set - * of intermediate results is available for a vectored operator, it is - * evaluated against those inputs producing another set of intermediate - * results for its target operator(s). Vectored operators may be evaluated - * many times during a given query and often have excellent parallelism due - * to the concurrent evaluation of the different operators on different sets - * of intermediate results. - */ - Vectored, - - /** - * The operator will run exactly once and must wait for all of its inputs to - * be assembled before it runs. - * <p> - * There are some operations for which this is always true, such as SORT. - * Other operations MAY use operator-at-once evaluation in order to benefit - * from a combination of more efficient IO patterns and simpler design. - * However, pipelined operators using large memory blocks have many of the - * benefits of operator-at-once evaluation. By deferring their evaluation - * until some minimum number of source data blocks are available, they may - * be evaluated once or more than once, depending on the data scale. - */ - OneShot; - -} Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/QuoteOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/QuoteOp.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/QuoteOp.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -38,10 +38,10 @@ * @author <a href="mailto:tho...@us...">Bryan Thompson</a> * @version $Id$ * - * @todo I think that we can avoid quoting operators by using annotations (for - * some cases) and through explicit interaction between operators for - * others (such as between a join and a predicate). If that proves to be - * true then this class will be dropped. + * @deprecated I think that we can avoid quoting operators by using annotations + * (for some cases) and through explicit interaction between + * operators for others (such as between a join and a predicate). If + * that proves to be true then this class will be dropped. */ public class QuoteOp extends BOpBase { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/AbstractRunningQuery.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/AbstractRunningQuery.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/AbstractRunningQuery.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -29,6 +29,8 @@ import java.nio.ByteBuffer; import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -235,6 +237,16 @@ */ private final AtomicBoolean didQueryTearDown = new AtomicBoolean(false); + /** + * A collection reporting on whether or not a given operator has been torn + * down. This collection is used to provide the guarantee that an operator + * is torn down exactly once, regardless of the #of invocations of the + * operator or the #of errors which might occur during query processing. + * + * @see PipelineOp#tearDown() + */ + private final Map<Integer/* bopId */, AtomicBoolean> tornDown = new LinkedHashMap<Integer, AtomicBoolean>(); + /** * Set the query deadline. The query will be cancelled when the deadline is * passed. If the deadline is passed, the query is immediately cancelled. @@ -601,8 +613,23 @@ try { - if (runState.startOp(msg)) + if (runState.startOp(msg)) { + + /* + * Set a flag in this collection so we will know that this + * operator needs to be torn down (we do not bother to tear down + * operators which have never been setup). + */ + tornDown.put(msg.bopId, new AtomicBoolean(false)); + + /* + * TODO It is a bit dangerous to hold the lock while we do this + * but this needs to be executed before any other thread can + * start an evaluation task for that operator. + */ lifeCycleSetUpOperator(msg.bopId); + + } } catch (TimeoutException ex) { @@ -616,19 +643,19 @@ } - /** - * Message provides notice that the operator has ended execution. The - * termination conditions for the query are checked. (For scale-out, the - * node node controlling the query needs to be involved for each operator - * start/stop in order to make the termination decision atomic). - * - * @param msg - * The {@link HaltOpMessage} - * - * @throws UnsupportedOperationException - * If this node is not the query coordinator. - */ - final protected void haltOp(final HaltOpMessage msg) { + /** + * Message provides notice that the operator has ended execution. The + * termination conditions for the query are checked. (For scale-out, the + * node controlling the query needs to be involved for each operator + * start/stop in order to make the termination decision atomic). + * + * @param msg + * The {@link HaltOpMessage} + * + * @throws UnsupportedOperationException + * If this node is not the query coordinator. + */ + /*final*/ protected void haltOp(final HaltOpMessage msg) { if (!controller) throw new UnsupportedOperationException(ERR_NOT_CONTROLLER); @@ -653,13 +680,20 @@ if (runState.haltOp(msg)) { - /* - * No more chunks can appear for this operator so invoke its end - * of life cycle hook. - */ + /* + * No more chunks can appear for this operator so invoke its end + * of life cycle hook IFF it has not yet been invoked. + */ - lifeCycleTearDownOperator(msg.bopId); + final AtomicBoolean tornDown = AbstractRunningQuery.this.tornDown + .get(msg.bopId); + if (tornDown.compareAndSet(false/* expect */, true/* update */)) { + + lifeCycleTearDownOperator(msg.bopId); + + } + if (runState.isAllDone()) { // Normal termination. @@ -681,6 +715,69 @@ } + /** + * Return <code>true</code> iff the preconditions have been satisfied for + * the "at-once" invocation of the specified operator (no predecessors are + * running or could be triggered and the operator has not been evaluated). + * + * @param bopId + * Some operator identifier. + * + * @return <code>true</code> iff the "at-once" evaluation of the operator + * may proceed. + */ + protected boolean isAtOnceReady(final int bopId) { + + lock.lock(); + + try { + +// if (isDone()) { +// // The query has already halted. +// throw new InterruptedException(); +// } + + return runState.isAtOnceReady(bopId); + + } finally { + + lock.unlock(); + + } + + } + +// /** +// * Return <code>true</code> iff there is already an instance of the operator +// * running. +// * +// * @param bopId +// * The bopId of the operator. +// * +// * @return True iff there is at least one instance of the operator running +// * (globally for this query). +// */ +// public boolean isOperatorRunning(final int bopId) { +// +// lock.lock(); +// +// try { +// +// final AtomicLong nrunning = runState.runningMap.get(bopId); +// +// if (nrunning == null) +// return false; +// +// return nrunning.get() > 0; +// +// } finally { +// +// lock.unlock(); +// +// } +// +// } + /** * Hook invoked the first time the given operator is evaluated for the * query. This may be used to set up life cycle resources for the operator, @@ -690,27 +787,53 @@ * @param bopId * The operator identifier. */ - protected void lifeCycleSetUpOperator(final int bopId) { + protected void lifeCycleSetUpOperator(final int bopId) { - if (log.isTraceEnabled()) - log.trace("queryId=" + queryId + ", bopId=" + bopId); + final BOp op = getBOpIndex().get(bopId); - } + if (op instanceof PipelineOp) { - /** - * Hook invoked the after the given operator has been evaluated for the - * query for what is known to be the last time. This may be used to tear - * down life cycle resources for the operator, such as a distributed hash - * table on a set of nodes identified by annotations of the operator. - * - * @param bopId - * The operator identifier. - */ - protected void lifeCycleTearDownOperator(final int bopId) { + try { - if (log.isTraceEnabled()) - log.trace("queryId=" + queryId + ", bopId=" + bopId); + ((PipelineOp) op).setUp(); + + } catch (Exception ex) { + + throw new RuntimeException(ex); + + } + } + + } + + /** + * Hook invoked the after the given operator has been evaluated for the + * query for what is known to be the last time. This may be used to tear + * down life cycle resources for the operator, such as a distributed hash + * table on a set of nodes identified by annotations of the operator. + * + * @param bopId + * The operator identifier. + */ + protected void lifeCycleTearDownOperator(final int bopId) { + + final BOp op = getBOpIndex().get(bopId); + + if (op instanceof PipelineOp) { + + try { + + ((PipelineOp) op).tearDown(); + + } catch (Exception ex) { + + throw new RuntimeException(ex); + + } + + } + } /** @@ -730,6 +853,27 @@ */ protected void lifeCycleTearDownQuery() { + final Iterator<Map.Entry<Integer/* bopId */, AtomicBoolean/* tornDown */>> itr = tornDown + .entrySet().iterator(); + + while(itr.hasNext()) { + + final Map.Entry<Integer/* bopId */, AtomicBoolean/* tornDown */> entry = itr + .next(); + + final AtomicBoolean tornDown = entry.getValue(); + + if (tornDown.compareAndSet(false/* expect */, true/* update */)) { + + /* + * Guaranteed one time tear down for this operator. + */ + lifeCycleTearDownOperator(entry.getKey()/* bopId */); + + } + + } + if (log.isTraceEnabled()) log.trace("queryId=" + queryId); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/BOpStats.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/BOpStats.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/BOpStats.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -30,6 +30,7 @@ import java.io.Serializable; import com.bigdata.bop.BOp; +import com.bigdata.bop.PipelineOp; import com.bigdata.counters.CAT; /** @@ -58,6 +59,13 @@ * The #of instances of a given operator which have been created for a given * query. This provides interesting information about the #of task instances * for each operator which were required to execute a query. + * + * TODO Due to the way this is incremented, this is always ONE (1) if + * {@link PipelineOp.Annotations#SHARED_STATE} is <code>true</code> (it + * reflects the #of times {@link #add(BOpStats)} was invoked plus one for + * the ctor rather than the #of times the operator task was invoked). This + * should be changed to reflect the #of operator task instances created + * instead. */ final public CAT opCount = new CAT(); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/ChunkedRunningQuery.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/ChunkedRunningQuery.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/ChunkedRunningQuery.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -35,7 +35,6 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; @@ -50,6 +49,7 @@ import com.bigdata.bop.IBindingSet; import com.bigdata.bop.NoSuchBOpException; import com.bigdata.bop.PipelineOp; +import com.bigdata.bop.fed.FederatedRunningQuery; import com.bigdata.journal.ITx; import com.bigdata.journal.Journal; import com.bigdata.relation.accesspath.BufferClosedException; @@ -642,19 +642,42 @@ } } - /** - * Examine the input queue for the (bopId,partitionId). If there is work - * available and no task is currently running, then drain the work queue and - * submit a task to consume that work. - * - * @param bundle - * The (bopId,partitionId). - * - * @return <code>true</code> if a new task was started. - */ + /** + * Overridden to attempt to consume another chunk each time an operator + * reports that it has halted evaluation. This is necessary because the + * haltOp() message can arrive asynchronously, so we need to test the work + * queues in case there are "at-once" operators awaiting the termination of + * their predecessor(s) in the pipeline. + */ + @Override + protected void haltOp(final HaltOpMessage msg) { + super.haltOp(msg); + consumeChunk(); + } + + /** + * Examine the input queue for the (bopId,partitionId). If there is work + * available, then drain the work queue and submit a task to consume that + * work. This handles {@link PipelineOp.Annotations#THREAD_SAFE}, + * {@link PipelineOp.Annotations#PIPELINED} as special cases. + * + * + * @param bundle + * The (bopId,partitionId). + * + * @return <code>true</code> if a new task was started. + * + * @todo Also handle {@link PipelineOp.Annotations#MAX_MEMORY} here by + * handshaking with the {@link FederatedRunningQuery}. + */ private boolean scheduleNext(final BSBundle bundle) { if (bundle == null) throw new IllegalArgumentException(); + final BOp bop = getBOpIndex().get(bundle.bopId); + final boolean threadSafe = bop.getProperty( + PipelineOp.Annotations.THREAD_SAFE, + PipelineOp.Annotations.DEFAULT_THREAD_SAFE); + final boolean pipelined = ((PipelineOp)bop).isPipelined();; lock.lock(); try { // Make sure the query is still running. @@ -664,14 +687,31 @@ ConcurrentHashMap<ChunkFutureTask, ChunkFutureTask> map = operatorFutures .get(bundle); if (map != null) { - int nrunning = 0; +// // #of instances of the operator already running. +// int nrunning = 0; for (ChunkFutureTask cft : map.keySet()) { - if (cft.isDone()) + if (cft.isDone()) { + // Remove tasks which have already terminated. map.remove(cft); - nrunning++; + } +// nrunning++; } - if (map.isEmpty()) + if (map.isEmpty()) { + // No tasks running for this operator. operatorFutures.remove(bundle); + } else { + // At least one task is running for this operator. + if (!threadSafe) { + /* + * This operator is not thread-safe, so reject + * concurrent execution for the same (bopId,shardId). + */ + if (log.isDebugEnabled()) + log.debug("Rejecting concurrent execution: " + + bundle + ", #running=" + map.size()); + return false; + } + } /* * FIXME If we allow a limit on the concurrency then we need to * manage things in order to guarantee that deadlock can not @@ -709,26 +749,53 @@ // } // // } - // Remove the work queue for that (bopId,partitionId). + // Get the work queue for that (bopId,partitionId). final BlockingQueue<IChunkMessage<IBindingSet>> queue = operatorQueues - .remove(bundle); - if (queue == null || queue.isEmpty()) { + .get(bundle); + if (queue == null) { // no work return false; } + if (!pipelined && !queue.isEmpty() && !isAtOnceReady(bundle.bopId)) { + /* + * This operator is not pipelined, so we need to wait until all + * of its input solutions have been materialized (no prior + * operator in the pipeline is running or has inputs available + * which could cause it to run). + * + * TODO This is where we should examine MAX_MEMORY and the + * buffered data to see whether or not to trigger an evaluation + * pass for the operator based on the data already materialized + * for that operator. + */ + if (log.isDebugEnabled()) + log.debug("Waiting on producer(s): bopId=" + bundle.bopId); + return false; + } + // Remove the work queue for that (bopId,partitionId). + operatorQueues.remove(bundle); + if (queue.isEmpty()) { + // no work + return false; + } // Drain the work queue for that (bopId,partitionId). final List<IChunkMessage<IBindingSet>> messages = new LinkedList<IChunkMessage<IBindingSet>>(); queue.drainTo(messages); final int nmessages = messages.size(); - /* - * Combine the messages into a single source to be consumed by a - * task. - */ - int nchunks = 1; - final IMultiSourceAsynchronousIterator<IBindingSet[]> source = new MultiSourceSequentialAsynchronousIterator<IBindingSet[]>(messages.remove(0).getChunkAccessor().iterator()); + /* + * Combine the messages into a single source to be consumed by a + * task. + * + * @todo We could limit the #of chunks combined here by leaving the + * rest on the work queue. + */ +// int nchunks = 1; + final IMultiSourceAsynchronousIterator<IBindingSet[]> source = new MultiSourceSequentialAsynchronousIterator<IBindingSet[]>(// + messages.remove(0).getChunkAccessor().iterator()// + ); for (IChunkMessage<IBindingSet> msg : messages) { source.add(msg.getChunkAccessor().iterator()); - nchunks++; +// nchunks++; } /* * Create task to consume that source. @@ -748,6 +815,9 @@ /* * Submit task for execution (asynchronous). */ + if (log.isDebugEnabled()) + log.debug("Running task: bop=" + bundle.bopId + ", nmessages=" + + nmessages); getQueryEngine().execute(cft); return true; } finally { @@ -1409,9 +1479,6 @@ */ synchronized (this) { - if (smallChunks == null) - smallChunks = new LinkedList<IBindingSet[]>(); - if (chunkSize + e.length > chunkCapacity) { // flush the buffer first. @@ -1419,6 +1486,9 @@ } + if (smallChunks == null) + smallChunks = new LinkedList<IBindingSet[]>(); + smallChunks.add(e); chunkSize += e.length; Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/IRunningQuery.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/IRunningQuery.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/IRunningQuery.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -9,15 +9,15 @@ lic...@bi... This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by +it under the terms of the GNU General License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +GNU General License for more details. -You should have received a copy of the GNU General Public License +You should have received a copy of the GNU General License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ @@ -114,26 +114,45 @@ * @return The query deadline (milliseconds since the epoch) and * {@link Long#MAX_VALUE} if no explicit deadline was specified. */ - public long getDeadline(); + long getDeadline(); /** * The timestamp (ms) when the query began execution. */ - public long getStartTime(); + long getStartTime(); /** * The timestamp (ms) when the query was done and ZERO (0) if the query is * not yet done. */ - public long getDoneTime(); + long getDoneTime(); /** * The elapsed time (ms) for the query. This will be updated for each call * until the query is done executing. */ - public long getElapsed(); - - /** + long getElapsed(); + +// /** +// * Return <code>true</code> if there are no operators which could +// * (re-)trigger the specified operator. +// * <p> +// * Note: This is intended to be invoked synchronously from within the +// * evaluation of the operator in order to determine whether or not the +// * operator can be invoked again for this running query. +// * +// * @param bopId +// * The specified operator. +// * @param nconsumed +// * The #of {@link IChunkMessage} consumed by the operator during +// * its current invocation. +// * +// * @return <code>true</code> iff it is not possible for the specified +// * operator to be retriggered. +// */ +// boolean isLastInvocation(final int bopId,final int nconsumed); + +// /** // * Cancel the running query (normal termination). // * <p> // * Note: This method provides a means for an operator to indicate that the Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/PipelineUtility.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/PipelineUtility.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/PipelineUtility.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -47,42 +47,45 @@ private static final Logger log = Logger.getLogger(PipelineUtility.class); - /** - * Return <code>true</code> iff <i>availableChunkMap</i> map is ZERO (0) for - * the given operator and its descendants AND the <i>runningCountMap</i> is - * ZERO (0) for the operator and all descendants of the operator. For the - * purposes of this method, only {@link BOp#args() operands} are considered - * as descendants. - * <p> - * Note: The movement of the intermediate binding set chunks during query - * processing forms an acyclic directed graph. We can decide whether or not - * a {@link BOp} in the query plan can be triggered by the current activity - * pattern by inspecting the {@link BOp} and its operands recursively. - * - * @param bopId - * The identifier for an operator which appears in the query - * plan. - * @param queryPlan - * The query plan. - * @param queryIndex - * An index for the query plan as constructed by - * {@link BOpUtility#getIndex(BOp)}. - * @param runningCountMap - * A map reporting the #of instances of each operator which are - * currently being evaluated (distinct evaluations are performed - * for each chunk and shard). - * @param availableChunkCountMap - * A map reporting the #of chunks available for each operator in - * the pipeline (we only report chunks for pipeline operators). - * - * @return <code>true</code> iff the {@link BOp} can not be triggered given - * the query plan and the activity map. - * - * @throws IllegalArgumentException - * if any argument is <code>null</code>. - * @throws NoSuchBOpException - * if <i>bopId</i> is not found in the query index. - */ + /** + * Return <code>true</code> iff the running query state is such that it is + * no longer possible for an operator to run which could cause solutions to + * be propagated to the operator identified by the <i>bopId</i>. + * Specifically, this returns true iff <i>availableChunkMap</i> map is ZERO + * (0) for the given operator and its descendants AND the + * <i>runningCountMap</i> is ZERO (0) for the operator and all descendants + * of the operator. For the purposes of this method, only {@link BOp#args() + * operands} are considered as descendants. + * <p> + * Note: The movement of the intermediate binding set chunks during query + * processing forms an acyclic directed graph. We can decide whether or not + * a {@link BOp} in the query plan can be triggered by the current activity + * pattern by inspecting the {@link BOp} and its operands recursively. + * + * @param bopId + * The identifier for an operator which appears in the query + * plan. + * @param queryPlan + * The query plan. + * @param queryIndex + * An index for the query plan as constructed by + * {@link BOpUtility#getIndex(BOp)}. + * @param runningCountMap + * A map reporting the #of instances of each operator which are + * currently being evaluated (distinct evaluations are performed + * for each chunk and shard). + * @param availableChunkCountMap + * A map reporting the #of chunks available for each operator in + * the pipeline (we only report chunks for pipeline operators). + * + * @return <code>true</code> iff the {@link BOp} can not be triggered given + * the query plan and the activity map. + * + * @throws IllegalArgumentException + * if any argument is <code>null</code>. + * @throws NoSuchBOpException + * if <i>bopId</i> is not found in the query index. + */ static public boolean isDone(final int bopId, final BOp queryPlan, final Map<Integer, BOp> queryIndex, final Map<Integer, AtomicLong> runningCountMap, @@ -127,8 +130,8 @@ if (runningCount != null && runningCount.get() != 0) { - if (log.isInfoEnabled()) - log.info("Operator can be triggered: op=" + op + if (log.isDebugEnabled()) + log.debug("Operator can be triggered: op=" + op + ", possible trigger=" + t + " is running."); return false; @@ -150,8 +153,8 @@ if (availableChunkCount != null && availableChunkCount.get() != 0) { - if (log.isInfoEnabled()) - log.info("Operator can be triggered: op=" + op + if (log.isDebugEnabled()) + log.debug("Operator can be triggered: op=" + op + ", possible trigger=" + t + " has " + availableChunkCount + " chunks available."); @@ -170,4 +173,145 @@ } + /** + * Return <code>true</code> iff the running query state is such that the + * "at-once" evaluation of the specified operator may proceed. The specific + * requirements are: (a) the operator is not running and has not been + * started; (b) no predecessor in the pipeline is running; and (c) no + * predecessor in the pipeline can be triggered. + * + * @param bopId + * The identifier for an operator which appears in the query + * plan. + * @param queryPlan + * The query plan. + * @param queryIndex + * An index for the query plan as constructed by + * {@link BOpUtility#getIndex(BOp)}. + * @param runningCountMap + * A map reporting the #of instances of each operator which are + * currently being evaluated (distinct evaluations are performed + * for each chunk and shard). + * @param availableChunkCountMap + * A map reporting the #of chunks available for each operator in + * the pipeline (we only report chunks for pipeline operators). + * + * @return <code>true</code> iff the "at-once" evaluation of the operator + * may proceed. + * + * @throws IllegalArgumentException + * if any argument is <code>null</code>. + * @throws NoSuchBOpException + * if <i>bopId</i> is not found in the query index. + * + * TODO Unit tests. + */ + static public boolean isAtOnceReady(final int bopId, final BOp queryPlan, + final Map<Integer, BOp> queryIndex, + final Map<Integer, AtomicLong> runningCountMap, + final Map<Integer, AtomicLong> availableChunkCountMap) { + + if (queryPlan == null) + throw new IllegalArgumentException(); + + if (queryIndex == null) + throw new IllegalArgumentException(); + + if (availableChunkCountMap == null) + throw new IllegalArgumentException(); + + final BOp op = queryIndex.get(bopId); + + if (op == null) + throw new NoSuchBOpException(bopId); + + final boolean didStart = runningCountMap.get(bopId) != null; + + if(didStart) { + + // Evaluation has already run (or begun) for this operator. + if (log.isInfoEnabled()) + log.info("Already ran/running: " + bopId); + + return false; + + } + + final Iterator<BOp> itr = BOpUtility.preOrderIterator(op); + + while (itr.hasNext()) { + + final BOp t = itr.next(); + + final Integer id = (Integer) t.getProperty(BOp.Annotations.BOP_ID); + + if (id == null) // TODO Why allow ops w/o bopId here? + continue; + + if(bopId == id.intValue()) { + + // Ignore self. + continue; + + } + + { + + /* + * If any descendants (aka predecessors) of the operator are + * running, then they could cause produce additional solutions + * so the operator is not ready for "at-once" evaluation. + */ + + final AtomicLong runningCount = runningCountMap.get(id); + + if (runningCount != null && runningCount.get() != 0) { + + if (log.isDebugEnabled()) + log.debug("Predecessor running: predecessorId=" + id + + ", predecessorRunningCount=" + runningCount); + + return false; + + } + + } + + { + + /* + * Any chunks available for a descendant (aka predecessor) of + * the operator could produce additional solutions as inputs to + * the operator so it is not ready for "at-once" evaluation. + */ + + final AtomicLong availableChunkCount = availableChunkCountMap + .get(id); + + if (availableChunkCount != null + && availableChunkCount.get() != 0) { + /* + * We are looking at some other predecessor of the specified + * operator. + */ + if (log.isDebugEnabled()) + log.debug("Predecessor can be triggered: predecessorId=" + + id + " has " + availableChunkCount + + " chunks available."); + + return false; + } + + } + + } + + // Success. + if (log.isInfoEnabled()) + log.info("Ready for 'at-once' evaluation: " + bopId); + + return true; + + } + } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/RunState.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/RunState.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/RunState.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -541,7 +541,7 @@ * Since it is defacto done when [isAllDone] is satisfied, this tests * for that condition first and then for isOperatorDone(). */ - final boolean isOpDone = isAllDone||isOperatorDone(msg.bopId); + final boolean isOpDone = isAllDone || isOperatorDone(msg.bopId); // if (isAllDone && !isOpDone) // throw new RuntimeException("Whoops!: "+this); @@ -575,23 +575,24 @@ } - /** - * Return <code>true</code> the specified operator can no longer be - * triggered by the query. The specific criteria are that no operators which - * are descendants of the specified operator are running or have chunks - * available against which they could run. Under those conditions it is not - * possible for a chunk to show up which would cause the operator to be - * executed. - * - * @param bopId - * Some operator identifier. - * - * @return <code>true</code> if the operator can not be triggered given the - * current query activity. - * - * @throws IllegalMonitorStateException - * unless the {@link #runStateLock} is held by the caller. - */ + /** + * Return <code>true</code> the specified operator can no longer be + * triggered by the query. The specific criteria are that no operators which + * are descendants of the specified operator are running or have chunks + * available against which they could run. Under those conditions it is not + * possible for a chunk to show up which would cause the operator to be + * executed. + * <p> + * Note: The caller MUST hold a lock across this operation in order for it + * to be atomic with respect to the concurrent evaluation of other operators + * for the same query. + * + * @param bopId + * Some operator identifier. + * + * @return <code>true</code> if the operator can not be triggered given the + * current query activity. + */ private boolean isOperatorDone(final int bopId) { return PipelineUtility.isDone(bopId, query, bopIndex, runningMap, @@ -599,6 +600,24 @@ } + /** + * Return <code>true</code> iff the preconditions have been satisfied for + * the "at-once" invocation of the specified operator (no predecessors are + * running or could be triggered and the operator has not been evaluated). + * + * @param bopId + * Some operator identifier. + * + * @return <code>true</code> iff the "at-once" evaluation of the operator + * may proceed. + */ + boolean isAtOnceReady(final int bopId) { + + return PipelineUtility.isAtOnceReady(bopId, query, bopIndex, + runningMap, availableMap); + + } + /** * Update the {@link RunState} to reflect that fact that a new evaluation * phase has begun for an operator. Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StandaloneChainedRunningQuery.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StandaloneChainedRunningQuery.java 2011-02-08 17:42:19 UTC (rev 4183) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StandaloneChainedRunningQuery.java 2011-02-08 17:50:40 UTC (rev 4184) @@ -27,7 +27,6 @@ package com.bigdata.bop.engine; -import java.nio.channels.ClosedByInterruptException; import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; @@ -46,11 +45,9 @@ import com.bigdata.bop.NoSuchBOpException; import com.bigdata.bop.PipelineOp; import com.bigdata.relation.accesspath.BlockingBuffer; -import com.bigdata.relation.accesspath.BufferClosedException; import com.bigdata.relation.accesspath.IAsynchronousIterator; import com.bigdata.relation.accesspath.IBlockingBuffer; import com.bigdata.relation.accesspath.MultiplexBlockingBuffer; -import com.bigdata.util.InnerCause; /** * An {@link IRunningQuery} implementation for a standalone database in which a @@ -68,7 +65,9 @@ * sources for the sink have been closed. * <p> * This implementation does not use {@link IChunkMessage}s, can not be used with - * scale-out, and does not support sharded indices. + * scale-out, and does not support sharded indices. This implementation ONLY + * supports pipelined operators. If "at-once" evaluation semantics are required, + * then use {@link ChunkedRunningQuery}. * * @todo Since each operator task runs exactly once there is less potential * parallelism in the operator task execution when compared to @@ -81,8 +80,16 @@ * @todo Run all unit tests of the query engine against the appropriate * strategies. * + * @todo Since this class does not support "at-once" evaluation, it can not be + * used with ORDER BY operator implementations. That really restricts its + * ... [truncated message content] |
From: <tho...@us...> - 2011-02-17 12:56:23
|
Revision: 4205 http://bigdata.svn.sourceforge.net/bigdata/?rev=4205&view=rev Author: thompsonbry Date: 2011-02-17 12:56:14 +0000 (Thu, 17 Feb 2011) Log Message: ----------- Initial check in of the LinkedBlockingQueue, LinkedBlockingDeque and their test suites from the JSR 166 sources. The source files are in the public domain per the author's declaration, which is replicated below: /* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/licenses/publicdomain */ These files are being introduced so that we may address certain patterns, such as found in BlockingBuffer and ChunkedRunningQuery, where nested locks occur which lead to deadlock if we use a blocking operation on the queue and otherwise lead to unsatisfactory designs involving polling and retries on the queue. The files as checked in have been modified solely to resolve dependencies and (in the case of the unit tests) to comment out some unused methods which depend on Java 1.7 features. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/TestAll.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/util/concurrent/TestAll.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingQueue.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/package.html branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/BlockingQueueTest.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/JSR166TestCase.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/LinkedBlockingDequeTest.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/LinkedBlockingQueueTest.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/TestAll.java Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java 2011-02-17 12:56:14 UTC (rev 4205) @@ -0,0 +1,1171 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + */ + +package com.bigdata.jsr166; + +import java.util.AbstractQueue; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * An optionally-bounded {@linkplain BlockingDeque blocking deque} based on + * linked nodes. + * + * <p> The optional capacity bound constructor argument serves as a + * way to prevent excessive expansion. The capacity, if unspecified, + * is equal to {@link Integer#MAX_VALUE}. Linked nodes are + * dynamically created upon each insertion unless this would bring the + * deque above capacity. + * + * <p>Most operations run in constant time (ignoring time spent + * blocking). Exceptions include {@link #remove(Object) remove}, + * {@link #removeFirstOccurrence removeFirstOccurrence}, {@link + * #removeLastOccurrence removeLastOccurrence}, {@link #contains + * contains}, {@link #iterator iterator.remove()}, and the bulk + * operations, all of which run in linear time. + * + * <p>This class and its iterator implement all of the + * <em>optional</em> methods of the {@link Collection} and {@link + * Iterator} interfaces. + * + * <p>This class is a member of the + * <a href="{@docRoot}/../technotes/guides/collections/index.html"> + * Java Collections Framework</a>. + * + * @since 1.6 + * @author Doug Lea + * @param <E> the type of elements held in this collection + */ +public class LinkedBlockingDeque<E> + extends AbstractQueue<E> + implements BlockingDeque<E>, java.io.Serializable { + + /* + * Implemented as a simple doubly-linked list protected by a + * single lock and using conditions to manage blocking. + * + * To implement weakly consistent iterators, it appears we need to + * keep all Nodes GC-reachable from a predecessor dequeued Node. + * That would cause two problems: + * - allow a rogue Iterator to cause unbounded memory retention + * - cause cross-generational linking of old Nodes to new Nodes if + * a Node was tenured while live, which generational GCs have a + * hard time dealing with, causing repeated major collections. + * However, only non-deleted Nodes need to be reachable from + * dequeued Nodes, and reachability does not necessarily have to + * be of the kind understood by the GC. We use the trick of + * linking a Node that has just been dequeued to itself. Such a + * self-link implicitly means to jump to "first" (for next links) + * or "last" (for prev links). + */ + + /* + * We have "diamond" multiple interface/abstract class inheritance + * here, and that introduces ambiguities. Often we want the + * BlockingDeque javadoc combined with the AbstractQueue + * implementation, so a lot of method specs are duplicated here. + */ + + private static final long serialVersionUID = -387911632671998426L; + + /** Doubly-linked list node class */ + static final class Node<E> { + /** + * The item, or null if this node has been removed. + */ + E item; + + /** + * One of: + * - the real predecessor Node + * - this Node, meaning the predecessor is tail + * - null, meaning there is no predecessor + */ + Node<E> prev; + + /** + * One of: + * - the real successor Node + * - this Node, meaning the successor is head + * - null, meaning there is no successor + */ + Node<E> next; + + Node(E x) { + item = x; + } + } + + /** + * Pointer to first node. + * Invariant: (first == null && last == null) || + * (first.prev == null && first.item != null) + */ + transient Node<E> first; + + /** + * Pointer to last node. + * Invariant: (first == null && last == null) || + * (last.next == null && last.item != null) + */ + transient Node<E> last; + + /** Number of items in the deque */ + private transient int count; + + /** Maximum number of items in the deque */ + private final int capacity; + + /** Main lock guarding all access */ + final ReentrantLock lock = new ReentrantLock(); + + /** Condition for waiting takes */ + private final Condition notEmpty = lock.newCondition(); + + /** Condition for waiting puts */ + private final Condition notFull = lock.newCondition(); + + /** + * Creates a {@code LinkedBlockingDeque} with a capacity of + * {@link Integer#MAX_VALUE}. + */ + public LinkedBlockingDeque() { + this(Integer.MAX_VALUE); + } + + /** + * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity. + * + * @param capacity the capacity of this deque + * @throws IllegalArgumentException if {@code capacity} is less than 1 + */ + public LinkedBlockingDeque(int capacity) { + if (capacity <= 0) throw new IllegalArgumentException(); + this.capacity = capacity; + } + + /** + * Creates a {@code LinkedBlockingDeque} with a capacity of + * {@link Integer#MAX_VALUE}, initially containing the elements of + * the given collection, added in traversal order of the + * collection's iterator. + * + * @param c the collection of elements to initially contain + * @throws NullPointerException if the specified collection or any + * of its elements are null + */ + public LinkedBlockingDeque(Collection<? extends E> c) { + this(Integer.MAX_VALUE); + final ReentrantLock lock = this.lock; + lock.lock(); // Never contended, but necessary for visibility + try { + for (E e : c) { + if (e == null) + throw new NullPointerException(); + if (!linkLast(new Node<E>(e))) + throw new IllegalStateException("Deque full"); + } + } finally { + lock.unlock(); + } + } + + + // Basic linking and unlinking operations, called only while holding lock + + /** + * Links node as first element, or returns false if full. + */ + private boolean linkFirst(Node<E> node) { + // assert lock.isHeldByCurrentThread(); + if (count >= capacity) + return false; + Node<E> f = first; + node.next = f; + first = node; + if (last == null) + last = node; + else + f.prev = node; + ++count; + notEmpty.signal(); + return true; + } + + /** + * Links node as last element, or returns false if full. + */ + private boolean linkLast(Node<E> node) { + // assert lock.isHeldByCurrentThread(); + if (count >= capacity) + return false; + Node<E> l = last; + node.prev = l; + last = node; + if (first == null) + first = node; + else + l.next = node; + ++count; + notEmpty.signal(); + return true; + } + + /** + * Removes and returns first element, or null if empty. + */ + private E unlinkFirst() { + // assert lock.isHeldByCurrentThread(); + Node<E> f = first; + if (f == null) + return null; + Node<E> n = f.next; + E item = f.item; + f.item = null; + f.next = f; // help GC + first = n; + if (n == null) + last = null; + else + n.prev = null; + --count; + notFull.signal(); + return item; + } + + /** + * Removes and returns last element, or null if empty. + */ + private E unlinkLast() { + // assert lock.isHeldByCurrentThread(); + Node<E> l = last; + if (l == null) + return null; + Node<E> p = l.prev; + E item = l.item; + l.item = null; + l.prev = l; // help GC + last = p; + if (p == null) + first = null; + else + p.next = null; + --count; + notFull.signal(); + return item; + } + + /** + * Unlinks x. + */ + void unlink(Node<E> x) { + // assert lock.isHeldByCurrentThread(); + Node<E> p = x.prev; + Node<E> n = x.next; + if (p == null) { + unlinkFirst(); + } else if (n == null) { + unlinkLast(); + } else { + p.next = n; + n.prev = p; + x.item = null; + // Don't mess with x's links. They may still be in use by + // an iterator. + --count; + notFull.signal(); + } + } + + // BlockingDeque methods + + /** + * @throws IllegalStateException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public void addFirst(E e) { + if (!offerFirst(e)) + throw new IllegalStateException("Deque full"); + } + + /** + * @throws IllegalStateException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public void addLast(E e) { + if (!offerLast(e)) + throw new IllegalStateException("Deque full"); + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + public boolean offerFirst(E e) { + if (e == null) throw new NullPointerException(); + Node<E> node = new Node<E>(e); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return linkFirst(node); + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + public boolean offerLast(E e) { + if (e == null) throw new NullPointerException(); + Node<E> node = new Node<E>(e); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return linkLast(node); + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException {@inheritDoc} + * @throws InterruptedException {@inheritDoc} + */ + public void putFirst(E e) throws InterruptedException { + if (e == null) throw new NullPointerException(); + Node<E> node = new Node<E>(e); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + while (!linkFirst(node)) + notFull.await(); + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException {@inheritDoc} + * @throws InterruptedException {@inheritDoc} + */ + public void putLast(E e) throws InterruptedException { + if (e == null) throw new NullPointerException(); + Node<E> node = new Node<E>(e); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + while (!linkLast(node)) + notFull.await(); + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException {@inheritDoc} + * @throws InterruptedException {@inheritDoc} + */ + public boolean offerFirst(E e, long timeout, TimeUnit unit) + throws InterruptedException { + if (e == null) throw new NullPointerException(); + Node<E> node = new Node<E>(e); + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + while (!linkFirst(node)) { + if (nanos <= 0) + return false; + nanos = notFull.awaitNanos(nanos); + } + return true; + } finally { + lock.unlock(); + } + } + + /** + * @throws NullPointerException {@inheritDoc} + * @throws InterruptedException {@inheritDoc} + */ + public boolean offerLast(E e, long timeout, TimeUnit unit) + throws InterruptedException { + if (e == null) throw new NullPointerException(); + Node<E> node = new Node<E>(e); + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + while (!linkLast(node)) { + if (nanos <= 0) + return false; + nanos = notFull.awaitNanos(nanos); + } + return true; + } finally { + lock.unlock(); + } + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E removeFirst() { + E x = pollFirst(); + if (x == null) throw new NoSuchElementException(); + return x; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E removeLast() { + E x = pollLast(); + if (x == null) throw new NoSuchElementException(); + return x; + } + + public E pollFirst() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return unlinkFirst(); + } finally { + lock.unlock(); + } + } + + public E pollLast() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return unlinkLast(); + } finally { + lock.unlock(); + } + } + + public E takeFirst() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + E x; + while ( (x = unlinkFirst()) == null) + notEmpty.await(); + return x; + } finally { + lock.unlock(); + } + } + + public E takeLast() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + E x; + while ( (x = unlinkLast()) == null) + notEmpty.await(); + return x; + } finally { + lock.unlock(); + } + } + + public E pollFirst(long timeout, TimeUnit unit) + throws InterruptedException { + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + E x; + while ( (x = unlinkFirst()) == null) { + if (nanos <= 0) + return null; + nanos = notEmpty.awaitNanos(nanos); + } + return x; + } finally { + lock.unlock(); + } + } + + public E pollLast(long timeout, TimeUnit unit) + throws InterruptedException { + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + E x; + while ( (x = unlinkLast()) == null) { + if (nanos <= 0) + return null; + nanos = notEmpty.awaitNanos(nanos); + } + return x; + } finally { + lock.unlock(); + } + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E getFirst() { + E x = peekFirst(); + if (x == null) throw new NoSuchElementException(); + return x; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E getLast() { + E x = peekLast(); + if (x == null) throw new NoSuchElementException(); + return x; + } + + public E peekFirst() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return (first == null) ? null : first.item; + } finally { + lock.unlock(); + } + } + + public E peekLast() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return (last == null) ? null : last.item; + } finally { + lock.unlock(); + } + } + + public boolean removeFirstOccurrence(Object o) { + if (o == null) return false; + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Node<E> p = first; p != null; p = p.next) { + if (o.equals(p.item)) { + unlink(p); + return true; + } + } + return false; + } finally { + lock.unlock(); + } + } + + public boolean removeLastOccurrence(Object o) { + if (o == null) return false; + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Node<E> p = last; p != null; p = p.prev) { + if (o.equals(p.item)) { + unlink(p); + return true; + } + } + return false; + } finally { + lock.unlock(); + } + } + + // BlockingQueue methods + + /** + * Inserts the specified element at the end of this deque unless it would + * violate capacity restrictions. When using a capacity-restricted deque, + * it is generally preferable to use method {@link #offer(Object) offer}. + * + * <p>This method is equivalent to {@link #addLast}. + * + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws NullPointerException if the specified element is null + */ + public boolean add(E e) { + addLast(e); + return true; + } + + /** + * @throws NullPointerException if the specified element is null + */ + public boolean offer(E e) { + return offerLast(e); + } + + /** + * @throws NullPointerException {@inheritDoc} + * @throws InterruptedException {@inheritDoc} + */ + public void put(E e) throws InterruptedException { + putLast(e); + } + + /** + * @throws NullPointerException {@inheritDoc} + * @throws InterruptedException {@inheritDoc} + */ + public boolean offer(E e, long timeout, TimeUnit unit) + throws InterruptedException { + return offerLast(e, timeout, unit); + } + + /** + * Retrieves and removes the head of the queue represented by this deque. + * This method differs from {@link #poll poll} only in that it throws an + * exception if this deque is empty. + * + * <p>This method is equivalent to {@link #removeFirst() removeFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException if this deque is empty + */ + public E remove() { + return removeFirst(); + } + + public E poll() { + return pollFirst(); + } + + public E take() throws InterruptedException { + return takeFirst(); + } + + public E poll(long timeout, TimeUnit unit) throws InterruptedException { + return pollFirst(timeout, unit); + } + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque. This method differs from {@link #peek peek} only in that + * it throws an exception if this deque is empty. + * + * <p>This method is equivalent to {@link #getFirst() getFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException if this deque is empty + */ + public E element() { + return getFirst(); + } + + public E peek() { + return peekFirst(); + } + + /** + * Returns the number of additional elements that this deque can ideally + * (in the absence of memory or resource constraints) accept without + * blocking. This is always equal to the initial capacity of this deque + * less the current {@code size} of this deque. + * + * <p>Note that you <em>cannot</em> always tell if an attempt to insert + * an element will succeed by inspecting {@code remainingCapacity} + * because it may be the case that another thread is about to + * insert or remove an element. + */ + public int remainingCapacity() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return capacity - count; + } finally { + lock.unlock(); + } + } + + /** + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + public int drainTo(Collection<? super E> c) { + return drainTo(c, Integer.MAX_VALUE); + } + + /** + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + public int drainTo(Collection<? super E> c, int maxElements) { + if (c == null) + throw new NullPointerException(); + if (c == this) + throw new IllegalArgumentException(); + final ReentrantLock lock = this.lock; + lock.lock(); + try { + int n = Math.min(maxElements, count); + for (int i = 0; i < n; i++) { + c.add(first.item); // In this order, in case add() throws. + unlinkFirst(); + } + return n; + } finally { + lock.unlock(); + } + } + + // Stack methods + + /** + * @throws IllegalStateException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public void push(E e) { + addFirst(e); + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E pop() { + return removeFirst(); + } + + // Collection methods + + /** + * Removes the first occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element {@code e} such that + * {@code o.equals(e)} (if such an element exists). + * Returns {@code true} if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * <p>This method is equivalent to + * {@link #removeFirstOccurrence(Object) removeFirstOccurrence}. + * + * @param o element to be removed from this deque, if present + * @return {@code true} if this deque changed as a result of the call + */ + public boolean remove(Object o) { + return removeFirstOccurrence(o); + } + + /** + * Returns the number of elements in this deque. + * + * @return the number of elements in this deque + */ + public int size() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return count; + } finally { + lock.unlock(); + } + } + + /** + * Returns {@code true} if this deque contains the specified element. + * More formally, returns {@code true} if and only if this deque contains + * at least one element {@code e} such that {@code o.equals(e)}. + * + * @param o object to be checked for containment in this deque + * @return {@code true} if this deque contains the specified element + */ + public boolean contains(Object o) { + if (o == null) return false; + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Node<E> p = first; p != null; p = p.next) + if (o.equals(p.item)) + return true; + return false; + } finally { + lock.unlock(); + } + } + + /* + * TODO: Add support for more efficient bulk operations. + * + * We don't want to acquire the lock for every iteration, but we + * also want other threads a chance to interact with the + * collection, especially when count is close to capacity. + */ + +// /** +// * Adds all of the elements in the specified collection to this +// * queue. Attempts to addAll of a queue to itself result in +// * {@code IllegalArgumentException}. Further, the behavior of +// * this operation is undefined if the specified collection is +// * modified while the operation is in progress. +// * +// * @param c collection containing elements to be added to this queue +// * @return {@code true} if this queue changed as a result of the call +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException {@inheritDoc} +// * @throws IllegalArgumentException {@inheritDoc} +// * @throws IllegalStateException {@inheritDoc} +// * @see #add(Object) +// */ +// public boolean addAll(Collection<? extends E> c) { +// if (c == null) +// throw new NullPointerException(); +// if (c == this) +// throw new IllegalArgumentException(); +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// boolean modified = false; +// for (E e : c) +// if (linkLast(e)) +// modified = true; +// return modified; +// } finally { +// lock.unlock(); +// } +// } + + /** + * Returns an array containing all of the elements in this deque, in + * proper sequence (from first to last element). + * + * <p>The returned array will be "safe" in that no references to it are + * maintained by this deque. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + * <p>This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this deque + */ + @SuppressWarnings("unchecked") + public Object[] toArray() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + Object[] a = new Object[count]; + int k = 0; + for (Node<E> p = first; p != null; p = p.next) + a[k++] = p.item; + return a; + } finally { + lock.unlock(); + } + } + + /** + * Returns an array containing all of the elements in this deque, in + * proper sequence; the runtime type of the returned array is that of + * the specified array. If the deque fits in the specified array, it + * is returned therein. Otherwise, a new array is allocated with the + * runtime type of the specified array and the size of this deque. + * + * <p>If this deque fits in the specified array with room to spare + * (i.e., the array has more elements than this deque), the element in + * the array immediately following the end of the deque is set to + * {@code null}. + * + * <p>Like the {@link #toArray()} method, this method acts as bridge between + * array-based and collection-based APIs. Further, this method allows + * precise control over the runtime type of the output array, and may, + * under certain circumstances, be used to save allocation costs. + * + * <p>Suppose {@code x} is a deque known to contain only strings. + * The following code can be used to dump the deque into a newly + * allocated array of {@code String}: + * + * <pre> + * String[] y = x.toArray(new String[0]);</pre> + * + * Note that {@code toArray(new Object[0])} is identical in function to + * {@code toArray()}. + * + * @param a the array into which the elements of the deque are to + * be stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose + * @return an array containing all of the elements in this deque + * @throws ArrayStoreException if the runtime type of the specified array + * is not a supertype of the runtime type of every element in + * this deque + * @throws NullPointerException if the specified array is null + */ + @SuppressWarnings("unchecked") + public <T> T[] toArray(T[] a) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + if (a.length < count) + a = (T[])java.lang.reflect.Array.newInstance + (a.getClass().getComponentType(), count); + + int k = 0; + for (Node<E> p = first; p != null; p = p.next) + a[k++] = (T)p.item; + if (a.length > k) + a[k] = null; + return a; + } finally { + lock.unlock(); + } + } + + public String toString() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + Node<E> p = first; + if (p == null) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + E e = p.item; + sb.append(e == this ? "(this Collection)" : e); + p = p.next; + if (p == null) + return sb.append(']').toString(); + sb.append(',').append(' '); + } + } finally { + lock.unlock(); + } + } + + /** + * Atomically removes all of the elements from this deque. + * The deque will be empty after this call returns. + */ + public void clear() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + for (Node<E> f = first; f != null; ) { + f.item = null; + Node<E> n = f.next; + f.prev = null; + f.next = null; + f = n; + } + first = last = null; + count = 0; + notFull.signalAll(); + } finally { + lock.unlock(); + } + } + + /** + * Returns an iterator over the elements in this deque in proper sequence. + * The elements will be returned in order from first (head) to last (tail). + * + * <p>The returned iterator is a "weakly consistent" iterator that + * will never throw {@link java.util.ConcurrentModificationException + * ConcurrentModificationException}, and guarantees to traverse + * elements as they existed upon construction of the iterator, and + * may (but is not guaranteed to) reflect any modifications + * subsequent to construction. + * + * @return an iterator over the elements in this deque in proper sequence + */ + public Iterator<E> iterator() { + return new Itr(); + } + + /** + * Returns an iterator over the elements in this deque in reverse + * sequential order. The elements will be returned in order from + * last (tail) to first (head). + * + * <p>The returned iterator is a "weakly consistent" iterator that + * will never throw {@link java.util.ConcurrentModificationException + * ConcurrentModificationException}, and guarantees to traverse + * elements as they existed upon construction of the iterator, and + * may (but is not guaranteed to) reflect any modifications + * subsequent to construction. + * + * @return an iterator over the elements in this deque in reverse order + */ + public Iterator<E> descendingIterator() { + return new DescendingItr(); + } + + /** + * Base class for Iterators for LinkedBlockingDeque + */ + private abstract class AbstractItr implements Iterator<E> { + /** + * The next node to return in next() + */ + Node<E> next; + + /** + * nextItem holds on to item fields because once we claim that + * an element exists in hasNext(), we must return item read + * under lock (in advance()) even if it was in the process of + * being removed when hasNext() was called. + */ + E nextItem; + + /** + * Node returned by most recent call to next. Needed by remove. + * Reset to null if this element is deleted by a call to remove. + */ + private Node<E> lastRet; + + abstract Node<E> firstNode(); + abstract Node<E> nextNode(Node<E> n); + + AbstractItr() { + // set to initial position + final ReentrantLock lock = LinkedBlockingDeque.this.lock; + lock.lock(); + try { + next = firstNode(); + nextItem = (next == null) ? null : next.item; + } finally { + lock.unlock(); + } + } + + /** + * Returns the successor node of the given non-null, but + * possibly previously deleted, node. + */ + private Node<E> succ(Node<E> n) { + // Chains of deleted nodes ending in null or self-links + // are possible if multiple interior nodes are removed. + for (;;) { + Node<E> s = nextNode(n); + if (s == null) + return null; + else if (s.item != null) + return s; + else if (s == n) + return firstNode(); + else + n = s; + } + } + + /** + * Advances next. + */ + void advance() { + final ReentrantLock lock = LinkedBlockingDeque.this.lock; + lock.lock(); + try { + // assert next != null; + next = succ(next); + nextItem = (next == null) ? null : next.item; + } finally { + lock.unlock(); + } + } + + public boolean hasNext() { + return next != null; + } + + public E next() { + if (next == null) + throw new NoSuchElementException(); + lastRet = next; + E x = nextItem; + advance(); + return x; + } + + public void remove() { + Node<E> n = lastRet; + if (n == null) + throw new IllegalStateException(); + lastRet = null; + final ReentrantLock lock = LinkedBlockingDeque.this.lock; + lock.lock(); + try { + if (n.item != null) + unlink(n); + } finally { + lock.unlock(); + } + } + } + + /** Forward iterator */ + private class Itr extends AbstractItr { + Node<E> firstNode() { return first; } + Node<E> nextNode(Node<E> n) { return n.next; } + } + + /** Descending iterator */ + private class DescendingItr extends AbstractItr { + Node<E> firstNode() { return last; } + Node<E> nextNode(Node<E> n) { return n.prev; } + } + + /** + * Save the state of this deque to a stream (that is, serialize it). + * + * @serialData The capacity (int), followed by elements (each an + * {@code Object}) in the proper order, followed by a null + * @param s the stream + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + // Write out capacity and any hidden stuff + s.defaultWriteObject(); + // Write out all elements in the proper order. + for (Node<E> p = first; p != null; p = p.next) + s.writeObject(p.item); + // Use trailing null as sentinel + s.writeObject(null); + } finally { + lock.unlock(); + } + } + + /** + * Reconstitute this deque from a stream (that is, + * deserialize it). + * @param s the stream + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + count = 0; + first = null; + last = null; + // Read in all elements and place in queue + for (;;) { + @SuppressWarnings("unchecked") + E item = (E)s.readObject(); + if (item == null) + break; + add(item); + } + } + +} Property changes on: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java ___________________________________________________________________ Added: svn:keywords + Id Date Revision Author HeadURL Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingQueue.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingQueue.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingQueue.java 2011-02-17 12:56:14 UTC (rev 4205) @@ -0,0 +1,883 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + */ + +package com.bigdata.jsr166; + +import java.util.AbstractQueue; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * An optionally-bounded {@linkplain BlockingQueue blocking queue} based on + * linked nodes. + * This queue orders elements FIFO (first-in-first-out). + * The <em>head</em> of the queue is that element that has been on the + * queue the longest time. + * The <em>tail</em> of the queue is that element that has been on the + * queue the shortest time. New elements + * are inserted at the tail of the queue, and the queue retrieval + * operations obtain elements at the head of the queue. + * Linked queues typically have higher throughput than array-based queues but + * less predictable performance in most concurrent applications. + * + * <p> The optional capacity bound constructor argument serves as a + * way to prevent excessive queue expansion. The capacity, if unspecified, + * is equal to {@link Integer#MAX_VALUE}. Linked nodes are + * dynamically created upon each insertion unless this would bring the + * queue above capacity. + * + * <p>This class and its iterator implement all of the + * <em>optional</em> methods of the {@link Collection} and {@link + * Iterator} interfaces. + * + * <p>This class is a member of the + * <a href="{@docRoot}/../technotes/guides/collections/index.html"> + * Java Collections Framework</a>. + * + * @since 1.5 + * @author Doug Lea + * @param <E> the type of elements held in this collection + * + */ +public class LinkedBlockingQueue<E> extends AbstractQueue<E> + implements BlockingQueue<E>, java.io.Serializable { + private static final long serialVersionUID = -6903933977591709194L; + + /* + * A variant of the "two lock queue" algorithm. The putLock gates + * entry to put (and offer), and has an associated condition for + * waiting puts. Similarly for the takeLock. The "count" field + * that they both rely on is maintained as an atomic to avoid + * needing to get both locks in most cases. Also, to minimize need + * for puts to get takeLock and vice-versa, cascading notifies are + * used. When a put notices that it has enabled at least one take, + * it signals taker. That taker in turn signals others if more + * items have been entered since the signal. And symmetrically for + * takes signalling puts. Operations such as remove(Object) and + * iterators acquire both locks. + * + * Visibility between writers and readers is provided as follows: + * + * Whenever an element is enqueued, the putLock is acquired and + * count updated. A subsequent reader guarantees visibility to the + * enqueued Node by either acquiring the putLock (via fullyLock) + * or by acquiring the takeLock, and then reading n = count.get(); + * this gives visibility to the first n items. + * + * To implement weakly consistent iterators, it appears we need to + * keep all Nodes GC-reachable from a predecessor dequeued Node. + * That would cause two problems: + * - allow a rogue Iterator to cause unbounded memory retention + * - cause cross-generational linking of old Nodes to new Nodes if + * a Node was tenured while live, which generational GCs have a + * hard time dealing with, causing repeated major collections. + * However, only non-deleted Nodes need to be reachable from + * dequeued Nodes, and reachability does not necessarily have to + * be of the kind understood by the GC. We use the trick of + * linking a Node that has just been dequeued to itself. Such a + * self-link implicitly means to advance to head.next. + */ + + /** + * Linked list node class + */ + static class Node<E> { + E item; + + /** + * One of: + * - the real successor Node + * - this Node, meaning the successor is head.next + * - null, meaning there is no successor (this is the last node) + */ + Node<E> next; + + Node(E x) { item = x; } + } + + /** The capacity bound, or Integer.MAX_VALUE if none */ + private final int capacity; + + /** Current number of elements */ + private final AtomicInteger count = new AtomicInteger(0); + + /** + * Head of linked list. + * Invariant: head.item == null + */ + private transient Node<E> head; + + /** + * Tail of linked list. + * Invariant: last.next == null + */ + private transient Node<E> last; + + /** Lock held by take, poll, etc */ + private final ReentrantLock takeLock = new ReentrantLock(); + + /** Wait queue for waiting takes */ + private final Condition notEmpty = takeLock.newCondition(); + + /** Lock held by put, offer, etc */ + private final ReentrantLock putLock = new ReentrantLock(); + + /** Wait queue for waiting puts */ + private final Condition notFull = putLock.newCondition(); + + /** + * Signals a waiting take. Called only from put/offer (which do not + * otherwise ordinarily lock takeLock.) + */ + private void signalNotEmpty() { + final ReentrantLock takeLock = this.takeLock; + takeLock.lock(); + try { + notEmpty.signal(); + } finally { + takeLock.unlock(); + } + } + + /** + * Signals a waiting put. Called only from take/poll. + */ + private void signalNotFull() { + final ReentrantLock putLock = this.putLock; + putLock.lock(); + try { + notFull.signal(); + } finally { + putLock.unlock(); + } + } + + /** + * Links node at end of queue. + * + * @param node the node + */ + private void enqueue(Node<E> node) { + // assert putLock.isHeldByCurrentThread(); + // assert last.next == null; + last = last.next = node; + } + + /** + * Removes a node from head of queue. + * + * @return the node + */ + private E dequeue() { + // assert takeLock.isHeldByCurrentThread(); + // assert head.item == null; + Node<E> h = head; + Node<E> first = h.next; + h.next = h; // help GC + head = first; + E x = first.item; + first.item = null; + return x; + } + + /** + * Lock to prevent both puts and takes. + */ + void fullyLock() { + putLock.lock(); + takeLock.lock(); + } + + /** + * Unlock to allow both puts and takes. + */ + void fullyUnlock() { + takeLock.unlock(); + putLock.unlock(); + } + +// /** +// * Tells whether both locks are held by current thread. +// */ +// boolean isFullyLocked() { +// return (putLock.isHeldByCurrentThread() && +// takeLock.isHeldByCurrentThread()); +// } + + /** + * Creates a {@code LinkedBlockingQueue} with a capacity of + * {@link Integer#MAX_VALUE}. + */ + public LinkedBlockingQueue() { + this(Integer.MAX_VALUE); + } + + /** + * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. + * + * @param capacity the capacity of this queue + * @throws IllegalArgumentException if {@code capacity} is not greater + * than zero + */ + public LinkedBlockingQueue(int capacity) { + if (capacity <= 0) throw new IllegalArgumentException(); + this.capacity = capacity; + last = head = new Node<E>(null); + } + + /** + * Creates a {@code LinkedBlockingQueue} with a capacity of + * {@link Integer#MAX_VALUE}, initially containing the elements of the + * given collection, + * added in traversal order of the collection's iterator. + * + * @param c the collection of elements to initially contain + * @throws NullPointerException if the specified collection or any + * of its elements are null + */ + public LinkedBlockingQueue(Collection<? extends E> c) { + this(Integer.MAX_VALUE); + final ReentrantLock putLock = this.putLock; + putLock.lock(); // Never contended, but necessary for visibility + try { + int n = 0; + for (E e : c) { + if (e == null) + throw new NullPointerException(); + if (n == capacity) + throw new IllegalStateException("Queue full"); + enqueue(new Node<E>(e)); + ++n; + } + count.set(n); + } finally { + putLock.unlock(); + } + } + + + // this doc comment is overridden to remove the reference to collections + // greater in size than Integer.MAX_VALUE + /** + * Returns the number of elements in this queue. + * + * @return the number of elements in this queue + */ + public int size() { + return count.get(); + } + + // this doc comment is a modified copy of the inherited doc comment, + // without the reference to unlimited queues. + /** + * Returns the number of additional elements that this queue can ideally + * (in the absence of memory or resource constraints) accept without + * blocking. This is always equal to the initial capacity of this queue + * less the current {@code size} of this queue. + * + * <p>Note that you <em>cannot</em> always tell if an attempt to insert + * an element will succeed by inspecting {@code remainingCapacity} + * because it may be the case that another thread is about to + * insert or remove an element. + */ + public int remainingCapacity() { + return capacity - count.get(); + } + + /** + * Inserts the specified element at the tail of this queue, waiting if + * necessary for space to become available. + * + * @throws InterruptedException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public void put(E e) throws InterruptedException { + if (e == null) throw new NullPointerException(); + // Note: convention in all put/take/etc is to preset local var + // holding count negative to indicate failure unless set. + int c = -1; + Node<E> node = new Node(e); + final ReentrantLock putLock = this.putLock; + final AtomicInteger count = this.count; + putLock.lockInterruptibly(); + try { + /* + * Note that count is used in wait guard even though it is + * not protected by lock. This works because count can + * only decrease at this point (all other puts are shut + * out by lock), and we (or some other waiting put) are + * signalled if it ever changes from capacity. Similarly + * for all other uses of count in other wait guards. + */ + while (count.get() == capacity) { + notFull.await(); + } + enqueue(node); + c = count.getAndIncrement(); + if (c + 1 < capacity) + notFull.signal(); + } finally { + putLock.unlock(); + } + if (c == 0) + signalNotEmpty(); + } + + /** + * Inserts the specified element at the tail of this queue, waiting if + * necessary up to the specified wait time for space to become available. + * + * @return {@code true} if successful, or {@code false} if + * the specified waiting time elapses before space is available. + * @throws InterruptedException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public boolean offer(E e, long timeout, TimeUnit unit) + throws InterruptedException { + + if (e == null) throw new NullPointerException(); + long nanos = unit.toNanos(timeout); + int c = -1; + final ReentrantLock putLock = this.putLock; + final AtomicInteger count = this.count; + putLock.lockInterruptibly(); + try { + while (count.get() == capacity) { + if (nanos <= 0) + return false; + nanos = notFull.awaitNanos(nanos); + } + enqueue(new Node<E>(e)); + c = count.getAndIncrement(); + if (c + 1 < capacity) + notFull.signal(); + } finally { + putLock.unlock(); + } + if (c == 0) + signalNotEmpty(); + return true; + } + + /** + * Inserts the specified element at the tail of this queue if it is + * possible to do so immediately without exceeding the queue's capacity, + * returning {@code true} upon success and {@code false} if this queue + * is full. + * When using a capacity-restricted queue, this method is generally + * preferable to method {@link BlockingQueue#add add}, which can fail to + * insert an element only by throwing an exception. + * + * @throws NullPointerException if the specified element is null + */ + public boolean offer(E e) { + if (e == null) throw new NullPointerException(); + final AtomicInteger count = this.count; + if (count.get() == capacity) + return false; + int c = -1; + Node<E> node = new Node(e); + final ReentrantLock putLock = this.putLock; + putLock.lock(); + try { + if (count.get() < capacity) { + enqueue(node); + c = count.getAndIncrement(); + if (c + 1 < capacity) + notFull.signal(); + } + } finally { + putLock.unlock(); + } + if (c == 0) + signalNotEmpty(); + return c >= 0; + } + + + public E take() throws InterruptedException { + E x; + int c = -1; + final AtomicInteger count = this.count; + final ReentrantLock takeLock = this.takeLock; + takeLock.lockInterruptibly(); + try { + while (count.get() == 0) { + notEmpty.await(); + } + x = dequeue(); + c = count.getAndDecrement(); + if (c > 1) + notEmpty.signal(); + } finally { + takeLock.unlock(); + } + if (c == capacity) + signalNotFull(); + return x; + } + + public E poll(long timeout, TimeUnit unit) throws InterruptedException { + E x = null; + int c = -1; + long nanos = unit.toNanos(timeout); + final AtomicInteger count = this.count; + final ReentrantLock takeLock = this.takeLock; + takeLock.lockInterruptibly(); + try { + while (count.get() == 0) { + if (nanos <= 0) + return null; + nanos = notEmpty.awaitNanos(nanos); + } + x = dequeue(); + c = count.getAndDecrement(); + if (c > 1) + notEmpty.signal(); + } finally { + takeLock.unlock(); + } + if (c == capacity) + signalNotFull(); + return x; + } + + public E poll() { + final AtomicInteger count = this.count; + if (count.get() == 0) + return null; + E x = null; + int c = -1; + final ReentrantLock takeLock = this.takeLock; + takeLock.lock(); + try { + if (count.get() > 0) { + x = dequeue(); + c = count.getAndDecrement(); + if (c > 1) + notEmpty.signal(); + } + } finally { + takeLock.unlock(); + } + if (c == capacity) + signalNotFull(); + return x; + } + + public E peek() { + if (count.get() == 0) + return null; + final ReentrantLock takeLock = this.takeLock; + takeLock.lock(); + try { + Node<E> first = head.next; + if (first == null) + return null; + else + return first.item; + } finally { + takeLock.unlock(); + } + } + + /** + * Unlinks interior Node p with predecessor trail. + */ + void unlink(Node<E> p, Node<E> trail) { + // assert isFullyLocked(); + // p.next is not changed, to allow iterators that are + // traversing p to maintain their weak-consistency guarantee. + p.item = null; + trail.next = p.next; + if (last == p) + last = trail; + if (count.getAndDecrement() == capacity) + notFull.signal(); + } + + /** + * Removes a single instance of the specified element from this queue, + * if it is present. More formally, removes an element {@code e} such + * that {@code o.equals(e)}, if this queue contains one or more such + * elements. + * Returns {@code true} if this queue contained the specified element + * (or equivalently, if this queue changed as a result of the call). + * + * @param o element to be removed from this queue, if present + * @return {@code true} if this queue changed as a result of the call + */ + public boolean remove(Object o) { + if (o == null) return false; + fullyLock(); + try { + for (Node<E> trail = head, p = trail.next; + p != null; + trail = p, p = p.next) { + if (o.equals(p.item)) { + unlink(p, trail); + return true; + } + } + return false; + } finally { + fullyUnlock(); + } + } + + /** + * Returns {@code true} if this queue contains the specified element. + * More formally, returns {@code true} if and only if this queue contains + * at least one element {@code e} such that {@code o.equals(e)}. + * + * @param o object to be checked for containment in this queue + * @return {@code true} if this queue contains the specified element + */ + public boolean contains(Object o) { + if (o == null) return false; + fullyLock(); + try { + for (Node<E> p = head.next; p != null; p = p.next) + if (o.equals(p.item)) + return true; + return false; + } finally { + fullyUnlock(); + } + } + + /** + * Returns an array containing all of the elements in this queue, in + * proper sequence. + * + * <p>The returned array will be "safe" in that no references to it are + * maintained by this queue. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + * <p>This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this queue + */ + public Object[] toArray() { + fullyLock(); + try { + int size = count.get(); + Object[] a = new Object[size]; + int k = 0; + for (Node<E> p = head.next; p != null; p = p.next) + a[k++] = p.item; + return a; + } finally { + fullyUnlock(); + } + } + + /** + * Returns an array containing all of the elements in this queue, in + * proper sequence; the runtime type of the returned array is that of + * the specified array. If the queue fits in the specified array, it + * is returned therein. Otherwise, a new array is allocated with the + * runtime type of the specified array and the size of this queue. + * + * <p>If this queue fits in the specified array with room to spare + * (i.e., the array has more elements than this queue), the element in + * the array immediately following the end of the queue is set to + * {@code null}. + * + * <p>Like the {@link #t... [truncated message content] |
From: <tho...@us...> - 2011-02-17 13:38:54
|
Revision: 4206 http://bigdata.svn.sourceforge.net/bigdata/?rev=4206&view=rev Author: thompsonbry Date: 2011-02-17 13:38:48 +0000 (Thu, 17 Feb 2011) Log Message: ----------- Added constructor variants for LinkedBlockingDeque which permit the caller to pass in their ReentrantLock. The intention is to support design patterns where an outer lock is used to guard operations both outside of the collection and within the collection. When the collection uses its own lock, this can create a deadlock if the outer class issues concurrent requests which lead to blocking operation on the collection. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/LinkedBlockingDequeTest.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java 2011-02-17 12:56:14 UTC (rev 4205) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/jsr166/LinkedBlockingDeque.java 2011-02-17 13:38:48 UTC (rev 4206) @@ -125,13 +125,13 @@ private final int capacity; /** Main lock guarding all access */ - final ReentrantLock lock = new ReentrantLock(); + final ReentrantLock lock;// = new ReentrantLock(); /** Condition for waiting takes */ - private final Condition notEmpty = lock.newCondition(); + private final Condition notEmpty;// = lock.newCondition(); /** Condition for waiting puts */ - private final Condition notFull = lock.newCondition(); + private final Condition notFull;// = lock.newCondition(); /** * Creates a {@code LinkedBlockingDeque} with a capacity of @@ -142,14 +142,52 @@ } /** + * Creates a {@code LinkedBlockingDeque} with a capacity of + * {@link Integer#MAX_VALUE} using the caller's lock. + */ + public LinkedBlockingDeque(final ReentrantLock lock) { + this(Integer.MAX_VALUE, lock); + } + + /** * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity. * * @param capacity the capacity of this deque * @throws IllegalArgumentException if {@code capacity} is less than 1 */ public LinkedBlockingDeque(int capacity) { + this(capacity, new ReentrantLock()); +// if (capacity <= 0) throw new IllegalArgumentException(); +// this.capacity = capacity; + } + + /** + * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity and + * the caller's {@link ReentrantLock} object. + * <p> + * <strong>Caution:</strong> By using the caller's lock, this constructor + * allows the caller to break the encapsulation of the synchronization and + * lock-based notification (signals). This can be used advantageously to + * create designs where an outer lock is shared by the collection which + * avoid deadlock arising from blocking operations on an inner lock while + * holding a distinct outer lock. However, the caller's decisions about its + * lock are no longer independent of the design decisions within this class + * since they share the same lock. + * + * @param capacity + * the capacity of this deque + * @param lock + * the lock object. + * @throws IllegalArgumentException + * if {@code capacity} is less than 1 + */ + public LinkedBlockingDeque(final int capacity, final ReentrantLock lock) { if (capacity <= 0) throw new IllegalArgumentException(); + if (lock == null) throw new NullPointerException(); this.capacity = capacity; + this.lock = lock; + this.notEmpty = lock.newCondition(); + this.notFull = lock.newCondition(); } /** Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/LinkedBlockingDequeTest.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/LinkedBlockingDequeTest.java 2011-02-17 12:56:14 UTC (rev 4205) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/jsr166/LinkedBlockingDequeTest.java 2011-02-17 13:38:48 UTC (rev 4206) @@ -7,6 +7,8 @@ import junit.framework.*; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.locks.ReentrantLock; + import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.io.*; @@ -326,7 +328,7 @@ */ public void testConstructor3() { try { - LinkedBlockingDeque q = new LinkedBlockingDeque(null); + LinkedBlockingDeque q = new LinkedBlockingDeque((Collection)null); shouldThrow(); } catch (NullPointerException success) {} } @@ -368,6 +370,27 @@ } /** + * Deque constructor with <code>null</code> {@link ReentrantLock} argument + * throws NPE. + */ + public void testConstructor7() { + try { + LinkedBlockingDeque q = new LinkedBlockingDeque(20,null/*lock*/); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from null Lock throws NPE + */ + public void testConstructor8() { + try { + LinkedBlockingDeque q = new LinkedBlockingDeque((ReentrantLock)null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** * Deque transitions from empty to full when elements added */ public void testEmptyFull() { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-02-20 21:20:51
|
Revision: 4211 http://bigdata.svn.sourceforge.net/bigdata/?rev=4211&view=rev Author: thompsonbry Date: 2011-02-20 21:20:44 +0000 (Sun, 20 Feb 2011) Log Message: ----------- Working on unit tests for getSharedVars(), canJoin(), and now canJoinWithConstraints(). Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestAll.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoin.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoinUsingConstraints.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java 2011-02-20 13:50:40 UTC (rev 4210) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java 2011-02-20 21:20:44 UTC (rev 4211) @@ -27,6 +27,7 @@ package com.bigdata.bop; +import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; @@ -39,6 +40,7 @@ import org.apache.log4j.Logger; import com.bigdata.bop.BOp.Annotations; +import com.bigdata.bop.controller.PartitionedJoinGroup; import com.bigdata.bop.engine.BOpStats; import com.bigdata.btree.AbstractNode; import com.bigdata.relation.accesspath.IAsynchronousIterator; @@ -1010,41 +1012,41 @@ } - /** - * Inject (or replace) an {@link Integer} "rowId" column. This does not have - * a side-effect on the source {@link IBindingSet}s. - * - * @param var - * The name of the column. - * @param start - * The starting value for the identifier. - * @param in - * The source {@link IBindingSet}s. - * - * @return The modified {@link IBindingSet}s. - */ - public static IBindingSet[] injectRowIdColumn(final IVariable var, - final int start, final IBindingSet[] in) { +// /** +// * Inject (or replace) an {@link Integer} "rowId" column. This does not have +// * a side-effect on the source {@link IBindingSet}s. +// * +// * @param var +// * The name of the column. +// * @param start +// * The starting value for the identifier. +// * @param in +// * The source {@link IBindingSet}s. +// * +// * @return The modified {@link IBindingSet}s. +// */ +// public static IBindingSet[] injectRowIdColumn(final IVariable<?> var, +// final int start, final IBindingSet[] in) { +// +// if (in == null) +// throw new IllegalArgumentException(); +// +// final IBindingSet[] out = new IBindingSet[in.length]; +// +// for (int i = 0; i < out.length; i++) { +// +// final IBindingSet bset = in[i].clone(); +// +// bset.set(var, new Constant<Integer>(Integer.valueOf(start + i))); +// +// out[i] = bset; +// +// } +// +// return out; +// +// } - if (in == null) - throw new IllegalArgumentException(); - - final IBindingSet[] out = new IBindingSet[in.length]; - - for (int i = 0; i < out.length; i++) { - - final IBindingSet bset = in[i].clone(); - - bset.set(var, new Constant<Integer>(Integer.valueOf(start + i))); - - out[i] = bset; - - } - - return out; - - } - /** * Return an ordered array of the bopIds associated with an ordered array of * predicates (aka a join path). @@ -1077,29 +1079,20 @@ } /** - * Return the variable references shared by tw operators. All variables - * spanned by either {@link BOp} are considered. + * Return the variable references shared by two operators. All variables + * spanned by either {@link BOp} are considered, regardless of whether they + * appear as operands or within annotations. * * @param p * An operator. * @param c * Another operator. * - * @param p - * A predicate. + * @return The variable(s) in common. This may be an empty set, but it is + * never <code>null</code>. * - * @param c - * A constraint. - * - * @return The variables in common -or- <code>null</code> iff there are no - * variables in common. - * * @throws IllegalArgumentException * if the two either reference is <code>null</code>. - * @throws IllegalArgumentException - * if the reference are the same. - * - * @todo unit tests. */ public static Set<IVariable<?>> getSharedVars(final BOp p, final BOp c) { @@ -1109,8 +1102,12 @@ if (c == null) throw new IllegalArgumentException(); - if (p == c) - throw new IllegalArgumentException(); + /* + * Note: This is allowed since both arguments might be the same variable + * or constant. + */ +// if (p == c) +// throw new IllegalArgumentException(); // The set of variables which are shared. final Set<IVariable<?>> sharedVars = new LinkedHashSet<IVariable<?>>(); @@ -1154,4 +1151,285 @@ } + /** + * Return <code>true</code> iff two predicates can join on the basis of at + * least one variable which is shared directly by those predicates. Only the + * operands of the predicates are considered. + * <p> + * Note: This method will only identify joins where the predicates directly + * share at least one variable. However, joins are also possible when the + * predicates share variables via one or more constraint(s). Use + * {@link #canJoinUsingConstraints(IPredicate[], IPredicate, IConstraint[])} + * to identify such joins. + * <p> + * Note: Any two predicates may join regardless of the presence of shared + * variables. However, such joins will produce the full cross product of the + * binding sets selected by each predicate. As such, they should be run last + * and this method will not return <code>true</code> for such predicates. + * <p> + * Note: This method is more efficient than {@link #getSharedVars(BOp, BOp)} + * because it does not materialize the sets of shared variables. However, it + * only considers the operands of the {@link IPredicate}s and is thus more + * restricted than {@link #getSharedVars(BOp, BOp)} as well. + * + * @param p1 + * A predicate. + * @param p2 + * Another predicate. + * + * @return <code>true</code> iff the predicates share at least one variable + * as an operand. + * + * @throws IllegalArgumentException + * if the two either reference is <code>null</code>. + */ +// * @throws IllegalArgumentException +// * if the reference are the same. + static public boolean canJoin(final IPredicate<?> p1, final IPredicate<?> p2) { + + if (p1 == null) + throw new IllegalArgumentException(); + + if (p2 == null) + throw new IllegalArgumentException(); + +// if (p1 == p2) +// throw new IllegalArgumentException(); + + // iterator scanning the operands of p1. + final Iterator<IVariable<?>> itr1 = BOpUtility.getArgumentVariables(p1); + + while (itr1.hasNext()) { + + final IVariable<?> v1 = itr1.next(); + + // iterator scanning the operands of p2. + final Iterator<IVariable<?>> itr2 = BOpUtility + .getArgumentVariables(p2); + + while (itr2.hasNext()) { + + final IVariable<?> v2 = itr2.next(); + + if (v1 == v2) { + + if (log.isDebugEnabled()) + log.debug("Can join: sharedVar=" + v1 + ", p1=" + p1 + + ", p2=" + p2); + + return true; + + } + + } + + } + + if (log.isDebugEnabled()) + log.debug("No directly shared variable: p1=" + p1 + ", p2=" + p2); + + return false; + + } + + /** + * Return <code>true</code> iff a predicate may be used to extend a join + * path on the basis of at least one variable which is shared either + * directly or via one or more constraints which may be attached to the + * predicate when it is added to the join path. The join path is used to + * decide which variables are known to be bound, which in turn decides which + * constraints may be run. Unlike the case when the variable is directly + * shared between the two predicates, a join involving a constraint requires + * us to know which variables are already bound so we can know when the + * constraint may be attached. + * <p> + * Note: Use {@link #canJoin(IPredicate, IPredicate)} instead to identify + * joins based on a variable which is directly shared. + * <p> + * Note: Any two predicates may join regardless of the presence of shared + * variables. However, such joins will produce the full cross product of the + * binding sets selected by each predicate. As such, they should be run last + * and this method will not return <code>true</code> for such predicates. + * + * @param path + * A join path containing at least one predicate. + * @param vertex + * A predicate which is being considered as an extension of that + * join path. + * @param constraints + * A set of zero or more constraints (optional). Constraints are + * attached dynamically once the variables which they use are + * bound. Hence, a constraint will always share a variable with + * any predicate to which it is attached. If any constraints are + * attached to the given vertex and they share a variable which + * has already been bound by the join path, then the vertex may + * join with the join path even if it does not directly bind that + * variable. + * + * @return <code>true</code> iff the vertex can join with the join path via + * a shared variable. + * + * @throws IllegalArgumentException + * if the join path is <code>null</code>. + * @throws IllegalArgumentException + * if the join path is empty. + * @throws IllegalArgumentException + * if any element in the join path is <code>null</code>. + * @throws IllegalArgumentException + * if the vertex is <code>null</code>. + * @throws IllegalArgumentException + * if the vertex is already part of the join path. + * @throws IllegalArgumentException + * if any element in the optional constraints array is + * <code>null</code>. + */ + static public boolean canJoinUsingConstraints(final IPredicate<?>[] path, + final IPredicate<?> vertex, final IConstraint[] constraints) { + + /* + * Check arguments. + */ + if (path == null) + throw new IllegalArgumentException(); + if (vertex == null) + throw new IllegalArgumentException(); + // constraints MAY be null. + if (path.length == 0) + throw new IllegalArgumentException(); + { + for (IPredicate<?> p : path) { + if (p == null) + throw new IllegalArgumentException(); + if (vertex == p) + throw new IllegalArgumentException(); + } + } + + /* + * Find the set of variables which are known to be bound because they + * are referenced as operands of the predicates in the join path. + */ + final Set<IVariable<?>> knownBound = new LinkedHashSet<IVariable<?>>(); + + for (IPredicate<?> p : path) { + + final Iterator<IVariable<?>> vitr = BOpUtility + .getArgumentVariables(p); + + while (vitr.hasNext()) { + + knownBound.add(vitr.next()); + + } + + } + + /* + * + * If the given predicate directly shares a variable with any of the + * predicates in the join path, then we can return immediately. + */ + { + + final Iterator<IVariable<?>> vitr = BOpUtility + .getArgumentVariables(vertex); + + while (vitr.hasNext()) { + + final IVariable<?> var = vitr.next(); + + if(knownBound.contains(var)) { + + if (log.isDebugEnabled()) + log.debug("Can join: sharedVar=" + var + ", path=" + + Arrays.toString(path) + ", vertex=" + vertex); + + return true; + + } + + } + + } + + if(constraints == null) { + + // No opportunity for a constraint based join. + + if (log.isDebugEnabled()) + log.debug("No directly shared variable: path=" + + Arrays.toString(path) + ", vertex=" + vertex); + + return false; + + } + + /* + * Find the set of constraints which can run with the vertex given the + * join path. + */ + { + + // Extend the new join path. + final IPredicate<?>[] newPath = new IPredicate[path.length + 1]; + + System.arraycopy(path/* src */, 0/* srcPos */, newPath/* dest */, + 0/* destPos */, path.length); + + newPath[path.length] = vertex; + + /* + * Find the constraints that will run with each vertex of the new + * join path. + */ + final IConstraint[][] constraintRunArray = PartitionedJoinGroup + .getJoinGraphConstraints(newPath, constraints); + + /* + * Consider only the constraints attached to the last vertex in the + * new join path. All of their variables will be bound since (by + * definition) a constraint may not run until its variables are + * bound. If any of the constraints attached to that last share any + * variables which were already known to be bound in the caller's + * join path, then the vertex can join (without of necessity being + * a full cross product join). + */ + final IConstraint[] vertexConstraints = constraintRunArray[path.length]; + + for (IConstraint c : vertexConstraints) { + + // consider all variables spanned by the constraint. + final Iterator<IVariable<?>> vitr = BOpUtility + .getSpannedVariables(c); + + while (vitr.hasNext()) { + + final IVariable<?> var = vitr.next(); + + if (knownBound.contains(var)) { + + if (log.isDebugEnabled()) + log.debug("Can join: sharedVar=" + var + ", path=" + + Arrays.toString(path) + ", vertex=" + + vertex + ", constraint=" + c); + + return true; + + } + + } + + } + + } + + if (log.isDebugEnabled()) + log.debug("No shared variable: path=" + Arrays.toString(path) + + ", vertex=" + vertex + ", constraints=" + + Arrays.toString(constraints)); + + return false; + + } + } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestAll.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestAll.java 2011-02-20 13:50:40 UTC (rev 4210) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestAll.java 2011-02-20 21:20:44 UTC (rev 4211) @@ -73,6 +73,12 @@ // counting variables, etc. suite.addTestSuite(TestBOpUtility.class); + // unit tests for allowing joins based on shared variables in preds. + suite.addTestSuite(TestBOpUtility_canJoin.class); + + // more complex logic for join paths. + suite.addTestSuite(TestBOpUtility_canJoinUsingConstraints.class); + // constraint operators (EQ, NE, etc). suite.addTest(com.bigdata.bop.constraint.TestAll.suite()); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility.java 2011-02-20 13:50:40 UTC (rev 4210) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility.java 2011-02-20 21:20:44 UTC (rev 4211) @@ -29,11 +29,13 @@ import java.util.Iterator; import java.util.Map; +import java.util.Set; import java.util.concurrent.FutureTask; import junit.framework.TestCase2; -import junit.framework.TestCase2; +import com.bigdata.bop.ap.Predicate; +import com.bigdata.bop.constraint.BOpConstraint; /** * Unit tests for {@link BOpUtility}. @@ -296,7 +298,7 @@ // .annotationOpPreOrderIterator(op2); // while (itr.hasNext()) { // final BOp t = itr.next(); -// System.out.println(i + " : " + t);// @todo uncomment +//// System.out.println(i + " : " + t); //// assertTrue("index=" + i + ", expected=" + expected[i] + ", actual=" //// + t, expected[i].equals(t)); // i++; @@ -828,5 +830,199 @@ } } + + /** + * Unit test for correct rejection of illegal arguments. + * + * @see BOpUtility#getSharedVars(BOp, BOp) + */ + public void test_getSharedVariables_correctRejection() { + + // correct rejection w/ null arg. + try { + BOpUtility.getSharedVars(Var.var("x"), null); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + // correct rejection w/ null arg. + try { + BOpUtility.getSharedVars(null, Var.var("x")); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * Unit test for correct identification of cases in which there are no + * shared variables. + * + * @see BOpUtility#getSharedVars(BOp, BOp) + */ + @SuppressWarnings("unchecked") + public void test_getSharedVariables_nothingShared() { + + // nothing shared. + assertTrue(BOpUtility.getSharedVars(Var.var("x"), Var.var("y")) + .isEmpty()); + + // nothing shared. + assertTrue(BOpUtility.getSharedVars(Var.var("x"), + new Constant<String>("x")).isEmpty()); + + // nothing shared. + assertTrue(BOpUtility.getSharedVars(// + Var.var("x"),// + new Predicate(new BOp[] { Var.var("y"), Var.var("z") },// + (Map) null/* annotations */)// + ).isEmpty()); + + // nothing shared. + assertTrue(BOpUtility.getSharedVars(// + Var.var("x"),// + new Predicate(new BOp[] { Var.var("y"), Var.var("z") },// + new NV("name", "value")// + )).isEmpty()); + + } + + /** + * Unit test for correct identification of cases in which there are shared + * variables. + * + * @see BOpUtility#getSharedVars(BOp, BOp) + */ + @SuppressWarnings("unchecked") + public void test_getSharedVariables_somethingShared() { + + // two variables + assertSameVariables(// + new IVariable[] { Var.var("x") }, // + BOpUtility.getSharedVars(// + Var.var("x"), // + Var.var("x")// + )); + + // variable and expression. + assertSameVariables(// + new IVariable[] { Var.var("x") }, // + BOpUtility.getSharedVars(// + Var.var("x"), // + new BOpBase(// + new BOp[] { new Constant<String>("x"), + Var.var("x") },// + null// annotations + )// + )); + + // expression and variable. + assertSameVariables(// + new IVariable[] { Var.var("x") }, // + BOpUtility.getSharedVars(// + new BOpBase(// + new BOp[] { new Constant<String>("x"), + Var.var("x") },// + null// annotations + ),// + Var.var("x") // + )); + + // variable and predicate w/o annotations. + assertSameVariables(// + new IVariable[] { Var.var("x") }, // + BOpUtility.getSharedVars(// + Var.var("x"),// + new Predicate(new BOp[] { Var.var("y"), Var.var("x") },// + (Map) null/* annotations */)// + )); + + // predicate w/o annotations and variable. + assertSameVariables(// + new IVariable[] { Var.var("x") }, // + BOpUtility.getSharedVars(// + new Predicate(new BOp[] { Var.var("y"), Var.var("x") },// + (Map) null/* annotations */),// + Var.var("x")// + )); + + // variable and predicate w/ annotations (w/o var). + assertSameVariables(// + new IVariable[] { Var.var("x") }, // + BOpUtility.getSharedVars(// + Var.var("x"),// + new Predicate(new BOp[] { Var.var("x"), Var.var("z") },// + new NV("name", "value")// + ))); + + // variable and predicate w/ annotations (w/ same var). + assertSameVariables(// + new IVariable[] { Var.var("x") }, // + BOpUtility.getSharedVars(// + Var.var("x"),// + new Predicate(new BOp[] { Var.var("y"), Var.var("z") },// + new NV("name", Var.var("x"))// + ))); + + // variable and predicate w/ annotations (w/ another var). + assertSameVariables(// + new IVariable[] { /*Var.var("x")*/ }, // + BOpUtility.getSharedVars(// + Var.var("x"),// + new Predicate(new BOp[] { Var.var("y"), Var.var("z") },// + new NV("name", Var.var("z"))// + ))); + + // two predicates + assertSameVariables(// + new IVariable[] { Var.var("y"), Var.var("z") }, // + BOpUtility.getSharedVars(// + new Predicate(new BOp[] { Var.var("y"), Var.var("z") },// + new NV("name", Var.var("z"))// + ), // + new Predicate(new BOp[] { Var.var("y"), Var.var("z") },// + new NV("name", Var.var("x"))// + )// + )); + + // two predicates + assertSameVariables(// + new IVariable[] { Var.var("x"), Var.var("y"), Var.var("z") }, // + BOpUtility.getSharedVars(// + new Predicate(new BOp[] { Var.var("y"), Var.var("x") },// + new NV("name", Var.var("z"))// + ), // + new Predicate(new BOp[] { Var.var("y"), Var.var("z") },// + new NV("name", Var.var("x"))// + )// + )); + + } + /** + * Test helper. + * @param expected The expected variables in any order. + * @param actual A set of variables actually reported. + */ + private static void assertSameVariables(final IVariable<?>[] expected, + final Set<IVariable<?>> actual) { + + for(IVariable<?> var : expected) { + + if(!actual.contains(var)) { + + fail("Expecting: "+var); + + } + + } + + assertEquals("size", expected.length, actual.size()); + + } + } Added: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoin.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoin.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoin.java 2011-02-20 21:20:44 UTC (rev 4211) @@ -0,0 +1,147 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2011. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on Feb 20, 2011 + */ + +package com.bigdata.bop; + +import com.bigdata.bop.ap.Predicate; + +import junit.framework.TestCase2; + +/** + * Unit tests for {@link BOpUtility#canJoin(IPredicate, IPredicate)} + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id$ + */ +public class TestBOpUtility_canJoin extends TestCase2 { + + /** + * + */ + public TestBOpUtility_canJoin() { + } + + /** + * @param name + */ + public TestBOpUtility_canJoin(String name) { + super(name); + } + + + /** + * Correct rejection tests. + * + * @see BOpUtility#canJoin(IPredicate, IPredicate). + */ + @SuppressWarnings("unchecked") + public void test_canJoin_correctRejection() { + + final IVariable<?> x = Var.var("x"); + final IVariable<?> y = Var.var("y"); + final IVariable<?> z = Var.var("z"); + + final IPredicate<?> p1 = new Predicate(new BOp[]{x,y}); + final IPredicate<?> p2 = new Predicate(new BOp[]{y,z}); + + // correct rejection w/ null arg. + try { + BOpUtility.canJoin(null,p2); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + // correct rejection w/ null arg. + try { + BOpUtility.canJoin(p1,null); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * Semantics tests focused on shared variables in the operands. + * + * @see BOpUtility#canJoin(IPredicate, IPredicate) + */ + @SuppressWarnings("unchecked") + public void test_canJoin() { + + final IVariable<?> u = Var.var("u"); + final IVariable<?> x = Var.var("x"); + final IVariable<?> y = Var.var("y"); + final IVariable<?> z = Var.var("z"); + + final IPredicate<?> p1 = new Predicate(new BOp[] { x, y }); + final IPredicate<?> p2 = new Predicate(new BOp[] { y, z }); + final IPredicate<?> p3 = new Predicate(new BOp[] { u, z }); + + // share y + assertTrue(BOpUtility.canJoin(p1, p2)); + + // share z + assertTrue(BOpUtility.canJoin(p2, p3)); + + // share z + assertFalse(BOpUtility.canJoin(p1, p3)); + + // shares (x,y) with self. + assertTrue(BOpUtility.canJoin(p1, p1)); + + } + + /** + * Verify that joins are not permitted when the variables are + * only shared via an annotation. + * + * @see BOpUtility#canJoin(IPredicate, IPredicate) + */ + @SuppressWarnings("unchecked") + public void test_canJoin_annotationsAreIngored() { + + final IVariable<?> x = Var.var("x"); + final IVariable<?> y = Var.var("y"); + final IVariable<?> z = Var.var("z"); + + final IPredicate<?> p1 = new Predicate(new BOp[] { x, },// + new NV("foo", y)// + ); + final IPredicate<?> p2 = new Predicate(new BOp[] { z },// + new NV("foo", y) + ); + + // verify that the variables in the annotations are ignored. + assertFalse(BOpUtility.canJoin(p1, p2)); + + } + +} Property changes on: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoin.java ___________________________________________________________________ Added: svn:keywords + Id Date Revision Author HeadURL Added: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoinUsingConstraints.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoinUsingConstraints.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoinUsingConstraints.java 2011-02-20 21:20:44 UTC (rev 4211) @@ -0,0 +1,494 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2011. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on Feb 20, 2011 + */ + +package com.bigdata.bop; + +import java.util.Map; + +import junit.framework.TestCase2; + +import com.bigdata.bop.ap.Predicate; +import com.bigdata.bop.constraint.AND; +import com.bigdata.bop.constraint.BOpConstraint; +import com.bigdata.bop.controller.JoinGraph.JGraph; + +/** + * + * This test suite is built around around BSBM Q5. Each test has an existing + * join path and a new vertex to be added to the join path. The question is + * whether or not the vertex <em>can join</em> with the join path using one or + * more shared variable(s). This tests a method used to incrementally grow a + * join path when it is dynamically decided that an {@link IPredicate} may be + * added to the join path based on shared variables. Static analysis easily + * reports those joins which are allowed based on the variables directly given + * with two {@link IPredicate}s. The purpose of this test suite is to explore + * when joins (based on shared variables) become permissible through + * {@link IConstraint}s as the variable(s) used within those constraints become + * bound. + * <p> + * Note: To avoid a dependency on the RDF model layer, this just uses String + * constants for URIs and Literals. + * + * @see BOpUtility#canJoin(IPredicate, IPredicate) + * @see BOpUtility#canJoinUsingConstraints(IPredicate[], IPredicate, + * IConstraint[]) + * @see JGraph + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id$ + */ +//@SuppressWarnings("unchecked") +public class TestBOpUtility_canJoinUsingConstraints extends TestCase2 { + + /** + * + */ + public TestBOpUtility_canJoinUsingConstraints() { + } + + /** + * @param name + */ + public TestBOpUtility_canJoinUsingConstraints(String name) { + super(name); + } + + /** + * Unit tests to verify that arguments are validated. + * + * @see BOpUtility#canJoinUsingConstraints(IPredicate[], IPredicate, + * IConstraint[]) + */ + public void test_canJoinUsingConstraints_illegalArgument() { + + final IVariable<?> x = Var.var("x"); + final IVariable<?> y = Var.var("y"); + + final IPredicate<?> p1 = new Predicate(new BOp[]{x}); + + final IPredicate<?> p2 = new Predicate(new BOp[]{y}); + + // path must not be null. + try { + BOpUtility.canJoinUsingConstraints(// + null, // path + p1,// vertex + new IConstraint[0]// constraints + ); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Expecting: " + IllegalArgumentException.class); + } + + // vertex must not be null. + try { + BOpUtility.canJoinUsingConstraints(// + new IPredicate[]{p1}, // path + null,// vertex + new IConstraint[0]// constraints + ); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Expecting: " + IllegalArgumentException.class); + } + + // path may not be empty. + try { + BOpUtility.canJoinUsingConstraints(// + new IPredicate[] {}, // path + p1,// vertex + new IConstraint[0]// constraints + ); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Expecting: " + IllegalArgumentException.class); + } + + // path elements may not be null. + try { + BOpUtility.canJoinUsingConstraints(// + new IPredicate[] { p2, null }, // path + p1,// vertex + new IConstraint[0]// constraints + ); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Expecting: " + IllegalArgumentException.class); + } + + // vertex must not appear in the path. + try { + BOpUtility.canJoinUsingConstraints(// + new IPredicate[] { p2, p1 }, // path + p1,// vertex + new IConstraint[0]// constraints + ); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Expecting: " + IllegalArgumentException.class); + } + + // constraint array may not contain null elements. + try { + BOpUtility.canJoinUsingConstraints(// + new IPredicate[] { p2 }, // path + p1,// vertex + new IConstraint[] { // + new NEConstant(x, new Constant<Integer>(12)), // + null // + }// constraints + ); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Expecting: " + IllegalArgumentException.class); + } + + } + + // The comparison operators. + static private final int GT = 0, LT = 1;// , EQ = 2, GTE = 3, LTE = 4; + + // The math operators. + static private final int PLUS = 0, MINUS = 1; + + // Annotation for the comparison or math operator. + static private final String OP = "op"; + + /** + * A do-nothing constraint. The constraint is never evaluated. It is only + * used to test the logic which decides when two predicates can join based + * on variable(s) shared via a constraint. + */ + static private final class MyCompareOp extends BOpConstraint { + + private static final long serialVersionUID = 1L; + + /** + * Required deep copy constructor. + * + * @param op + */ + public MyCompareOp(MyCompareOp op) { + super(op); + } + + /** + * @param args + * @param annotations + */ + public MyCompareOp(BOp[] args, Map<String, Object> annotations) { + super(args, annotations); + } + + public boolean accept(IBindingSet bindingSet) { + throw new UnsupportedOperationException(); + } + + } + + /** + * A do-nothing constraint. The constraint is never evaluated. It is only + * used to test the logic which decides when two predicates can join based + * on variable(s) shared via a constraint. + */ + static private final class NEConstant extends BOpConstraint { + + private static final long serialVersionUID = 1L; + + /** + * Required deep copy constructor. + * + * @param op + */ + public NEConstant(NEConstant op) { + super(op); + } + + /** + * @param args + * @param annotations + */ + public NEConstant(BOp[] args, Map<String, Object> annotations) { + super(args, annotations); + } + + public NEConstant(IVariable<?> var, IConstant<?> value) { + this(new BOp[] { var, value }, null/* annotations */); + } + + public boolean accept(IBindingSet bindingSet) { + throw new UnsupportedOperationException(); + } + + } + + /** + * A do-nothing value expression. The expression is never evaluated. It is + * only used to test the logic which decides when two predicates can join + * based on variable(s) shared via a constraint. + */ + static private final class MathBOp extends ImmutableBOp implements + IValueExpression { + + private static final long serialVersionUID = 1L; + + /** + * Required deep copy constructor. + * + * @param op + */ + public MathBOp(final MathBOp op) { + + super(op); + + } + + /** + * Required shallow copy constructor. + * + * @param args + * The operands. + * @param op + * The operation. + */ + public MathBOp(final BOp[] args, Map<String, Object> anns) { + + super(args, anns); + + if (args.length != 2 || args[0] == null || args[1] == null + || getProperty(OP) == null) { + + throw new IllegalArgumentException(); + + } + + } + + /** + * + * @param left + * The left operand. + * @param right + * The right operand. + * @param op + * The annotation specifying the operation to be performed on + * those operands. + */ + public MathBOp(final IValueExpression left, + final IValueExpression right, final int op) { + + this(new BOp[] { left, right }, NV.asMap(new NV(OP, op))); + + } + + public Object get(IBindingSet bindingSet) { + throw new UnsupportedOperationException(); + } + } + + static private final String rdfs = "http://www.w3.org/2000/01/rdf-schema#"; + + static private final String bsbm = "http://www4.wiwiss.fu-berlin.de/bizer/bsbm/v01/vocabulary/"; + + static private final String rdfsLabel = rdfs + "label"; + + static private final String productFeature = bsbm + "productFeature"; + + static private final String productPropertyNumeric1 = "productPropertyNumeric1"; + + static private final String productPropertyNumeric2 = bsbm + + "productPropertyNumeric2"; + + static private final String productInstance = "http://www4.wiwiss.fu-berlin.de/bizer/bsbm/v01/instances/dataFromProducer1/Product22"; + + private int nextId = 0; + + final IVariable<?> product = Var.var("product"); + + final IVariable<?> productLabel = Var.var("productLabel"); + + final IVariable<?> prodFeature = Var.var("prodFeature"); + + final IVariable<?> simProperty1 = Var.var("simProperty1"); + + final IVariable<?> simProperty2 = Var.var("simProperty2"); + + final IVariable<?> origProperty1 = Var.var("origProperty1"); + + final IVariable<?> origProperty2 = Var.var("origProperty2"); + + /** ?product rdfs:label ?productLabel . */ + final private IPredicate<?> p0 = new Predicate(new BOp[] {// + product, new Constant(rdfsLabel), productLabel },// + new NV(BOp.Annotations.BOP_ID, nextId++)// + ); + + /** productInstance bsbm:productFeature ?prodFeature . */ + final private IPredicate<?> p1 = new Predicate(new BOp[] { // + new Constant(productInstance), new Constant(productFeature), + prodFeature },// + new NV(BOp.Annotations.BOP_ID, nextId++)// + ); + + /** ?product bsbm:productFeature ?prodFeature . */ + final private IPredicate<?> p2 = new Predicate(new BOp[] { // + product, new Constant(productFeature), prodFeature },// + new NV(BOp.Annotations.BOP_ID, nextId++)// + ); + + /** productInstance bsbm:productPropertyNumeric1 ?origProperty1 . */ + final private IPredicate<?> p3 = new Predicate(new BOp[] { // + new Constant<String>(productInstance), + new Constant(productPropertyNumeric1), origProperty1 },// + new NV(BOp.Annotations.BOP_ID, nextId++)// + ); + + /** ?product bsbm:productPropertyNumeric1 ?simProperty1 . */ + final private IPredicate<?> p4 = new Predicate(new BOp[] { // + product, new Constant(productPropertyNumeric1), simProperty1 },// + new NV(BOp.Annotations.BOP_ID, nextId++)// + ); + + /** productInstance bsbm:productPropertyNumeric2 ?origProperty2 . */ + final private IPredicate<?> p5 = new Predicate(new BOp[] { // + new Constant(productInstance), + new Constant(productPropertyNumeric2), origProperty2 },// + new NV(BOp.Annotations.BOP_ID, nextId++)// + ); + + /** ?product bsbm:productPropertyNumeric2 ?simProperty2 . */ + final private IPredicate<?> p6 = new Predicate(new BOp[] { // + product, new Constant(productPropertyNumeric2), simProperty2 },// + new NV(BOp.Annotations.BOP_ID, nextId++)// + ); + + /** The vertices of the join graph (the predicates). */ + final IPredicate<?>[] preds = new IPredicate[] { p0, p1, p2, p3, p4, p5, p6 }; + + /** + * FILTER (productInstance != ?product) + */ + final IConstraint c0 = new NEConstant(product, new Constant<String>( + productInstance)); + + /** + * FILTER (?simProperty1 < (?origProperty1 + 120) && ?simProperty1 > + * (?origProperty1 - 120)) + * <p> + * Note: The AND in the compound filters is typically optimized out such + * that each of these is represented as its own IConstraint, but I have + * combined them for the purposes of these unit tests. + */ + final IConstraint c1 = new AND(// + new MyCompareOp( + new BOp[] { + simProperty1, + new MathBOp(origProperty1, new Constant<Integer>( + 120), PLUS) }, NV.asMap(new NV[] { new NV( + OP, LT) })), // + new MyCompareOp(new BOp[] { + simProperty1, + new MathBOp(origProperty1, new Constant<Integer>(120), + MINUS) }, NV.asMap(new NV[] { new NV(OP, GT) }))// + ); + + /** + * FILTER (?simProperty2 < (?origProperty2 + 170) && ?simProperty2 > + * (?origProperty2 - 170)) + * <p> + * Note: The AND in the compound filters is typically optimized out such + * that each of these is represented as its own IConstraint, but I have + * combined them for the purposes of these unit tests. + */ + final IConstraint c2 = new AND(// + new MyCompareOp( + new BOp[] { + simProperty2, + new MathBOp(origProperty2, new Constant<Integer>( + 170), PLUS) }, NV.asMap(new NV[] { new NV( + OP, LT) })),// + new MyCompareOp(new BOp[] { + simProperty2, + new MathBOp(origProperty2, new Constant<Integer>(170), + MINUS) }, NV.asMap(new NV[] { new NV(OP, GT) }))// + ); + + /** The constraints on the join graph. */ + final IConstraint[] constraints = new IConstraint[] { c0, c1, c2 }; + + /** + * path=[p0], vertex=p2, constraints=[]. + */ + public void test_canJoinUsingConstraints_01() { + + // share ?product + assertTrue(BOpUtility.canJoin(p0, p2)); + assertTrue(BOpUtility.canJoinUsingConstraints(// + new IPredicate[] { p0 }, // path + p2,// vertex + new IConstraint[0]// constraints + )); + + } + + /** + * path=[p0], vertex=p2, constraints=[]. + * + * @todo Test w/o any constraints or with all constraints. Testing with only + * some of the constraints does not add much here (we probably do not + * need to have [c0] defined for this set of unit tests). + * + * @todo These are the full plans generated by the runtime and static + * optimizers. One way to test canJoinXXX() is to run out these join + * plans and verify that they report "true" in each case. However, the + * critical bit to test are join plans where the predicates w/o the + * shared variables can be run earlier due to the FILTERs. + * + * <pre> + * test_bsbm_q5 : static [0] : : ids=[1, 2, 4, 6, 0, 3, 5] + * test_bsbm_q5 : runtime[0] : : ids=[1, 2, 0, 4, 6, 3, 5] + * </pre> + */ + public void test_canJoinUsingConstraints_02() { + + // share ?product + assertTrue(BOpUtility.canJoin(p0, p2)); + assertTrue(BOpUtility.canJoinUsingConstraints(// + new IPredicate[] { p0 }, // path + p2,// vertex + new IConstraint[0]// constraints + )); + + } + +} Property changes on: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/TestBOpUtility_canJoinUsingConstraints.java ___________________________________________________________________ Added: svn:keywords + Id Date Revision Author HeadURL This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mrp...@us...> - 2011-02-23 00:48:50
|
Revision: 4232 http://bigdata.svn.sourceforge.net/bigdata/?rev=4232&view=rev Author: mrpersonick Date: 2011-02-23 00:48:43 +0000 (Wed, 23 Feb 2011) Log Message: ----------- modified the static query optimizer to give preferential treatment to tails that share variables with runFirst expanders Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/joinGraph/fast/DefaultEvaluationPlan2.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/joinGraph/fast/TestDefaultEvaluationPlan.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/joinGraph/fast/DefaultEvaluationPlan2.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/joinGraph/fast/DefaultEvaluationPlan2.java 2011-02-22 23:56:05 UTC (rev 4231) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/joinGraph/fast/DefaultEvaluationPlan2.java 2011-02-23 00:48:43 UTC (rev 4232) @@ -30,10 +30,15 @@ import java.util.Arrays; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import org.apache.log4j.Logger; + +import com.bigdata.bop.BOpUtility; import com.bigdata.bop.IPredicate; +import com.bigdata.bop.IVariable; import com.bigdata.bop.IVariableOrConstant; +import com.bigdata.bop.Var; import com.bigdata.bop.joinGraph.IEvaluationPlan; import com.bigdata.bop.joinGraph.IRangeCountFactory; import com.bigdata.journal.ITx; @@ -195,7 +200,7 @@ /** * Compute the evaluation order. */ - private void calc(IRule rule) { + private void calc(final IRule rule) { if (order != null) return; @@ -224,16 +229,28 @@ } */ + final Set<IVariable<?>> runFirstVars = new HashSet<IVariable<?>>(); + int startIndex = 0; for (int i = 0; i < tailCount; i++) { - IAccessPathExpander expander = rule.getTail(i).getAccessPathExpander(); + final IPredicate pred = rule.getTail(i); + final IAccessPathExpander expander = pred.getAccessPathExpander(); if (expander != null && expander.runFirst()) { if (DEBUG) log.debug("found a run first, tail " + i); + final Iterator<IVariable<?>> it = BOpUtility.getArgumentVariables(pred); + while (it.hasNext()) { + runFirstVars.add(it.next()); + } order[startIndex++] = i; used[i] = true; } } + // if there are no more tails left after the expanders, we're done + if (startIndex == tailCount) { + return; + } + // if there is only one tail left after the expanders if (startIndex == tailCount-1) { if (DEBUG) log.debug("one tail left"); @@ -248,6 +265,25 @@ } } + int preferredFirstTail = -1; + // give preferential treatment to a tail that shares variables with the + // runFirst expanders + for (int i = 0; i < tailCount; i++) { + // only check unused tails + if (used[i]) { + continue; + } + final IPredicate pred = rule.getTail(i); + final Iterator<IVariable<?>> it = BOpUtility.getArgumentVariables(pred); + while (it.hasNext()) { + if (runFirstVars.contains(it.next())) { + preferredFirstTail = i; + } + } + if (preferredFirstTail != -1) + break; + } + // if there are only two tails left after the expanders if (startIndex == tailCount-2) { if (DEBUG) log.debug("two tails left"); @@ -267,8 +303,13 @@ } } if (DEBUG) log.debug(t1 + ", " + t2); - order[tailCount-2] = cardinality(t1) <= cardinality(t2) ? t1 : t2; - order[tailCount-1] = cardinality(t1) <= cardinality(t2) ? t2 : t1; + if (preferredFirstTail != -1) { + order[tailCount-2] = preferredFirstTail; + order[tailCount-1] = preferredFirstTail == t1 ? t2 : t1; + } else { + order[tailCount-2] = cardinality(t1) <= cardinality(t2) ? t1 : t2; + order[tailCount-1] = cardinality(t1) <= cardinality(t2) ? t2 : t1; + } return; } @@ -276,11 +317,16 @@ * There will be (tails-1) joins, we just need to figure out what * they should be. */ - Join join = getFirstJoin(); + Join join = preferredFirstTail == -1 ? getFirstJoin() : getFirstJoin(preferredFirstTail); int t1 = ((Tail) join.getD1()).getTail(); int t2 = ((Tail) join.getD2()).getTail(); - order[startIndex] = cardinality(t1) <= cardinality(t2) ? t1 : t2; - order[startIndex+1] = cardinality(t1) <= cardinality(t2) ? t2 : t1; + if (preferredFirstTail == -1) { + order[startIndex] = cardinality(t1) <= cardinality(t2) ? t1 : t2; + order[startIndex+1] = cardinality(t1) <= cardinality(t2) ? t2 : t1; + } else { + order[startIndex] = t1; + order[startIndex+1] = t2; + } used[order[startIndex]] = true; used[order[startIndex+1]] = true; for (int i = startIndex+2; i < tailCount; i++) { @@ -359,6 +405,48 @@ return new Join(minT1, minT2, minJoinCardinality, vars); } + private Join getFirstJoin(final int preferredFirstTail) { + if (DEBUG) { + log.debug("evaluating first join"); + } + + + long minJoinCardinality = Long.MAX_VALUE; + long minOtherTailCardinality = Long.MAX_VALUE; + Tail minT2 = null; + final int i = preferredFirstTail; + final Tail t1 = new Tail(i, rangeCount(i), getVars(i)); + for (int j = 0; j < tailCount; j++) { + // check only non-same and unused tails + if (i == j || used[j]) { + continue; + } + Tail t2 = new Tail(j, rangeCount(j), getVars(j)); + long t2Cardinality = cardinality(j); + long joinCardinality = computeJoinCardinality(t1, t2); + if(DEBUG) log.debug("evaluating " + i + " X " + j + ": cardinality= " + joinCardinality); + if (joinCardinality < minJoinCardinality) { + if(DEBUG) log.debug("found a new min: " + joinCardinality); + minJoinCardinality = joinCardinality; + minOtherTailCardinality = t2Cardinality; + minT2 = t2; + } else if (joinCardinality == minJoinCardinality) { + if (t2Cardinality < minOtherTailCardinality) { + if(DEBUG) log.debug("found a new min: " + joinCardinality); + minJoinCardinality = joinCardinality; + minOtherTailCardinality = t2Cardinality; + minT2 = t2; + } + } + } + + // the join variables is the union of the join dimensions' variables + Set<String> vars = new HashSet<String>(); + vars.addAll(t1.getVars()); + vars.addAll(minT2.getVars()); + return new Join(t1, minT2, minJoinCardinality, vars); + } + /** * Similar to {@link #getFirstJoin()}, but we have one join dimension * already calculated. Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/joinGraph/fast/TestDefaultEvaluationPlan.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/joinGraph/fast/TestDefaultEvaluationPlan.java 2011-02-22 23:56:05 UTC (rev 4231) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/joinGraph/fast/TestDefaultEvaluationPlan.java 2011-02-23 00:48:43 UTC (rev 4232) @@ -40,15 +40,14 @@ import com.bigdata.bop.IConstant; import com.bigdata.bop.IConstraint; import com.bigdata.bop.IPredicate; +import com.bigdata.bop.IPredicate.Annotations; import com.bigdata.bop.IVariableOrConstant; import com.bigdata.bop.NV; import com.bigdata.bop.Var; -import com.bigdata.bop.IPredicate.Annotations; import com.bigdata.bop.ap.Predicate; import com.bigdata.bop.joinGraph.IEvaluationPlan; import com.bigdata.bop.joinGraph.IEvaluationPlanFactory; import com.bigdata.bop.joinGraph.IRangeCountFactory; -import com.bigdata.bop.joinGraph.fast.DefaultEvaluationPlan2; import com.bigdata.btree.keys.ISortKeyBuilder; import com.bigdata.config.IValidator; import com.bigdata.io.IStreamSerializer; @@ -61,6 +60,7 @@ import com.bigdata.relation.accesspath.IBlockingBuffer; import com.bigdata.relation.accesspath.IBuffer; import com.bigdata.relation.accesspath.IElementFilter; +import com.bigdata.relation.rule.IAccessPathExpander; import com.bigdata.relation.rule.IRule; import com.bigdata.relation.rule.IStep; import com.bigdata.relation.rule.Rule; @@ -252,7 +252,120 @@ // fail("write test"); // // } + + + public void testRunFirstExpanders() { + + final String relation = "spo"; + final long timestamp = ITx.READ_COMMITTED; + /* + * ?l search Mike + * ?s ?p ?l + * ?s type Person + * ?p type Property + */ + + final Constant<?> search = new Constant<String>("search"); + final Constant<?> Mike = new Constant<String>("Mike"); + final Constant<?> type = new Constant<String>("type"); + final Constant<?> Person = new Constant<String>("Person"); + final Constant<?> Property = new Constant<String>("Property"); + + final IAccessPathExpander expander = new IAccessPathExpander() { + + @Override + public IAccessPath getAccessPath(IAccessPath accessPath) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean backchain() { + return false; + } + + @Override + public boolean runFirst() { + return true; + } + + }; + + final IPredicate<?> pred0 = new Predicate(// + new IVariableOrConstant[] {// + Var.var("l"), search, Mike },// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { relation }),// + new NV(Annotations.TIMESTAMP,timestamp),// + new NV(Predicate.Annotations.ACCESS_PATH_EXPANDER, expander) + ); + + final IPredicate<?> pred1 = new Predicate(// + new IVariableOrConstant[] {// + Var.var("s"), Var.var("p"), Var.var("l") },// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { relation }),// + new NV(Annotations.TIMESTAMP,timestamp)// + ); + + final IPredicate<?> pred2 = new Predicate( // + new IVariableOrConstant[] {// + Var.var("s"), type, Person },// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { relation }),// + new NV(Annotations.TIMESTAMP,timestamp)// + ); + + final IPredicate<?> pred3 = new Predicate( // + new IVariableOrConstant[] {// + Var.var("p"), type, Property },// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { relation }),// + new NV(Annotations.TIMESTAMP,timestamp)// + ); + + final IRule rule = new Rule(getName(), null/* head */, + new IPredicate[] { pred0, pred1, pred2, pred3 }, // + null// constraints + ); + + final Map<IPredicate,Long> rangeCount = new HashMap<IPredicate,Long>(); + { + rangeCount.put(pred0, 0L); + rangeCount.put(pred1, 1000000L); + rangeCount.put(pred2, 10000L); + rangeCount.put(pred3, 100L); + } + + final IEvaluationPlan plan = newPlan(new MockJoinNexus( + new MockRangeCountFactory(rangeCount)), rule); + + assertFalse(plan.isEmpty()); + + final int[] expected = new int[] { 0, 1, 3, 2 }; + + final int[] actual = plan.getOrder(); + + if (!Arrays.equals(expected, actual)) + fail("evaluation order: expected=" + Arrays.toString(expected) + + ", actual=" + Arrays.toString(actual)); + +// assertFalse("isFullyBound(0)", plan.isFullyBound(0)); +// assertTrue( "isFullyBound(1)", plan.isFullyBound(1)); +// assertFalse("isFullyBound(2)", plan.isFullyBound(2)); +// assertTrue( "isFullyBound(3)", plan.isFullyBound(3)); +// assertFalse("isFullyBound(4)", plan.isFullyBound(4)); +// +// assertEquals("getVarCount(0)", 1, plan.getVariableCount(0)); +// assertEquals("getVarCount(1)", 0, plan.getVariableCount(1)); +// assertEquals("getVarCount(2)", 1, plan.getVariableCount(2)); +// assertEquals("getVarCount(3)", 0, plan.getVariableCount(3)); +// assertEquals("getVarCount(4)", 1, plan.getVariableCount(4)); + + } + + + /** * Mock object. * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mar...@us...> - 2011-02-24 17:49:50
|
Revision: 4245 http://bigdata.svn.sourceforge.net/bigdata/?rev=4245&view=rev Author: martyncutcher Date: 2011-02-24 17:49:44 +0000 (Thu, 24 Feb 2011) Log Message: ----------- Remove unneeded reference to RWSectorStore Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/SectorAllocator.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/sector/TestMemoryManager.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/SectorAllocator.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/SectorAllocator.java 2011-02-24 17:34:41 UTC (rev 4244) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/sector/SectorAllocator.java 2011-02-24 17:49:44 UTC (rev 4245) @@ -35,7 +35,6 @@ import com.bigdata.rwstore.FixedOutputStream; import com.bigdata.rwstore.IAllocationContext; import com.bigdata.rwstore.IWriteCacheManager; -import com.bigdata.rwstore.RWSectorStore; import com.bigdata.rwstore.RWWriteCacheService; /** @@ -666,7 +665,7 @@ } - public int alloc(RWSectorStore sectorStore, int size, IAllocationContext context) { + public int alloc(int size, IAllocationContext context) { return alloc(size); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/sector/TestMemoryManager.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/sector/TestMemoryManager.java 2011-02-24 17:34:41 UTC (rev 4244) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/sector/TestMemoryManager.java 2011-02-24 17:49:44 UTC (rev 4245) @@ -10,6 +10,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import com.bigdata.io.DirectBufferPool; +import com.bigdata.rwstore.sector.MemoryManagerResourceError; import com.bigdata.util.concurrent.DaemonThreadFactory; import junit.framework.TestCase; @@ -47,12 +49,10 @@ String retstr = getString(saddr); assertTrue(helloWorld.equals(retstr)); - - System.out.println(helloWorld + " allocated address: " + saddr + " returned: " + retstr); } private void installMemoryManager() { - manager = new MemoryManager(5 * sectorSize, sectorSize); + manager = new MemoryManager(DirectBufferPool.INSTANCE, 10); } /** @@ -64,7 +64,7 @@ while (--i > 0) { // final int sector = r.nextInt(12); final int sector = r.nextInt(32 * 1024); - final int bit = r.nextInt(64 * 1024); + final int bit = r.nextInt(32 * 1024); final int rwaddr = SectorAllocator.makeAddr(sector, bit); final int rsector = SectorAllocator.getSectorIndex(rwaddr); final int rbit = SectorAllocator.getSectorOffset(rwaddr); @@ -76,14 +76,26 @@ installMemoryManager(); for (int i = 0; i < 20; i++) { - doStressAllocations(manager, true, 80000, 5 + r.nextInt(200)); + doStressAllocations(manager, true, 50000, 5 + r.nextInt(5000)); } } + public void testSimpleBlob() { + installMemoryManager(); + + String blob = new String(c_testData, 0, 11000); + + final long saddr = allocate(manager, blob); + + String retstr = getString(saddr); + + assertTrue(blob.equals(retstr)); + } + public void testAllocationContexts() { installMemoryManager(); - final IMemoryManager context = manager.createAllocationContext(); + IMemoryManager context = manager.createAllocationContext(); for (int i = 0; i < 500; i++) { doStressAllocations(context, false, 5000, 5 + r.nextInt(3000)); context.clear(); @@ -133,30 +145,34 @@ int f = r.nextInt(addrs.size()); long faddr = ((Long) addrs.remove(f)).longValue(); mm.free(faddr); - // System.out.println("freeing: " + faddr); frees++; } } } catch (MemoryManagerResourceError err) { // all okay } - - System.out.println("Committed " + allocs + " allocations, and " + frees + " frees"); } private String getString(long saddr) { - final ByteBuffer ret = manager.get(saddr)[0]; - final byte[] data; - if (ret.isDirect()) { - ByteBuffer indbuf = ByteBuffer.allocate(ret.remaining()); - data = indbuf.array(); - indbuf.put(ret); - indbuf.flip(); - } else { - data = ret.array(); + StringBuffer sb = new StringBuffer(); + + final ByteBuffer[] bufs = manager.get(saddr); + + for (int i = 0; i < bufs.length; i++) { + final byte[] data; + if (bufs[i].isDirect()) { + ByteBuffer indbuf = ByteBuffer.allocate(bufs[i].remaining()); + data = indbuf.array(); + indbuf.put(bufs[i]); + indbuf.flip(); + } else { + data = bufs[i].array(); + } + + sb.append(new String(data)); } - return new String(data); + return sb.toString(); } private long allocate(final IMemoryManager mm, String val) { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-02-28 16:24:59
|
Revision: 4259 http://bigdata.svn.sourceforge.net/bigdata/?rev=4259&view=rev Author: thompsonbry Date: 2011-02-28 16:24:53 +0000 (Mon, 28 Feb 2011) Log Message: ----------- Fixed parameter checks for DistinctBindingSetOp Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/DistinctBindingSetOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/solutions/TestDistinctBindingSets.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/DistinctBindingSetOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/DistinctBindingSetOp.java 2011-02-27 21:14:54 UTC (rev 4258) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/DistinctBindingSetOp.java 2011-02-28 16:24:53 UTC (rev 4259) @@ -1,5 +1,6 @@ package com.bigdata.bop.solutions; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -7,6 +8,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.FutureTask; +import org.apache.log4j.Logger; + import com.bigdata.bop.BOp; import com.bigdata.bop.BOpContext; import com.bigdata.bop.ConcurrentHashMapAnnotations; @@ -32,6 +35,9 @@ */ public class DistinctBindingSetOp extends PipelineOp { + private final static transient Logger log = Logger + .getLogger(DistinctBindingSetOp.class); + /** * */ @@ -74,11 +80,16 @@ } // shared state is used to share the hash table. - if (isSharedState()) { + if (!isSharedState()) { throw new UnsupportedOperationException(Annotations.SHARED_STATE + "=" + isSharedState()); } - + + final IVariable<?>[] vars = (IVariable[]) getProperty(Annotations.VARIABLES); + + if (vars == null || vars.length == 0) + throw new IllegalArgumentException(); + } /** @@ -266,8 +277,14 @@ final Solution s = new Solution(r); + if (log.isTraceEnabled()) + log.trace("considering: " + Arrays.toString(r)); + final boolean distinct = map.putIfAbsent(s, s) == null; + if (distinct && log.isDebugEnabled()) + log.debug("accepted: " + Arrays.toString(r)); + return distinct ? r : null; } @@ -297,8 +314,6 @@ for (IBindingSet bset : a) { -// System.err.println("considering: " + bset); - /* * Test to see if this solution is distinct from those * already seen. @@ -315,9 +330,6 @@ * this operator. */ -// System.err.println("accepted: " -// + Arrays.toString(vals)); - final ListBindingSet tmp = new ListBindingSet(); for (int i = 0; i < vars.length; i++) { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/solutions/TestDistinctBindingSets.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/solutions/TestDistinctBindingSets.java 2011-02-27 21:14:54 UTC (rev 4258) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/solutions/TestDistinctBindingSets.java 2011-02-28 16:24:53 UTC (rev 4259) @@ -42,6 +42,7 @@ import com.bigdata.bop.IConstant; import com.bigdata.bop.IVariable; import com.bigdata.bop.NV; +import com.bigdata.bop.PipelineOp; import com.bigdata.bop.Var; import com.bigdata.bop.bindingSet.ArrayBindingSet; import com.bigdata.bop.bindingSet.HashBindingSet; @@ -160,7 +161,66 @@ data = null; } + + public void test_ctor_correctRejection() { + + final Var<?> x = Var.var("x"); + final int distinctId = 1; + + // w/o variables. + try { + new DistinctBindingSetOp(new BOp[]{}, + NV.asMap(new NV[]{// + new NV(DistinctBindingSetOp.Annotations.BOP_ID,distinctId),// +// new NV(DistinctBindingSetOp.Annotations.VARIABLES,new IVariable[]{x}),// + new NV(PipelineOp.Annotations.EVALUATION_CONTEXT, + BOpEvaluationContext.CONTROLLER),// + new NV(PipelineOp.Annotations.SHARED_STATE, + true),// + })); + fail("Expecting: "+IllegalArgumentException.class); + } catch(IllegalArgumentException ex) { + if(log.isInfoEnabled()) + log.info("Ignoring expected exception: "+ex); + } + + // w/o evaluation on the query controller. + try { + new DistinctBindingSetOp(new BOp[]{}, + NV.asMap(new NV[]{// + new NV(DistinctBindingSetOp.Annotations.BOP_ID,distinctId),// + new NV(DistinctBindingSetOp.Annotations.VARIABLES,new IVariable[]{x}),// +// new NV(PipelineOp.Annotations.EVALUATION_CONTEXT, +// BOpEvaluationContext.CONTROLLER),// + new NV(PipelineOp.Annotations.SHARED_STATE, + true),// + })); + fail("Expecting: "+UnsupportedOperationException.class); + } catch(UnsupportedOperationException ex) { + if(log.isInfoEnabled()) + log.info("Ignoring expected exception: "+ex); + } + + // w/o shared state. + try { + new DistinctBindingSetOp(new BOp[]{}, + NV.asMap(new NV[]{// + new NV(DistinctBindingSetOp.Annotations.BOP_ID,distinctId),// + new NV(DistinctBindingSetOp.Annotations.VARIABLES,new IVariable[]{x}),// + new NV(PipelineOp.Annotations.EVALUATION_CONTEXT, + BOpEvaluationContext.CONTROLLER),// +// new NV(PipelineOp.Annotations.SHARED_STATE, +// true),// + })); + fail("Expecting: "+UnsupportedOperationException.class); + } catch(UnsupportedOperationException ex) { + if(log.isInfoEnabled()) + log.info("Ignoring expected exception: "+ex); + } + + } + /** * Unit test for distinct. * @@ -179,10 +239,10 @@ NV.asMap(new NV[]{// new NV(DistinctBindingSetOp.Annotations.BOP_ID,distinctId),// new NV(DistinctBindingSetOp.Annotations.VARIABLES,new IVariable[]{x}),// - new NV(MemorySortOp.Annotations.EVALUATION_CONTEXT, + new NV(PipelineOp.Annotations.EVALUATION_CONTEXT, BOpEvaluationContext.CONTROLLER),// -// new NV(MemorySortOp.Annotations.SHARED_STATE, -// true),// + new NV(PipelineOp.Annotations.SHARED_STATE, + true),// })); // the expected solutions This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-03-03 17:12:38
|
Revision: 4265 http://bigdata.svn.sourceforge.net/bigdata/?rev=4265&view=rev Author: thompsonbry Date: 2011-03-03 17:12:31 +0000 (Thu, 03 Mar 2011) Log Message: ----------- https://sourceforge.net/apps/trac/bigdata/ticket/221 Added an efficient code path for BTree#removeAll() when using the RWStore without delete markers for the index. There is now a unit test for this code path in TestRWJournal. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/Leaf.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/Node.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestDirtyIterators.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestIterators.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestRemoveAll.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTree.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTree.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -625,7 +625,7 @@ * split may occur without forcing eviction of either node participating in * the split. * <p> - * Note: The code in {@link Node#postOrderNodeIterator(boolean)} and + * Note: The code in {@link Node#postOrderNodeIterator(boolean, boolean)} and * {@link DirtyChildIterator} MUST NOT touch the hard reference queue since * those iterators are used when persisting a node using a post-order * traversal. If a hard reference queue eviction drives the serialization of @@ -3481,8 +3481,8 @@ * * Note: This iterator only visits dirty nodes. */ - final Iterator<AbstractNode> itr = node - .postOrderNodeIterator(true/* dirtyNodesOnly */); + final Iterator<AbstractNode> itr = node.postOrderNodeIterator( + true/* dirtyNodesOnly */, false/* nodesOnly */); while (itr.hasNext()) { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -551,25 +551,46 @@ } - public Iterator<AbstractNode> postOrderNodeIterator() { + final public Iterator<AbstractNode> postOrderNodeIterator() { - return postOrderNodeIterator(false); + return postOrderNodeIterator(false/* dirtyNodesOnly */, false/* nodesOnly */); } /** - * Post-order traversal of nodes and leaves in the tree. For any given - * node, its children are always visited before the node itself (hence - * the node occurs in the post-order position in the traversal). The - * iterator is NOT safe for concurrent modification. + * Post-order traversal of nodes and leaves in the tree. For any given node, + * its children are always visited before the node itself (hence the node + * occurs in the post-order position in the traversal). The iterator is NOT + * safe for concurrent modification. * * @param dirtyNodesOnly * When true, only dirty nodes and leaves will be visited - * + * * @return Iterator visiting {@link AbstractNode}s. */ - abstract public Iterator<AbstractNode> postOrderNodeIterator(boolean dirtyNodesOnly); + final public Iterator<AbstractNode> postOrderNodeIterator( + final boolean dirtyNodesOnly) { + return postOrderNodeIterator(dirtyNodesOnly, false/* nodesOnly */); + + } + + /** + * Post-order traversal of nodes and leaves in the tree. For any given node, + * its children are always visited before the node itself (hence the node + * occurs in the post-order position in the traversal). The iterator is NOT + * safe for concurrent modification. + * + * @param dirtyNodesOnly + * When true, only dirty nodes and leaves will be visited + * @param nodesOnly + * When <code>true</code>, the leaves will not be visited. + * + * @return Iterator visiting {@link AbstractNode}s. + */ + abstract public Iterator<AbstractNode> postOrderNodeIterator( + final boolean dirtyNodesOnly, final boolean nodesOnly); + public ITupleIterator entryIterator() { return rangeIterator(null/* fromKey */, null/* toKey */, Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BTree.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BTree.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -28,6 +28,7 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; +import java.util.Iterator; import java.util.concurrent.atomic.AtomicLong; import com.bigdata.BigdataStatics; @@ -1174,19 +1175,64 @@ assertNotReadOnly(); - /* - * FIXME Per https://sourceforge.net/apps/trac/bigdata/ticket/221, we - * should special case this for the RWStore when delete markers are not - * enabled and just issue deletes against each node and leave in the - * BTree. This could be done using a post-order traversal of the nodes - * and leaves such that the parent is not removed from the store until - * its children have been removed. The deletes should be low-level - * IRawStore#delete(addr) invocations without maintenance to the B+Tree - * data structures. Afterwards replaceRootWithEmptyLeaf() should be - * invoked to discard the hard reference ring buffer and associate a new - * root leaf with the B+Tree. - */ - if (getIndexMetadata().getDeleteMarkers() + if (!getIndexMetadata().getDeleteMarkers() + && getStore() instanceof RWStrategy) { + + /* + * Per https://sourceforge.net/apps/trac/bigdata/ticket/221, we + * special case this for the RWStore when delete markers are not + * enabled and just issue deletes against each node and leave in the + * BTree. This is done using a post-order traversal of the nodes and + * leaves such that the parent is not removed from the store until + * its children have been removed. The deletes are low-level + * IRawStore#delete(addr) invocations without maintenance to the + * B+Tree data structures. Afterwards replaceRootWithEmptyLeaf() is + * invoked to discard the hard reference ring buffer and associate a + * new root leaf with the B+Tree. + * + * FIXME https://sourceforge.net/apps/trac/bigdata/ticket/217 - we + * should update the performance counters when we use this short + * cut to release the storage associated with the B+Tree. + */ + + /* + * Visit all Nodes using a pre-order traversal, but do not + * materialize the leaves. + */ + final Iterator<AbstractNode> itr = getRoot().postOrderNodeIterator( + false/* dirtyNodesOnly */, true/* nodesOnly */); + + while(itr.hasNext()) { + + final Node node = (Node) itr.next(); + + final int nchildren = node.getChildCount(); + + for (int i = 0; i < nchildren; i++) { + + final long childAddr = node.getChildAddr(i); + + if(childAddr != 0L) { + + // delete persistent child. + getStore().delete(childAddr); + + } + + } + + } + + // delete root iff persistent. + if (getRoot().getIdentity() != 0L) { + + getStore().delete(getRoot().getIdentity()); + + } + + replaceRootWithEmptyLeaf(); + + } else if (getIndexMetadata().getDeleteMarkers() || getStore() instanceof RWStrategy) { /* Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/Leaf.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/Leaf.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/Leaf.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -1708,17 +1708,24 @@ /** * Visits this leaf if unless it is not dirty and the flag is true, in which * case the returned iterator will not visit anything. + * + * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") - public Iterator<AbstractNode> postOrderNodeIterator(final boolean dirtyNodesOnly) { + public Iterator<AbstractNode> postOrderNodeIterator( + final boolean dirtyNodesOnly, final boolean nodesOnly) { if (dirtyNodesOnly && ! isDirty() ) { return EmptyIterator.DEFAULT; + } else if(nodesOnly) { + + return EmptyIterator.DEFAULT; + } else { - + return new SingleValueIterator(this); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/Node.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/Node.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/Node.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -2874,7 +2874,7 @@ @Override @SuppressWarnings("unchecked") public Iterator<AbstractNode> postOrderNodeIterator( - final boolean dirtyNodesOnly) { + final boolean dirtyNodesOnly, final boolean nodesOnly) { if (dirtyNodesOnly && !dirty) { @@ -2886,7 +2886,7 @@ * Iterator append this node to the iterator in the post-order position. */ - return new Striterator(postOrderIterator1(dirtyNodesOnly)) + return new Striterator(postOrderIterator1(dirtyNodesOnly,nodesOnly)) .append(new SingleValueIterator(this)); } @@ -2916,7 +2916,7 @@ */ @SuppressWarnings("unchecked") private Iterator<AbstractNode> postOrderIterator1( - final boolean dirtyNodesOnly) { + final boolean dirtyNodesOnly,final boolean nodesOnly) { /* * Iterator visits the direct children, expanding them in turn with a @@ -2969,8 +2969,8 @@ // visit the children (recursive post-order // traversal). final Striterator itr = new Striterator( - ((Node) child) - .postOrderIterator1(dirtyNodesOnly)); + ((Node) child).postOrderIterator1( + dirtyNodesOnly, nodesOnly)); // append this node in post-order position. itr.append(new SingleValueIterator(child)); @@ -2985,6 +2985,9 @@ // BTree.log.debug("child is leaf: " + child); // Visit the leaf itself. + if (nodesOnly) + return EmptyIterator.DEFAULT; + return new SingleValueIterator(child); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestDirtyIterators.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestDirtyIterators.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestDirtyIterators.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -38,7 +38,7 @@ * mechanisms. * * @see Node#childIterator(boolean) - * @see AbstractNode#postOrderNodeIterator(boolean) + * @see AbstractNode#postOrderNodeIterator(boolean, boolean) * * @see TestIterators, which handles iterators that do not differentiate between * clear and dirty nodes. Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestIterators.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestIterators.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestIterators.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -40,12 +40,12 @@ * * @see Leaf#entryIterator() * @see Node#childIterator(boolean) - * @see AbstractNode#postOrderNodeIterator(boolean) + * @see AbstractNode#postOrderNodeIterator(boolean, boolean) * * @see TestDirtyIterators, which handles tests when some nodes or leaves are * NOT dirty and verifies that the iterators do NOT visit such nodes or * leaves. This tests {@link AbstractNode#postOrderNodeIterator()} as well - * since that is just {@link AbstractNode#postOrderNodeIterator(boolean)} + * since that is just {@link AbstractNode#postOrderNodeIterator(boolean, boolean)} * with <code>false</code> passed in. * * @author <a href="mailto:tho...@us...">Bryan Thompson</a> Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestRemoveAll.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestRemoveAll.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestRemoveAll.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -27,15 +27,19 @@ package com.bigdata.btree; +import java.util.Properties; import java.util.UUID; import org.apache.log4j.Level; import com.bigdata.btree.keys.KeyBuilder; +import com.bigdata.journal.BufferMode; +import com.bigdata.journal.Journal; import com.bigdata.journal.TestRestartSafe; import com.bigdata.rawstore.Bytes; import com.bigdata.rawstore.IRawStore; import com.bigdata.rawstore.SimpleMemoryRawStore; +import com.bigdata.rwstore.RWStore; /** * Test suite for {@link BTree#removeAll()}. @@ -165,5 +169,5 @@ } } - + } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java 2011-03-03 17:01:49 UTC (rev 4264) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java 2011-03-03 17:12:31 UTC (rev 4265) @@ -33,14 +33,18 @@ import java.util.ArrayList; import java.util.Properties; import java.util.TreeMap; +import java.util.UUID; import junit.extensions.proxy.ProxyTestSuite; import junit.framework.Test; +import com.bigdata.btree.BTree; import com.bigdata.btree.IIndex; import com.bigdata.btree.ITuple; import com.bigdata.btree.ITupleIterator; import com.bigdata.btree.IndexMetadata; +import com.bigdata.btree.SimpleEntry; +import com.bigdata.btree.keys.KeyBuilder; import com.bigdata.journal.AbstractInterruptsTestCase; import com.bigdata.journal.AbstractJournalTestCase; import com.bigdata.journal.AbstractMRMWTestCase; @@ -56,6 +60,7 @@ import com.bigdata.journal.TestJournalBasics; import com.bigdata.journal.Journal.Options; import com.bigdata.rawstore.AbstractRawStoreTestCase; +import com.bigdata.rawstore.Bytes; import com.bigdata.rawstore.IRawStore; import com.bigdata.util.InnerCause; @@ -228,7 +233,97 @@ } - /** + /** + * Unit tests for optimization when using the {@link RWStore} but not using + * delete markers. In this case, a post-order traversal is used to + * efficiently delete the nodes and leaves and the root leaf is then + * replaced. + */ + public void test_removeAllRWStore() { + + final Journal store = new Journal(getProperties()); + + try { + + final BTree btree; + { + + final IndexMetadata metadata = new IndexMetadata(UUID + .randomUUID()); + + metadata.setBranchingFactor(3); + + metadata.setDeleteMarkers(false); + + btree = BTree.create(store, metadata); + + } + + final KeyBuilder keyBuilder = new KeyBuilder(Bytes.SIZEOF_INT); + + final int NTRIALS = 100; + + final int NINSERTS = 1000; + + final double removeAllRate = 0.001; + + final double checkpointRate = 0.001; + + for (int i = 0; i < NTRIALS; i++) { + + for (int j = 0; j < NINSERTS; j++) { + + if (r.nextDouble() < checkpointRate) { + + // flush to the backing store. + if (log.isInfoEnabled()) + log.info("checkpoint: " + btree.getStatistics()); + + btree.writeCheckpoint(); + + } + + if (r.nextDouble() < removeAllRate) { + + if (log.isInfoEnabled()) + log.info("removeAll: " + btree.getStatistics()); + + btree.removeAll(); + + } + + final int tmp = r.nextInt(10000); + + final byte[] key = keyBuilder.reset().append(tmp).getKey(); + + btree.insert(key, new SimpleEntry(tmp)); + + } + + } + + if (log.isInfoEnabled()) + log.info("will removeAll: "+btree.getStatistics()); + + btree.removeAll(); + + if (log.isInfoEnabled()) + log.info("will checkpoint: " + btree.getStatistics()); + + btree.writeCheckpoint(); + + if (log.isInfoEnabled()) + log.info(" did checkpoint: " + btree.getStatistics()); + + } finally { + + store.destroy(); + + } + + } + + /** * Test suite integration for {@link AbstractRestartSafeTestCase}. * * @todo there are several unit tests in this class that deal with This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-03-03 21:27:09
|
Revision: 4267 http://bigdata.svn.sourceforge.net/bigdata/?rev=4267&view=rev Author: thompsonbry Date: 2011-03-03 21:27:02 +0000 (Thu, 03 Mar 2011) Log Message: ----------- Refactored the DefaultResourceLocator to remove the base class, which was simply a cache. Added a propertyCache to the DefaultResourceLocator to provide sharing of the materialized properties from the global row store across views which are backed by the same commit point. This sharing is only done for the local Journal right now as it depends on fast access to the commit time. This change could be extended to scale-out with a refactor to replace the native long transaction identifier with a thin interface capable of reporting both the transaction identifier and the commit point against which the transaction is reading. Added unit test for properties caching by the DefaultResourceLocator. Modified Journal to expose access to historical views of the global row store. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/Journal.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/AbstractResource.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/locator/DefaultResourceLocator.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/relation/locator/TestDefaultResourceLocator.java Removed Paths: ------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/locator/AbstractCachingResourceLocator.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/Journal.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/Journal.java 2011-03-03 18:38:51 UTC (rev 4266) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/Journal.java 2011-03-03 21:27:02 UTC (rev 4267) @@ -1125,14 +1125,39 @@ /* * global row store. + */ + public SparseRowStore getGlobalRowStore() { + + return getGlobalRowStoreHelper().getGlobalRowStore(); + + } + + /** + * Return a view of the global row store as of the specified timestamp. This + * is mainly used to provide access to historical views. * + * @param timestamp + * The specified timestamp. + * + * @return The global row store view -or- <code>null</code> if no view + * exists as of that timestamp. + */ + public SparseRowStore getGlobalRowStore(final long timestamp) { + + return getGlobalRowStoreHelper().get(timestamp); + + } + + /** + * Return the {@link GlobalRowStoreHelper}. + * <p> * Note: An atomic reference provides us with a "lock" object which doubles * as a reference. We are not relying on its CAS properties. */ - public SparseRowStore getGlobalRowStore() { + private final GlobalRowStoreHelper getGlobalRowStoreHelper() { + + GlobalRowStoreHelper t = globalRowStoreHelper.get(); - GlobalRowStoreHelper t = globalRowStoreHelper.get(); - if (t == null) { synchronized (globalRowStoreHelper) { @@ -1151,14 +1176,14 @@ .set(t = new GlobalRowStoreHelper(this)); } - + } } - return globalRowStoreHelper.get().getGlobalRowStore(); + return globalRowStoreHelper.get(); + } - } final private AtomicReference<GlobalRowStoreHelper> globalRowStoreHelper = new AtomicReference<GlobalRowStoreHelper>(); /* Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/AbstractResource.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/AbstractResource.java 2011-03-03 18:38:51 UTC (rev 4266) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/AbstractResource.java 2011-03-03 21:27:02 UTC (rev 4267) @@ -47,7 +47,6 @@ import com.bigdata.journal.IIndexManager; import com.bigdata.journal.IResourceLock; import com.bigdata.journal.IResourceLockService; -import com.bigdata.rawstore.Bytes; import com.bigdata.rdf.rules.FastClosure; import com.bigdata.rdf.rules.FullClosure; import com.bigdata.rdf.rules.RuleFastClosure5; @@ -691,12 +690,12 @@ } // Write the map on the row store. - final Map afterMap = indexManager.getGlobalRowStore().write( - RelationSchema.INSTANCE, map); - + final Map<String, Object> afterMap = indexManager.getGlobalRowStore() + .write(RelationSchema.INSTANCE, map); + if(log.isDebugEnabled()) { - log.debug("Properties after write: "+afterMap); + log.debug("Properties after write: " + afterMap); } Deleted: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/locator/AbstractCachingResourceLocator.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/locator/AbstractCachingResourceLocator.java 2011-03-03 18:38:51 UTC (rev 4266) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/locator/AbstractCachingResourceLocator.java 2011-03-03 21:27:02 UTC (rev 4267) @@ -1,191 +0,0 @@ -/* - -Copyright (C) SYSTAP, LLC 2006-2008. All rights reserved. - -Contact: - SYSTAP, LLC - 4501 Tower Road - Greensboro, NC 27410 - lic...@bi... - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -*/ -/* - * Created on Jun 30, 2008 - */ - -package com.bigdata.relation.locator; - -import java.lang.ref.WeakReference; -import java.util.concurrent.TimeUnit; - -import org.apache.log4j.Logger; - -import com.bigdata.cache.ConcurrentWeakValueCache; -import com.bigdata.cache.ConcurrentWeakValueCacheWithTimeout; -import com.bigdata.util.NT; - -/** - * Abstract base class for {@link IResourceLocator}s with caching. The cache - * uses {@link WeakReference}s so that cache entries will be cleared if the - * referenced item is cleared. - * - * @author <a href="mailto:tho...@us...">Bryan Thompson</a> - * @version $Id$ - */ -abstract public class AbstractCachingResourceLocator<T extends ILocatableResource> - implements IResourceLocator<T> { - - protected static final Logger log = Logger - .getLogger(AbstractCachingResourceLocator.class); - - protected static final boolean INFO = log.isInfoEnabled(); - - final private transient ConcurrentWeakValueCache<NT, T> cache; - - final private int capacity; - - /** - * The cache capacity. - */ - final public int capacity() { - - return capacity; - - } - - /** - * - * @param capacity - * The cache capacity. - * @param timeout - * The timeout in milliseconds for stale entries. - */ - protected AbstractCachingResourceLocator(final int capacity, - final long timeout) { - - if (capacity <= 0) - throw new IllegalArgumentException(); - - if (timeout < 0) - throw new IllegalArgumentException(); - - this.capacity = capacity; - -// this.cache = new WeakValueCache<NT, T>(new LRUCache<NT, T>(capacity)); - - this.cache = new ConcurrentWeakValueCacheWithTimeout<NT, T>(capacity, - TimeUnit.MILLISECONDS.toNanos(timeout)); - - } - - /** - * Looks up the resource in the cache (thread-safe since the underlying - * cache is thread-safe). - * - * @param namespace - * - * @param timestamp - * - * @return The relation -or- <code>null</code> iff it is not in the cache. - */ - protected T get(final String namespace, final long timestamp) { - - if (namespace == null) - throw new IllegalArgumentException(); - - final T r = cache.get(new NT(namespace, timestamp)); - - if (INFO) { - - log.info((r == null ? "miss" : "hit ") + ": namespace=" + namespace - + ", timestamp=" + timestamp); - - } - - return r; - - } - - /** - * Places the resource in the cache. - * <p> - * Note: The underlying cache is thread-safe. However, when adding an entry - * to the cache the caller MUST be synchronized on the named resource, use - * {@link #get(String, long)} to determine that there is no such entry in - * the cache, and then {@link #put(ILocatableResource)} the entry. - * <p> - * Note: Read committed views are allowed into the cache. - * <p> - * For a Journal, this depends on Journal#getIndex(name,timestamp) returning - * a ReadCommittedView for an index so that the view does in fact have - * read-committed semantics. - * <p> - * For a federation, read-committed semantics are achieved by the - * IClientIndex implementations since they always make standoff requests to - * one (or more) data services. Those requests allow the data service to - * resolve the then most recent view for the index for each request. - * - * @param resource - * The resource. - */ - protected void put(final T resource) { - - if (resource == null) - throw new IllegalArgumentException(); - - final String namespace = resource.getNamespace().toString(); - - final long timestamp = resource.getTimestamp(); - - if (INFO) { - - log.info("Caching: namespace=" + namespace + ", timestamp=" - + timestamp); - - } - - cache.put(new NT(namespace, timestamp), resource); - -// cache.put(new NT(namespace, timestamp), resource, false/* dirty */); - - } - - /** - * Clears any resource having the same namespace and timestamp from the - * cache. - * <p> - * Note: The caller MUST be synchronized on the named resource. - * - * @return <code>true</code> iff there was an entry in the cache for the - * same resource namespace and timestamp, in which case it was - * cleared from the cache. - */ - protected boolean clear(final String namespace, final long timestamp) { - - if (namespace == null) - throw new IllegalArgumentException(); - - if (cache.remove(new NT(namespace, timestamp)) != null) { - - return true; - - } - - return false; - - } - -} Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/locator/DefaultResourceLocator.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/locator/DefaultResourceLocator.java 2011-03-03 18:38:51 UTC (rev 4266) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/relation/locator/DefaultResourceLocator.java 2011-03-03 21:27:02 UTC (rev 4267) @@ -33,24 +33,30 @@ import java.util.Properties; import java.util.UUID; import java.util.WeakHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import org.apache.log4j.Logger; import com.bigdata.btree.IIndex; +import com.bigdata.cache.ConcurrentWeakValueCache; +import com.bigdata.cache.ConcurrentWeakValueCacheWithTimeout; import com.bigdata.cache.LRUCache; import com.bigdata.concurrent.NamedLock; import com.bigdata.journal.AbstractTask; +import com.bigdata.journal.ICommitRecord; import com.bigdata.journal.IIndexManager; import com.bigdata.journal.IIndexStore; import com.bigdata.journal.Journal; import com.bigdata.journal.TemporaryStore; +import com.bigdata.journal.TimestampUtility; import com.bigdata.relation.AbstractResource; import com.bigdata.relation.IRelation; import com.bigdata.relation.RelationSchema; import com.bigdata.service.IBigdataFederation; import com.bigdata.sparse.SparseRowStore; +import com.bigdata.util.NT; /** * Generic implementation relies on a ctor for the resource with the following @@ -108,21 +114,27 @@ * @param <T> * The generic type of the [R]elation. */ -public class DefaultResourceLocator<T extends ILocatableResource> extends - AbstractCachingResourceLocator<T> implements IResourceLocator<T> { +public class DefaultResourceLocator<T extends ILocatableResource> // + implements IResourceLocator<T> { protected static final transient Logger log = Logger .getLogger(DefaultResourceLocator.class); - - protected static final boolean INFO = log.isInfoEnabled(); - - protected static final boolean DEBUG = log.isDebugEnabled(); protected final transient IIndexManager indexManager; private final IResourceLocator<T> delegate; - + /** + * Cache for recently located resources. + */ + final private transient ConcurrentWeakValueCache<NT, T> resourceCache; + + /** + * Cache for recently materialized properties from the GRS. + */ + final /*private*/ transient ConcurrentWeakValueCache<NT, Map<String,Object>> propertyCache; + + /** * Provides locks on a per-namespace basis for higher concurrency. */ private final transient NamedLock<String> namedLock = new NamedLock<String>(); @@ -170,8 +182,6 @@ final IResourceLocator<T> delegate, final int cacheCapacity, final long cacheTimeout) { - super(cacheCapacity, cacheTimeout); - if (indexManager == null) throw new IllegalArgumentException(); @@ -179,6 +189,18 @@ this.delegate = delegate;// MAY be null. + if (cacheCapacity <= 0) + throw new IllegalArgumentException(); + + if (cacheTimeout < 0) + throw new IllegalArgumentException(); + + this.resourceCache = new ConcurrentWeakValueCacheWithTimeout<NT, T>( + cacheCapacity, TimeUnit.MILLISECONDS.toNanos(cacheTimeout)); + + this.propertyCache = new ConcurrentWeakValueCacheWithTimeout<NT, Map<String, Object>>( + cacheCapacity, TimeUnit.MILLISECONDS.toNanos(cacheTimeout)); + } // @todo hotspot 2% total query time. @@ -187,46 +209,86 @@ if (namespace == null) throw new IllegalArgumentException(); - if (INFO) { + if (log.isInfoEnabled()) { - log.info("namespace=" + namespace+", timestamp="+timestamp); + log.info("namespace=" + namespace + ", timestamp=" + timestamp); } + T resource = null; + final NT nt; + + /* + * Note: The drawback with resolving the resource against the + * [commitTime] is that the views will be the same object instance and + * will have the timestamp associated with the [commitTime] rather than + * the caller's timestamp. This breaks the assumption that + * resource#getTimestamp() returns the transaction identifier for a + * read-only transaction. In order to fix that, we resort to sharing the + * Properties object instead of the resource view. + */ +// if (TimestampUtility.isReadOnly(timestamp) +// && indexManager instanceof Journal) { +// +// /* +// * If we are looking on a local Journal (standalone database) then +// * we resolve the caller's [timestamp] to the commit point against +// * which the resource will be located and handle caching of the +// * resource using that commit point. This is done in order to share +// * a read-only view of a resource with any request which would be +// * serviced by the same commit point. Any such views are read-only +// * and immutable. +// */ +// +// final Journal journal = (Journal) indexManager; +// +// // find the commit record on which we need to read. +// final long commitTime = journal.getCommitRecord( +// TimestampUtility.asHistoricalRead(timestamp)) +// .getTimestamp(); +// +// nt = new NT(namespace, commitTime); +// +// } else { + + nt = new NT(namespace, timestamp); +// +// } + // test cache: hotspot 93% of method time. - T resource = get(namespace, timestamp); + resource = resourceCache.get(nt); if (resource != null) { - if (DEBUG) + if (log.isDebugEnabled()) log.debug("cache hit: " + resource); // cache hit. return resource; } - + /* - * Since there was a cache miss, acquire a lock the named relation so - * that the locate + cache.put sequence will be atomic. + * Since there was a cache miss, acquire a lock for the named relation + * so that the locate + cache.put sequence will be atomic. */ final Lock lock = namedLock.acquireLock(namespace); try { // test cache now that we have the lock. - resource = get(namespace, timestamp); + resource = resourceCache.get(nt); if (resource != null) { - if (DEBUG) + if (log.isDebugEnabled()) log.debug("cache hit: " + resource); return resource; } - if (INFO) + if (log.isInfoEnabled()) log.info("cache miss: namespace=" + namespace + ", timestamp=" + timestamp); @@ -250,7 +312,7 @@ * resolve this request. */ - if(INFO) { + if(log.isInfoEnabled()) { log.info("Not found - passing to delegate: namespace=" + namespace + ", timestamp=" + timestamp); @@ -262,7 +324,7 @@ if (resource != null) { - if (INFO) { + if (log.isInfoEnabled()) { log.info("delegate answered: " + resource); @@ -275,15 +337,15 @@ } if (log.isInfoEnabled()) - log.info("Not found: namespace=" + namespace + ", timestamp=" - + timestamp); + log.info("Not found: namespace=" + namespace + + ", timestamp=" + timestamp); // not found. return null; } - if (DEBUG) { + if (log.isDebugEnabled()) { log.debug(properties.toString()); @@ -310,7 +372,7 @@ } - if (DEBUG) { + if (log.isDebugEnabled()) { log.debug("Implementation class=" + cls.getName()); @@ -321,8 +383,8 @@ properties); // Add to the cache. - put(resource); - + resourceCache.put(nt, resource); + return resource; } finally { @@ -373,7 +435,7 @@ * and removed from the [seeAlso] weak value cache. */ - if (INFO) + if (log.isInfoEnabled()) log.info("Closed? " + indexManager); } else { @@ -400,7 +462,7 @@ if (properties != null) { - if (INFO) { + if (log.isInfoEnabled()) { log.info("Found: namespace=" + namespace + " on " + indexManager); @@ -428,7 +490,7 @@ if (properties != null) { - if (INFO) { + if (log.isInfoEnabled()) { log.info("Found: namespace=" + namespace + " on " + indexManager); @@ -460,28 +522,19 @@ * @param indexManager * @param namespace * The resource identifier - this is the primary key. - * @param timestampIsIgnored + * @param timestamp * The timestamp of the resource view. * * @return The {@link Properties} iff there is a logical row for the given * namespace. - * - * @todo The timestamp of the resource view is currently ignored. This - * probably should be modified to use the corresponding view of the - * global row store rather than always using the read-committed / - * unisolated view. That would make the properties immutable for a - * historical resource view and thus more easily cached. However, - * it would also make it impossible to modify those properties for - * historical views as any changes would only apply to views whose - * commit time was after the change to the global row store. */ protected Properties locateResourceOn(final IIndexManager indexManager, - final String namespace, final long timestampIsIgnored) { + final String namespace, final long timestamp) { - if (INFO) { + if (log.isInfoEnabled()) { log.info("indexManager=" + indexManager + ", namespace=" - + namespace + ", timestamp=" + timestampIsIgnored); + + namespace + ", timestamp=" + timestamp); } @@ -489,23 +542,105 @@ * Look at the global row store view corresponding to the specified * timestamp. * - * @todo caching may be useful here for historical reads. + * Note: caching here is important in order to reduce the heap pressure + * associated with large numbers of concurrent historical reads against + * the same commit point when those reads are performed within read-only + * transactions and, hence, each read is performed with a DISTINCT + * timestamp. Since the timestamps are distinct, the resource [cache] + * will have cache misses. This code provides for a [propertyCache] + * which ensures that we share the materialized properties from the GRS + * across resource views backed by the same commit point (and also avoid + * unnecessary GRS reads). */ - final SparseRowStore rowStore = indexManager - .getGlobalRowStore(/*timestamp*/); - - final Map<String, Object> map = rowStore == null ? null : rowStore - .read(RelationSchema.INSTANCE, namespace); + final Map<String, Object> map; + if (TimestampUtility.isReadOnly(timestamp) + && !TimestampUtility.isReadCommitted(timestamp) + && indexManager instanceof Journal) { -// System.err.println("Reading properties: namespace="+namespace+", timestamp="+timestampIsIgnored); -// log.fatal("Reading properties: "+namespace,new RuntimeException()); - + final Journal journal = (Journal) indexManager; + + // find the commit record on which we need to read. + final ICommitRecord commitRecord = journal + .getCommitRecord(TimestampUtility + .asHistoricalRead(timestamp)); + + if (commitRecord != null) { + + // find the timestamp associated with that commit record. + final long commitTime = commitRecord.getTimestamp(); + + // Check the cache before materializing the properties from the + // GRS. + final Map<String, Object> cachedMap = propertyCache.get(new NT( + namespace, commitTime)); + + if (cachedMap != null) { + + // The properties are in the cache. + map = cachedMap; + + } else { + + // Use the GRS view as of that commit point. + final SparseRowStore rowStore = journal + .getGlobalRowStore(commitTime); + + // Read the properties from the GRS. + map = rowStore == null ? null : rowStore.read( + RelationSchema.INSTANCE, namespace); + + if (map != null) { + + // Stuff the properties into the cache. + propertyCache.put(new NT(namespace, commitTime), map); + + } + + } + + } else { + + /* + * No such commit record. + * + * @todo We can probably just return [null] for this case. + */ + + final SparseRowStore rowStore = indexManager + .getGlobalRowStore(/* timestamp */); + + // Read the properties from the GRS. + map = rowStore == null ? null : rowStore.read( + RelationSchema.INSTANCE, namespace); + + } + + } else { + + /* + * @todo The timestamp of the resource view is currently ignored. + * This probably should be modified to use the corresponding view of + * the global row store rather than always using the read-committed + * / unisolated view, which will require exposing a + * getGlobalRowStore(timestamp) method on IIndexStore. + */ + + final SparseRowStore rowStore = indexManager + .getGlobalRowStore(/* timestamp */); + + // Read the properties from the GRS. + map = rowStore == null ? null : rowStore.read( + RelationSchema.INSTANCE, namespace); + + } + if (map == null) { - if (DEBUG) { + if (log.isDebugEnabled()) { - log.debug("No properties: indexManager=" + indexManager - + ", namespace=" + namespace); + log.debug("Not found: indexManager=" + indexManager + + ", namespace=" + namespace + ", timestamp=" + + timestamp); } @@ -513,21 +648,23 @@ } + // wrap with properties object to prevent cross view mutation. final Properties properties = new Properties(); properties.putAll(map); - if (DEBUG) { + if (log.isTraceEnabled()) { - log.debug("Read properties: indexManager=" + indexManager - + ", namespace=" + namespace + " :: " + properties); + log.trace("Read properties: indexManager=" + indexManager + + ", namespace=" + namespace + ", timestamp=" + timestamp + + " :: " + properties); } return properties; } - + /** * Create a new view of the relation. * @@ -588,7 +725,7 @@ r.init(); - if(INFO) { + if(log.isInfoEnabled()) { log.info("new instance: "+r); @@ -614,7 +751,7 @@ * @param instance * The instance. */ - public T putInstance(T instance) { + public T putInstance(final T instance) { if (instance == null) throw new IllegalArgumentException(); @@ -623,7 +760,7 @@ final long timestamp = instance.getTimestamp(); - if (INFO) { + if (log.isInfoEnabled()) { log.info("namespace=" + namespace+", timestamp="+timestamp); @@ -634,11 +771,13 @@ try { - final T tmp = get(namespace, timestamp); + final NT nt = new NT(namespace, timestamp); + final T tmp = resourceCache.get(nt); + if (tmp != null) { - if(INFO) { + if(log.isInfoEnabled()) { log.info("Existing instance already in cache: "+tmp); @@ -648,10 +787,10 @@ } - put(instance); - - if (INFO) { + resourceCache.put(nt, instance); + if (log.isInfoEnabled()) { + log.info("Instance added to cache: " + instance); } @@ -665,15 +804,15 @@ } } - + /** - * Resources that hold hard references to local index objects MUST discarded - * during abort processing. Otherwise the same resource objects will be - * returned from the cache and buffered writes on the indices for those - * relations (if they are local index objects) will still be visible, this - * defeating the abort semantics. + * Resources that hold hard references to local index objects MUST be + * discarded during abort processing. Otherwise the same resource objects + * will be returned from the cache and buffered writes on the indices for + * those relations (if they are local index objects) will still be visible, + * thus defeating the abort semantics. */ - public void discard(ILocatableResource<T> instance) { + public void discard(final ILocatableResource<T> instance) { if (instance == null) throw new IllegalArgumentException(); @@ -682,9 +821,9 @@ final long timestamp = instance.getTimestamp(); - if (INFO) { + if (log.isInfoEnabled()) { - log.info("namespace=" + namespace+", timestamp="+timestamp); + log.info("namespace=" + namespace + ", timestamp=" + timestamp); } @@ -693,10 +832,16 @@ try { - final boolean found = clear(namespace, timestamp); - - if (INFO) { + final NT nt = new NT(namespace, timestamp); + /* + * Clear the resource cache, but we do not need to clear the + * property cache since it only retains immutable historical state. + */ + final boolean found = resourceCache.remove(nt) != null; + + if (log.isInfoEnabled()) { + log.info("instance=" + instance + ", found=" + found); } @@ -744,7 +889,7 @@ */ seeAlso.put(indexManager, null); - if (INFO) { + if (log.isInfoEnabled()) { log.info("size=" + seeAlso.size() + ", added indexManager=" + indexManager); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/relation/locator/TestDefaultResourceLocator.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/relation/locator/TestDefaultResourceLocator.java 2011-03-03 18:38:51 UTC (rev 4266) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/relation/locator/TestDefaultResourceLocator.java 2011-03-03 21:27:02 UTC (rev 4267) @@ -28,13 +28,14 @@ package com.bigdata.relation.locator; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import junit.framework.TestCase2; @@ -49,11 +50,11 @@ import com.bigdata.journal.Journal.Options; import com.bigdata.rdf.spo.ISPO; import com.bigdata.relation.AbstractRelation; -import com.bigdata.relation.accesspath.IAccessPath; +import com.bigdata.relation.AbstractResource; import com.bigdata.service.IBigdataFederation; import com.bigdata.striterator.IChunkedOrderedIterator; import com.bigdata.striterator.IKeyOrder; -import com.bigdata.util.concurrent.DaemonThreadFactory; +import com.bigdata.util.NT; /** * Test suite for location relations, etc. @@ -102,8 +103,6 @@ final Journal store = new Journal( properties ); - final ExecutorService executorService = Executors.newCachedThreadPool(DaemonThreadFactory.defaultThreadFactory()); - final String namespace = "test"; try { @@ -221,12 +220,239 @@ store.destroy(); - executorService.shutdownNow(); - } } + /** + * Unit test for property caching for locatable resources. + */ + public void test_propertyCache() { + + final Properties properties = getProperties(); + + final Journal store = new Journal( properties ); + + final String namespace = "test"; + + try { + + // write a small record onto the journal and force a commit. + { + final ByteBuffer b = ByteBuffer.allocate(4); + b.putInt(0); + b.flip(); + store.write(b); + assertNotSame(0L,store.commit()); + } + + // verify resource can not be located yet. + { + // resource does not exist in UNISOLATED view. + assertNull(store.getResourceLocator().locate(namespace, + ITx.UNISOLATED)); + + // resource does not exist at lastCommitTime. + assertNull(store.getResourceLocator().locate(namespace, + store.getLastCommitTime())); + + // resource does not exist at read-only tx. + { + final long tx = store.newTx(ITx.READ_COMMITTED); + try { + assertNull(store.getResourceLocator().locate(namespace, + store.getLastCommitTime())); + } finally { + store.abort(tx); + } + } + } + + // instantiate relation. + MockRelation mockRelation = new MockRelation(store, namespace, + ITx.UNISOLATED, properties); + + // verify resource still can not be located. + { + // resource does not exist in UNISOLATED view. + assertNull(store.getResourceLocator().locate(namespace, + ITx.UNISOLATED)); + + // resource does not exist at lastCommitTime. + assertNull(store.getResourceLocator().locate(namespace, + store.getLastCommitTime())); + + // resource does not exist at read-only tx. + { + final long tx = store.newTx(ITx.READ_COMMITTED); + try { + assertNull(store.getResourceLocator().locate(namespace, + store.getLastCommitTime())); + } finally { + store.abort(tx); + } + } + } + + // create the resource, which writes the properties into the GRS. + mockRelation.create(); + + /* + */ + { + + /* + * The UNISOLATED view of the resource should be locatable now + * since the writes on the global row store are unisolated. + */ + assertNotNull(store.getResourceLocator().locate(namespace, + ITx.UNISOLATED)); + + // a request for the unisolated view gives us the same instance. + assertTrue(store.getResourceLocator().locate(namespace, + ITx.UNISOLATED) == mockRelation); + + /* + * The read-committed view of the resource is also locatable. + */ + assertNotNull(store.getResourceLocator().locate(namespace, + ITx.READ_COMMITTED)); + + /* + * The read committed view is not the same instance as the + * unisolated view. + */ + assertTrue(((MockRelation) store.getResourceLocator().locate( + namespace, ITx.READ_COMMITTED)) != mockRelation); + + } + + // commit time immediately proceeding this commit. + final long priorCommitTime = store.getLastCommitTime(); + + // commit, noting the commit time. + final long lastCommitTime = store.commit(); + + if(log.isInfoEnabled()) { + log.info("priorCommitTime=" + priorCommitTime); + log.info("lastCommitTime =" + lastCommitTime); + } + + /* + * Now create a few transactions against the newly created resource + * and verify that the views returned for those transactions are + * distinct, but that they share the same set of default Properties + * (e.g., the propertyCache is working). + * + * @todo also test a read-historical read ! + */ + final long tx1 = store.newTx(store.getLastCommitTime()); // read-only tx + final long tx2 = store.newTx(store.getLastCommitTime()); // read-only tx + final long ts1 = store.getLastCommitTime() - 1; // historical read + try { + + assertTrue(tx1 != tx2); + assertTrue(ts1 != tx1); + assertTrue(ts1 != tx2); + + /* + * @todo There might not be enough commit latency to have + * lastCommitTime - 1 be GT priorCommitTime. If this happens + * either resolve issue 145 or add some latency into the test. + * + * @see http://sourceforge.net/apps/trac/bigdata/ticket/145 + */ + assertTrue(ts1 > priorCommitTime); + + // unisolated view. + final AbstractResource<?> view_un = (AbstractResource<?>) store + .getResourceLocator().locate(namespace, ITx.UNISOLATED); + assertNotNull(view_un); + + // tx1 view. + final AbstractResource<?> view_tx1 = (AbstractResource<?>) store + .getResourceLocator().locate(namespace, tx1); + assertNotNull(view_tx1); + + // tx2 view. + final AbstractResource<?> view_tx2 = (AbstractResource<?>) store + .getResourceLocator().locate(namespace, tx2); + assertNotNull(view_tx2); + + // all views are distinct. + assertTrue(view_un != view_tx1); + assertTrue(view_un != view_tx2); + assertTrue(view_tx1 != view_tx2); + + // each view has its correct timestamp. + assertEquals(ITx.UNISOLATED, view_un.getTimestamp()); + assertEquals(tx1, view_tx1.getTimestamp()); + assertEquals(tx2, view_tx2.getTimestamp()); + + // each view has its own Properties object. + final Properties p_un = view_un.getProperties(); + final Properties p_tx1 = view_tx1.getProperties(); + final Properties p_tx2 = view_tx2.getProperties(); + assertTrue(p_un != p_tx1); + assertTrue(p_un != p_tx2); + assertTrue(p_tx1 != p_tx2); + + /* + * Verify that the [propertyCache] is working. + * + * Note: Unfortunately, I have not been able to devise any means + * of testing the [propertyCache] without exposing that as a + * package private object. + */ + final DefaultResourceLocator<?> locator = (DefaultResourceLocator<?>) store + .getResourceLocator(); + + // Not cached for the UNISOLATED view (mutable views can not be + // cached). + assertNull(locator.propertyCache.get(new NT(namespace, + ITx.UNISOLATED))); + +// if (true) { +// final Iterator<Map.Entry<NT, WeakReference<Map<String, Object>>>> itr = locator.propertyCache +// .entryIterator(); +// while (itr.hasNext()) { +// final Map.Entry<NT, WeakReference<Map<String, Object>>> e = itr +// .next(); +// System.err.println(e.getKey() + " => " +// + e.getValue().get()); +// } +// } + + // Not cached for the actual tx ids or read-only timestamp. + assertNull(locator.propertyCache.get(new NT(namespace,tx1))); + assertNull(locator.propertyCache.get(new NT(namespace,tx2))); + assertNull(locator.propertyCache.get(new NT(namespace,ts1))); + + /* + * Cached for the last commit time, which should have been used + * to hand back the Properties for {tx1, tx2, ts1}. + */ + assertNotNull(locator.propertyCache.get(new NT(namespace, + lastCommitTime))); + + // nothing for the prior commit time. + assertNull(locator.propertyCache.get(new NT(namespace, + priorCommitTime))); + + } finally { + store.abort(tx1); + store.abort(tx2); + } + + } finally { + + store.destroy(); + + } + + } + + @SuppressWarnings("unchecked") private static class MockRelation extends AbstractRelation { static final private String indexName = "foo"; @@ -298,66 +524,41 @@ @Override public String getFQN(IKeyOrder keyOrder) { - // TODO Auto-generated method stub return null; } public long delete(IChunkedOrderedIterator itr) { - // TODO Auto-generated method stub return 0; } public long insert(IChunkedOrderedIterator itr) { - // TODO Auto-generated method stub return 0; } -// public IAccessPath getAccessPath(IPredicate predicate) { -// // TODO Auto-generated method stub -// return null; -// } - - public long getElementCount(boolean exact) { - // TODO Auto-generated method stub - return 0; - } - public Set getIndexNames() { - // TODO Auto-generated method stub return null; } - public IKeyOrder getPrimaryKeyOrder() { - // TODO Auto-generated method stub return null; } public Iterator getKeyOrders() { - // TODO Auto-generated method stub return null; } public IKeyOrder getKeyOrder(IPredicate p) { - // TODO Auto-generated method stub return null; } - -// public Object newElement(IPredicate predicate, IBindingSet bindingSet) { -// // TODO Auto-generated method stub -// return null; -// } public Object newElement(List a, IBindingSet bindingSet) { - // TODO Auto-generated method stub return null; } public Class<ISPO> getElementClass() { - return null; + } - } - } + } // class MockRelation } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-03-03 21:31:32
|
Revision: 4268 http://bigdata.svn.sourceforge.net/bigdata/?rev=4268&view=rev Author: thompsonbry Date: 2011-03-03 21:31:26 +0000 (Thu, 03 Mar 2011) Log Message: ----------- Optimization for BOpUtility#getSharedVariables() with unit test. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/util/TestBOpUtility_sharedVariables.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java 2011-03-03 21:27:02 UTC (rev 4267) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java 2011-03-03 21:31:26 UTC (rev 4268) @@ -1108,11 +1108,8 @@ // if (p == c) // throw new IllegalArgumentException(); - // The set of variables which are shared. - final Set<IVariable<?>> sharedVars = new LinkedHashSet<IVariable<?>>(); - // Collect the variables appearing anywhere in [p]. - final Set<IVariable<?>> p1vars = new LinkedHashSet<IVariable<?>>(); + Set<IVariable<?>> p1vars = null; { final Iterator<IVariable<?>> itr = BOpUtility @@ -1120,12 +1117,29 @@ while (itr.hasNext()) { + if(p1vars == null) { + + // lazy initialization. + p1vars = new LinkedHashSet<IVariable<?>>(); + + } + p1vars.add(itr.next()); } } + if (p1vars == null) { + + // Fast path when no variables in [p]. + return Collections.emptySet(); + + } + + // The set of variables which are shared. + Set<IVariable<?>> sharedVars = null; + // Consider the variables appearing anywhere in [c]. { @@ -1138,16 +1152,26 @@ if (p1vars.contains(avar)) { - sharedVars.add(avar); + if (sharedVars == null) { + // lazy initialization. + sharedVars = new LinkedHashSet<IVariable<?>>(); + + } + + sharedVars.add(avar); + } } } - return sharedVars; + if (sharedVars == null) + return Collections.emptySet(); + return sharedVars; + } } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/util/TestBOpUtility_sharedVariables.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/util/TestBOpUtility_sharedVariables.java 2011-03-03 21:27:02 UTC (rev 4267) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/util/TestBOpUtility_sharedVariables.java 2011-03-03 21:31:26 UTC (rev 4268) @@ -98,6 +98,14 @@ @SuppressWarnings("unchecked") public void test_getSharedVariables_nothingShared() { + // nothing shared because no variables for one operand. + assertTrue(BOpUtility.getSharedVars(new Constant<Integer>(12), Var.var("y")) + .isEmpty()); + + // nothing shared because no variables for the other operand. + assertTrue(BOpUtility.getSharedVars(Var.var("y"),new Constant<Integer>(12)) + .isEmpty()); + // nothing shared. assertTrue(BOpUtility.getSharedVars(Var.var("x"), Var.var("y")) .isEmpty()); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-03-21 16:40:20
|
Revision: 4322 http://bigdata.svn.sourceforge.net/bigdata/?rev=4322&view=rev Author: thompsonbry Date: 2011-03-21 16:40:13 +0000 (Mon, 21 Mar 2011) Log Message: ----------- Introduced an ICUVersionRecord and runtime checking of the deployed ICU version based on self-reporting via ICU's VersionInfo class. The ICUVersionRecord's address is stored in the ICommitRecord. Old stores will automatically introduce this record on their next commit. New stores will write this record when they are first created. Update to new versions of ICU will be refused based on an incompatible value for this record unless Options#UPDATE_ICUVERSION is forced to "true". See https://sourceforge.net/apps/trac/bigdata/ticket/193 Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/AbstractJournal.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/Options.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestAll.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUPortabilityBug.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/TestDiskJournal.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java 2011-03-21 16:40:13 UTC (rev 4322) @@ -0,0 +1,251 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2011. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on Mar 21, 2011 + */ + +package com.bigdata.btree.keys; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.bigdata.journal.Name2Addr; +import com.ibm.icu.util.VersionInfo; + +/** + * Persistent record in which we store the version metadata for the ICU + * dependency in use when the journal was created. bigdata uses Unicode sort + * keys for various indices, including {@link Name2Addr}. A change in the ICU + * version can result in sort keys which are NOT compatible. Binary + * compatibility for Unicode sort keys is an absolute requirement for bigdata. + * The purpose of this persistence capable data record is to note the version of + * ICU against which bigdata was linked with the associated binary store file + * was created. + * <p> + * Note: This can result in data which apparently becomes "lost", such as this + * <a href="http://sourceforge.net/apps/trac/bigdata/ticket/193>trac issue</a>. + * The underlying problem was substituting a newer version of ICU for the one + * included in the bigdata distribution. Such errors are now caught by detecting + * a change in the ICU runtime environment. + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id$ + */ +public class ICUVersionRecord implements Externalizable { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private VersionInfo icuVersion; + private VersionInfo ucolRuntimeVersion; + private VersionInfo ucolBuilderVersion; + private VersionInfo ucolTailoringsVersion; + + /** + * The ICU software version number. + * + * @see VersionInfo#ICU_VERSION + */ + public VersionInfo getICUVersion() { + return icuVersion; + } + + /** + * If this version number changes, then the sort keys for the same Unicode + * string could be different. + * + * @see VersionInfo#UCOL_RUNTIME_VERSION + */ + public VersionInfo getUColRuntimeVersion() { + return ucolRuntimeVersion; + } + + /** + * If this version number changes, then the same tailoring might result in + * assigning different collation elements to code points (which could break + * binary compatibility on sort keys). + * + * @see VersionInfo#UCOL_BUILDER_VERSION + */ + public VersionInfo getUColBuilderVersion() { + return ucolBuilderVersion; + } + + /** + * The version of the collation tailorings. + * + * @see VersionInfo#UCOL_TAILORINGS_VERSION + */ + public VersionInfo getUColTailoringsVersion() { + return ucolTailoringsVersion; + } + + /** + * Factory returns a record reporting on the ICU dependency as currently + * linked with the code base. + */ + public static ICUVersionRecord newInstance() { + + final ICUVersionRecord r = new ICUVersionRecord(// + VersionInfo.ICU_VERSION,// + VersionInfo.UCOL_RUNTIME_VERSION,// + VersionInfo.UCOL_BUILDER_VERSION,// + VersionInfo.UCOL_TAILORINGS_VERSION// + ); + + return r; + + } + + private ICUVersionRecord(// + VersionInfo icuVersion,// + VersionInfo ucolRuntimeVesion,// + VersionInfo ucolBuilderVersion,// + VersionInfo ucolTailoringsVersion// + ) { + + this.icuVersion = icuVersion; + + this.ucolRuntimeVersion = ucolRuntimeVesion; + + this.ucolBuilderVersion = ucolBuilderVersion; + + this.ucolTailoringsVersion = ucolTailoringsVersion; + + } + + /** + * De-serialization contructor <strong>only</strong>. + */ + public ICUVersionRecord() { + + } + + + /** The initial version of this data record. */ + private static final transient int VERSION0 = 0; + + private static final transient int CURRENT_VERSION = VERSION0; + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + + final int version = in.readInt(); + switch (version) { + case VERSION0: + break; + default: + throw new IOException("Unknown version: " + version); + } + + icuVersion = readVersionInfo(in); + ucolRuntimeVersion = readVersionInfo(in); + ucolBuilderVersion = readVersionInfo(in); + ucolTailoringsVersion = readVersionInfo(in); + + } + + public void writeExternal(ObjectOutput out) throws IOException { + + out.writeInt(CURRENT_VERSION); + writeVersionInfo(icuVersion, out); + writeVersionInfo(ucolRuntimeVersion, out); + writeVersionInfo(ucolBuilderVersion, out); + writeVersionInfo(ucolTailoringsVersion, out); + + } + + private VersionInfo readVersionInfo(final ObjectInput in) throws IOException { + + final int major = in.readInt(); + final int minor = in.readInt(); + final int milli = in.readInt(); + final int micro = in.readInt(); + + return VersionInfo.getInstance(major, minor, milli, micro); + + } + + private void writeVersionInfo(final VersionInfo v, final ObjectOutput out) + throws IOException { + + out.writeInt(v.getMajor()); + out.writeInt(v.getMinor()); + out.writeInt(v.getMicro()); + out.writeInt(v.getMilli()); + + } + + /** + * A human readable representation of the data record. + */ + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append(getClass().getName()); + sb.append("{icuVersion=" + icuVersion); + sb.append(",ucolRuntimeVersion=" + ucolRuntimeVersion); + sb.append(",ucolBuilderVersion=" + ucolBuilderVersion); + sb.append(",ucolTailoringsVersion=" + ucolTailoringsVersion); + sb.append("}"); + return sb.toString(); + } + + public int hashCode() { + return super.hashCode(); + } + + public boolean equals(final Object o) { + if (this == o) + return true; + if (!(o instanceof ICUVersionRecord)) + return false; + final ICUVersionRecord r = (ICUVersionRecord) o; + if (!icuVersion.equals(r.icuVersion)) + return false; + if (!ucolRuntimeVersion.equals(r.ucolRuntimeVersion)) + return false; + if (!ucolBuilderVersion.equals(r.ucolBuilderVersion)) + return false; + if (!ucolTailoringsVersion.equals(r.ucolTailoringsVersion)) + return false; + return true; + } + + /** + * Writes out the {@link ICUVersionRecord} for the current classpath. + * + * @param args + * Ignored. + */ + static public void main(String[] args) { + + System.out.println(ICUVersionRecord.newInstance().toString()); + + } + +} Property changes on: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java ___________________________________________________________________ Added: svn:keywords + Id Date Revision Author HeadURL Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/AbstractJournal.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/AbstractJournal.java 2011-03-21 16:39:10 UTC (rev 4321) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/AbstractJournal.java 2011-03-21 16:40:13 UTC (rev 4322) @@ -60,6 +60,7 @@ import com.bigdata.btree.IIndex; import com.bigdata.btree.IndexMetadata; import com.bigdata.btree.ReadOnlyIndex; +import com.bigdata.btree.keys.ICUVersionRecord; import com.bigdata.cache.ConcurrentWeakValueCache; import com.bigdata.cache.ConcurrentWeakValueCacheWithTimeout; import com.bigdata.cache.HardReferenceQueue; @@ -76,6 +77,7 @@ import com.bigdata.ha.QuorumService; import com.bigdata.io.IDataRecord; import com.bigdata.io.IDataRecordAccess; +import com.bigdata.io.SerializerUtil; import com.bigdata.journal.Name2Addr.Entry; import com.bigdata.journal.ha.HAWriteMessage; import com.bigdata.mdi.IResourceMetadata; @@ -174,18 +176,25 @@ */ public static transient final int ROOT_NAME2ADDR = 0; - /** - * The index of the address where the root block copy from the previous - * commit is stored - */ + /** + * The index of the root address where the root block copy from the previous + * commit is stored. + */ public static transient final int PREV_ROOTBLOCK = 1; /** - * The index of the address of the delete blocks associated with - * this transaction + * The index of the root address of the delete blocks associated with + * this transaction. */ public static transient final int DELETEBLOCK = 2; + /** + * The index of the root address containing the {@link ICUVersionRecord}. + * That record specifies the ICU version metadata which was in force when + * the journal was created. + */ + public static transient final int ROOT_ICUVERSION = 3; + /** * A clone of the properties used to initialize the {@link Journal}. */ @@ -291,6 +300,11 @@ private volatile CommitRecordIndex _commitRecordIndex; /** + * The {@link ICUVersionRecord} iff known. + */ + private volatile ICUVersionRecord _icuVersionRecord; + + /** * The configured capacity for the {@link HardReferenceQueue} backing the * index cache maintained by the "live" {@link Name2Addr} object. * @@ -1003,6 +1017,27 @@ // new or re-load commit record index from store via root block. this._commitRecordIndex = _getCommitRecordIndex(); + // new or re-load from the store. + this._icuVersionRecord = _getICUVersionRecord(); + + // verify the ICU version. + if (this._icuVersionRecord != null + && !ICUVersionRecord.newInstance().equals( + this._icuVersionRecord)) { + + final boolean update = Boolean.valueOf(properties.getProperty( + Options.UPDATE_ICU_VERSION, "false")); + + if (!update) { + + throw new RuntimeException("ICUVersionChange: store=" + + this._icuVersionRecord + ", runtime=" + + ICUVersionRecord.newInstance()); + + } + + } + // Give the store a chance to set any committers that it defines. setupCommitters(); @@ -2081,6 +2116,9 @@ // clear reference and reload from the store. _commitRecordIndex = _getCommitRecordIndex(); + // clear reference and reload from the store. + _icuVersionRecord = _getICUVersionRecord(); + // clear the array of committers. _committers = new ICommitter[_committers.length]; @@ -2813,12 +2851,114 @@ if (_bufferStrategy instanceof RWStrategy) setCommitter(DELETEBLOCK, new DeleteBlockCommitter((RWStrategy) _bufferStrategy)); + /* + * Responsible for writing the ICUVersionRecord exactly once onto + * the backing store, e.g., when the store is created or when it is + * open with the "update" option specified for ICU. + */ + setCommitter(ROOT_ICUVERSION, new ICUVersionCommitter()); - } } + /** + * Return the {@link ICUVersionRecord} from the current + * {@link ICommitRecord} -or- a new instance for the current runtime + * environment if the root address for {@link #ROOT_ICUVERSION} is + * {@link #NULL}. + */ + private ICUVersionRecord _getICUVersionRecord() { + + assert _fieldReadWriteLock.writeLock().isHeldByCurrentThread(); + + final long addr = getRootAddr(ROOT_ICUVERSION); + + final ICUVersionRecord r; + if (addr == NULL) { + // New instance for the current runtime environment. + r = ICUVersionRecord.newInstance(); + } else { + // Existing instance from the store. + r = (ICUVersionRecord) SerializerUtil.deserialize(read(addr)); + } + return r; + + } + + /** + * Writes the {@link ICUVersionRecord} onto the store iff either (a) it does + * not exist; or (b) it exists, it differs from the last persistent record, + * and the update flag was specified. + * + * @author <a href="mailto:tho...@us...">Bryan + * Thompson</a> + * + * @see Options#UPDATE_ICU_VERSION + */ + private class ICUVersionCommitter implements ICommitter { + + private boolean update; + + private long lastAddr; + + private ICUVersionCommitter() { + + // the "update" option. + update = Boolean.valueOf(properties.getProperty( + Options.UPDATE_ICU_VERSION, "false")); + + // lookup the address of the ICU version record (may be NULL). + lastAddr = getRootAddr(ROOT_ICUVERSION); + + } + + /** + * Commits a new {@link ICUVersionRecord} IF none is defined -OR- IF one + * is defined, it is a different version of ICU, and the update flag is + * set. + */ + public long handleCommit(final long commitTime) { + + if(!update && lastAddr != NULL) { + + // Nothing changed. + return lastAddr; + + } + + /* + * Note: The Journal only validates the persistent ICU version + * record in its constructor. By the time the code reaches this + * point, it is either in agreement or will be written. + */ + + final ICUVersionRecord r = ICUVersionRecord.newInstance(); + + if (lastAddr == NULL || !(r.equals(_icuVersionRecord) && update)) { + + if (_icuVersionRecord != null && update) + log.warn("Updating ICUVersion: old=" + _icuVersionRecord + + ", new=" + r); + + // do not update next time. + update = false; + + // write ICU version record onto the store. + lastAddr = write(ByteBuffer.wrap(SerializerUtil.serialize(r))); + + // return address of the ICU version record. + return lastAddr; + + } + + // Nothing changed. + return lastAddr; + + } + + } + /* * named indices. */ Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/Options.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/Options.java 2011-03-21 16:39:10 UTC (rev 4321) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/Options.java 2011-03-21 16:40:13 UTC (rev 4322) @@ -32,6 +32,7 @@ import com.bigdata.btree.Checkpoint; import com.bigdata.btree.IndexSegment; +import com.bigdata.btree.keys.ICUVersionRecord; import com.bigdata.cache.HardReferenceQueue; import com.bigdata.io.DirectBufferPool; import com.bigdata.io.FileLockUtility; @@ -351,6 +352,19 @@ String IGNORE_BAD_ROOT_BLOCK = AbstractJournal.class.getName()+".ignoreBadRootBlock"; /** + * <strong>WARNING - The use of this option is dangerous.</strong> This + * option may be used to update the {@link ICUVersionRecord} associated with + * the journal. ICU provides a Unicode sort key generation service for + * bigdata. Unicode sort keys are used in many indices, including the + * {@link Name2Addr} index. If the new ICU version produces Unicode sort + * keys which are not binary compatible with the Journal, then your data may + * become inaccessible since you will be unable to probe the + * {@link Name2Addr} index to locate named indices. The same problem can + * manifest with application indices which use Unicode sort keys. + */ + String UPDATE_ICU_VERSION = AbstractJournal.class.getName()+".updateICUVersion"; + + /** * An optional boolean property (default is {@value #DEFAULT_CREATE}). When * <code>true</code> and the named file is not found, a new journal will be * created. If the file exists but is empty, then a new journal will be Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestAll.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestAll.java 2011-03-21 16:39:10 UTC (rev 4321) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestAll.java 2011-03-21 16:40:13 UTC (rev 4322) @@ -72,7 +72,8 @@ suite.addTestSuite(TestICUUnicodeKeyBuilder.class); suite.addTestSuite(TestICUPortabilityBug.class); - + suite.addTestSuite(TestICUVersionRecord.class); + return suite; } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUPortabilityBug.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUPortabilityBug.java 2011-03-21 16:39:10 UTC (rev 4321) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUPortabilityBug.java 2011-03-21 16:40:13 UTC (rev 4322) @@ -38,11 +38,16 @@ /** * This is a unit test for a possible ICU portability bug. + * <p> + * Note: This issue has been resolved. The problem was that someone had + * substituted a difference version of ICU on the classpath in the deployed + * system. * * @see https://sourceforge.net/apps/trac/bigdata/ticket/193 * * @author <a href="mailto:tho...@us...">Bryan Thompson</a> - * @version $Id$ + * @version $Id: TestICUPortabilityBug.java 4313 2011-03-18 13:16:31Z + * thompsonbry $ */ public class TestICUPortabilityBug extends TestCase { Added: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java 2011-03-21 16:40:13 UTC (rev 4322) @@ -0,0 +1,70 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2011. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on Mar 21, 2011 + */ + +package com.bigdata.btree.keys; + +import com.bigdata.io.SerializerUtil; + +import junit.framework.TestCase2; + +/** + * Test suite for {@link ICUVersionRecord} + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id$ + */ +public class TestICUVersionRecord extends TestCase2 { + + /** + * + */ + public TestICUVersionRecord() { + } + + /** + * @param name + */ + public TestICUVersionRecord(String name) { + super(name); + } + + public void test_roundTrip() { + + final ICUVersionRecord r1 = ICUVersionRecord.newInstance(); + + final ICUVersionRecord r2 = ICUVersionRecord.newInstance(); + + assertTrue(r1.equals(r2)); + + final ICUVersionRecord r3 = (ICUVersionRecord) SerializerUtil + .deserialize(SerializerUtil.serialize(r1)); + + assertTrue(r1.equals(r3)); + + } + +} Property changes on: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java ___________________________________________________________________ Added: svn:keywords + Id Date Revision Author HeadURL Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/TestDiskJournal.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/TestDiskJournal.java 2011-03-21 16:39:10 UTC (rev 4321) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/TestDiskJournal.java 2011-03-21 16:40:13 UTC (rev 4322) @@ -42,6 +42,8 @@ * * @author <a href="mailto:tho...@us...">Bryan Thompson</a> * @version $Id$ + * + * @deprecated by {@link TestWORMStrategy} */ public class TestDiskJournal extends AbstractJournalTestCase { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-03-23 12:27:01
|
Revision: 4326 http://bigdata.svn.sourceforge.net/bigdata/?rev=4326&view=rev Author: thompsonbry Date: 2011-03-23 12:26:50 +0000 (Wed, 23 Mar 2011) Log Message: ----------- Bug fix for the ICUVersionRecord deserialization. Extension of the unit tests to correctly detect this problem. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java 2011-03-23 00:48:21 UTC (rev 4325) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/keys/ICUVersionRecord.java 2011-03-23 12:26:50 UTC (rev 4326) @@ -122,7 +122,7 @@ } - private ICUVersionRecord(// + ICUVersionRecord(// VersionInfo icuVersion,// VersionInfo ucolRuntimeVesion,// VersionInfo ucolBuilderVersion,// @@ -184,8 +184,8 @@ final int major = in.readInt(); final int minor = in.readInt(); + final int micro = in.readInt(); final int milli = in.readInt(); - final int micro = in.readInt(); return VersionInfo.getInstance(major, minor, milli, micro); @@ -246,6 +246,14 @@ System.out.println(ICUVersionRecord.newInstance().toString()); + final ICUVersionRecord a = ICUVersionRecord.newInstance(); + final ICUVersionRecord b = ICUVersionRecord.newInstance(); + + if(!a.equals(b)) + throw new AssertionError(); + if(!b.equals(a)) + throw new AssertionError(); + } } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java 2011-03-23 00:48:21 UTC (rev 4325) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/keys/TestICUVersionRecord.java 2011-03-23 12:26:50 UTC (rev 4326) @@ -28,6 +28,7 @@ package com.bigdata.btree.keys; import com.bigdata.io.SerializerUtil; +import com.ibm.icu.util.VersionInfo; import junit.framework.TestCase2; @@ -67,4 +68,31 @@ } + public void test_roundTrip2() { + + final ICUVersionRecord r1 = new ICUVersionRecord( + VersionInfo.getInstance(3, 6, 2, 1),// + VersionInfo.getInstance(1, 8, 5, 7),// + VersionInfo.getInstance(6, 3, 1, 8),// + VersionInfo.getInstance(4, 6, 8, 12)// + ); + + final ICUVersionRecord r2 = new ICUVersionRecord( + VersionInfo.getInstance(3, 6, 2, 1),// + VersionInfo.getInstance(1, 8, 5, 7),// + VersionInfo.getInstance(6, 3, 1, 8),// + VersionInfo.getInstance(4, 6, 8, 12)// + ); + + assertTrue(r1.equals(r2)); + + assertFalse(r1.equals(ICUVersionRecord.newInstance())); + + final ICUVersionRecord r3 = (ICUVersionRecord) SerializerUtil + .deserialize(SerializerUtil.serialize(r1)); + + assertTrue(r1.equals(r3)); + + } + } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-03-30 18:02:03
|
Revision: 4351 http://bigdata.svn.sourceforge.net/bigdata/?rev=4351&view=rev Author: thompsonbry Date: 2011-03-30 18:01:55 +0000 (Wed, 30 Mar 2011) Log Message: ----------- Commit of partial progress towards a subquery hash join implementation. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/MemorySortOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestAll.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryOp.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryHashJoinOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/AbstractSubqueryTestCase.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryHashJoinOp.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineOp.java 2011-03-30 17:08:25 UTC (rev 4350) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/PipelineOp.java 2011-03-30 18:01:55 UTC (rev 4351) @@ -38,6 +38,7 @@ import com.bigdata.bop.engine.IChunkMessage; import com.bigdata.bop.engine.QueryEngine; import com.bigdata.bop.solutions.SliceOp; +import com.bigdata.bop.solutions.SortOp.Annotations; import com.bigdata.relation.accesspath.IAsynchronousIterator; /** @@ -160,33 +161,18 @@ */ int DEFAULT_PIPELINE_QUEUE_CAPACITY = 10; - /** + /** * Annotation used to mark pipelined (aka vectored) operators. When * <code>false</code> the operator will use either "at-once" or * "blocked" evaluation depending on how it buffers its data for * evaluation. - */ - String PIPELINED = (PipelineOp.class.getName() + ".pipelined").intern(); - - /** - * @see #PIPELINED - */ - boolean DEFAULT_PIPELINED = true; - - /** - * For non-{@link #PIPELINED} operators, this non-negative value - * specifies the maximum #of bytes which the operator may buffer on the - * native heap before evaluation of the operator is triggered -or- ZERO - * (0) if the operator buffers the data on the Java heap (default - * {@value #DEFAULT_MAX_MEMORY}). When non-zero, the #of bytes specified - * should be a multiple of 4k. For a shared operation, the value is the - * maximum #of bytes which may be buffered per shard. * <p> - * Operator "at-once" evaluation will be used if either (a) the operator - * is buffering data on the Java heap; or (b) the operator is buffering - * data on the native heap and the amount of buffered data does not - * exceed the specified value for {@link #MAX_MEMORY}. For convenience, - * the value {@link Integer#MAX_VALUE} may be specified to indicate that + * When <code>false</code>, operator "at-once" evaluation will be used + * if either (a) the operator is buffering data on the Java heap; or (b) + * the operator is buffering data on the native heap and the amount of + * buffered data does not exceed the specified value for + * {@link #MAX_MEMORY}. For convenience, you may specify + * {@link Integer#MAX_VALUE} for {@link #MAX_MEMORY} to indicate that * "at-once" evaluation is required. * <p> * When data are buffered on the Java heap, "at-once" evaluation is @@ -202,6 +188,21 @@ * semantics. Such operators MUST throw an exception if the value of * this annotation could result in multiple evaluation passes. */ + String PIPELINED = (PipelineOp.class.getName() + ".pipelined").intern(); + + /** + * @see #PIPELINED + */ + boolean DEFAULT_PIPELINED = true; + + /** + * For non-{@link #PIPELINED} operators, this non-negative value + * specifies the maximum #of bytes which the operator may buffer on the + * native heap before evaluation of the operator is triggered -or- ZERO + * (0) if the operator buffers the data on the Java heap (default + * {@value #DEFAULT_MAX_MEMORY}). For a sharded operation, the value is + * the maximum #of bytes which may be buffered per shard. + */ String MAX_MEMORY = (PipelineOp.class.getName() + ".maxMemory").intern(); /** @@ -456,4 +457,24 @@ } + /** + * Assert that this operator is annotated as an "at-once" operator which + * buffers its data on the java heap. + */ + protected void assertAtOnceJavaHeapOp() { + + // operator may not be broken into multiple tasks. + if (getMaxParallel() != 1) { + throw new UnsupportedOperationException(Annotations.MAX_PARALLEL + + "=" + getMaxParallel()); + } + + // operator is "at-once" (not pipelined). + if (isPipelined()) { + throw new UnsupportedOperationException(Annotations.PIPELINED + "=" + + isPipelined()); + } + + } + } Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryHashJoinOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryHashJoinOp.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryHashJoinOp.java 2011-03-30 18:01:55 UTC (rev 4351) @@ -0,0 +1,424 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2010. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on Aug 18, 2010 + */ + +package com.bigdata.bop.controller; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; + +import com.bigdata.bop.BOp; +import com.bigdata.bop.BOpContext; +import com.bigdata.bop.BOpUtility; +import com.bigdata.bop.IBindingSet; +import com.bigdata.bop.IConstraint; +import com.bigdata.bop.IPredicate; +import com.bigdata.bop.IVariable; +import com.bigdata.bop.NV; +import com.bigdata.bop.PipelineOp; +import com.bigdata.bop.engine.IRunningQuery; +import com.bigdata.bop.engine.QueryEngine; +import com.bigdata.bop.join.PipelineJoin; +import com.bigdata.relation.accesspath.IAsynchronousIterator; + +/** + * Hash join with subquery. + * <p> + * All source solutions are fully materialized in a hash table. The keys of the + * hash table are the as-bound join variable(s). The values in the hash table is + * the list of solutions having a specific value for the as-bound join + * variables. Once all solutions are materialized, the subquery is evaluated + * once. For each solution materialized by the subquery, the operator probes the + * hash table using the as-bound join variables for the subquery solution. If + * there is a hit in the hash table, then operator then outputs the cross + * product of the subquery solution with the solutions list found under that key + * in the hash table, applying any optional CONSTRAINTS. + * <p> + * In order to support OPTIONAL semantics for the subquery, a bit flag must be + * carried for each entry in the hash table. Once the subquery solutions have + * been exhausted, if the bit was never set for some entry and the subquery is + * optional, then the solutions associated with that entry are output, applying + * any optional CONSTRAINTS. + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + */ +public class SubqueryHashJoinOp extends PipelineOp { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public interface Annotations extends PipelineOp.Annotations { + + /** + * The join variables (required). This is an {@link IVariable}[] with + * at least one variable. The order of the entries is used when forming + * the as-bound keys for the hash table. Duplicate elements and null + * elements are not permitted. + */ + String JOIN_VARS = SubqueryHashJoinOp.class.getName() + ".subquery"; + + /** + * The subquery to be evaluated (required). This should be a + * {@link PipelineOp}. (It is basically the equivalent of the + * {@link IPredicate} for a {@link PipelineJoin}). + */ + String SUBQUERY = SubqueryHashJoinOp.class.getName() + ".subquery"; + + /** + * An optional {@link IVariable}[] identifying the variables to be + * retained in the {@link IBindingSet}s written out by the operator. All + * variables are retained unless this annotation is specified. + * + * @todo This should be on {@link SubqueryOp} as well. + */ + String SELECT = SubqueryHashJoinOp.class.getName() + ".select"; + + /** + * An {@link IConstraint}[] which places restrictions on the legal + * patterns in the variable bindings (optional). + * + * @todo This should be on {@link SubqueryOp} as well. + */ + String CONSTRAINTS = SubqueryHashJoinOp.class.getName() + ".constraints"; + + /** + * When <code>true</code> the subquery has optional semantics (if the + * subquery fails, the original binding set will be passed along to the + * downstream sink anyway) (default {@value #DEFAULT_OPTIONAL}). + */ + String OPTIONAL = SubqueryHashJoinOp.class.getName() + ".optional"; + + boolean DEFAULT_OPTIONAL = false; + + } + +// /** +// * @see Annotations#MAX_PARALLEL +// */ +// public int getMaxParallel() { +// return getProperty(Annotations.MAX_PARALLEL, +// Annotations.DEFAULT_MAX_PARALLEL); +// } + + /** + * Deep copy constructor. + */ + public SubqueryHashJoinOp(final SubqueryHashJoinOp op) { + super(op); + } + + /** + * Shallow copy constructor. + * + * @param args + * @param annotations + */ + public SubqueryHashJoinOp(final BOp[] args, + final Map<String, Object> annotations) { + + super(args, annotations); + +// if (!getEvaluationContext().equals(BOpEvaluationContext.CONTROLLER)) +// throw new IllegalArgumentException(Annotations.EVALUATION_CONTEXT +// + "=" + getEvaluationContext()); + + final IVariable<?>[] joinVars = (IVariable[]) getRequiredProperty(Annotations.JOIN_VARS); + + if (joinVars.length == 0) + throw new IllegalArgumentException(Annotations.JOIN_VARS); + + for (IVariable<?> var : joinVars) { + + if (var == null) + throw new IllegalArgumentException(Annotations.JOIN_VARS); + + } + + getRequiredProperty(Annotations.SUBQUERY); + + assertAtOnceJavaHeapOp(); + +// if (!getProperty(Annotations.CONTROLLER, Annotations.DEFAULT_CONTROLLER)) +// throw new IllegalArgumentException(Annotations.CONTROLLER); + +// // The id of this operator (if any). +// final Integer thisId = (Integer)getProperty(Annotations.BOP_ID); +// +// for(BOp op : args) { +// +// final Integer sinkId = (Integer) op +// .getRequiredProperty(Annotations.SINK_REF); +// +// if(sinkId.equals(thisId)) +// throw new RuntimeException("Operand may not target ") +// +// } + + } + + public SubqueryHashJoinOp(final BOp[] args, NV... annotations) { + + this(args, NV.asMap(annotations)); + + } + + public FutureTask<Void> eval(final BOpContext<IBindingSet> context) { + + return new FutureTask<Void>(new ControllerTask(this, context)); + + } + + /** + * Evaluates the arguments of the operator as subqueries. The arguments are + * evaluated in order. An {@link Executor} with limited parallelism to + * evaluate the arguments. If the controller operator is interrupted, then + * the subqueries are cancelled. If a subquery fails, then all subqueries + * are cancelled. + */ + private static class ControllerTask implements Callable<Void> { + + private final BOpContext<IBindingSet> context; + private final boolean optional; + private final PipelineOp subquery; + + public ControllerTask(final SubqueryHashJoinOp controllerOp, final BOpContext<IBindingSet> context) { + + if (controllerOp == null) + throw new IllegalArgumentException(); + + if (context == null) + throw new IllegalArgumentException(); + + this.context = context; + + this.optional = controllerOp.getProperty(Annotations.OPTIONAL, + Annotations.DEFAULT_OPTIONAL); + + this.subquery = (PipelineOp) controllerOp + .getRequiredProperty(Annotations.SUBQUERY); + + } + + /** + * Evaluate the subquery. + * + * @todo Support limited parallelism for each binding set read from the + * source. We will need to keep track of the running subqueries in + * order to wait on them before returning from this method and in + * order to cancel them if something goes wrong. + */ + public Void call() throws Exception { + + try { + + final IAsynchronousIterator<IBindingSet[]> sitr = context + .getSource(); + + while(sitr.hasNext()) { + + final IBindingSet[] chunk = sitr.next(); + + for(IBindingSet bset : chunk) { + + final IRunningQuery runningSubquery = new SubqueryTask( + bset, subquery, context).call(); + + if (!runningSubquery.isDone()) { + + throw new AssertionError("Future not done: " + + runningSubquery.toString()); + + } + + } + + } + + // Now that we know the subqueries ran Ok, flush the sink. + context.getSink().flush(); + + // Done. + return null; + + } finally { + + context.getSource().close(); + + context.getSink().close(); + + if (context.getSink2() != null) + context.getSink2().close(); + + } + + } + + /** + * Run a subquery. + * + * @author <a href="mailto:tho...@us...">Bryan + * Thompson</a> + */ + private class SubqueryTask implements Callable<IRunningQuery> { + + /** + * The evaluation context for the parent query. + */ + private final BOpContext<IBindingSet> parentContext; + + /** + * The source binding set. This will be copied to the output if + * there are no solutions for the subquery (optional join + * semantics). + */ + private final IBindingSet bset; + + /** + * The root operator for the subquery. + */ + private final BOp subQueryOp; + + public SubqueryTask(final IBindingSet bset, final BOp subQuery, + final BOpContext<IBindingSet> parentContext) { + + this.bset = bset; + + this.subQueryOp = subQuery; + + this.parentContext = parentContext; + + } + + public IRunningQuery call() throws Exception { + + // The subquery + IRunningQuery runningSubquery = null; + // The iterator draining the subquery + IAsynchronousIterator<IBindingSet[]> subquerySolutionItr = null; + try { + + final QueryEngine queryEngine = parentContext.getRunningQuery() + .getQueryEngine(); + + runningSubquery = queryEngine.eval((PipelineOp) subQueryOp, + bset); + + long ncopied = 0L; + try { + + // Iterator visiting the subquery solutions. + subquerySolutionItr = runningSubquery.iterator(); + + // Copy solutions from the subquery to the query. + ncopied = BOpUtility.copy(subquerySolutionItr, + parentContext.getSink(), null/* sink2 */, + null/* constraints */, null/* stats */); + + // wait for the subquery to halt / test for errors. + runningSubquery.get(); + + } catch (InterruptedException ex) { + + // this thread was interrupted, so cancel the subquery. + runningSubquery + .cancel(true/* mayInterruptIfRunning */); + + // rethrow the exception. + throw ex; + + } + + if (ncopied == 0L && optional) { + + /* + * Since there were no solutions for the subquery, copy + * the original binding set to the default sink. + * + * @todo If we add a CONSTRAINTS annotation to the + * SubqueryOp then we need to make sure that it is + * applied to all solutions copied out of the subquery. + */ + + parentContext.getSink().add(new IBindingSet[]{bset}); + + } + + // done. + return runningSubquery; + + } catch (Throwable t) { + + if (runningSubquery == null + || runningSubquery.getCause() != null) { + /* + * If things fail before we start the subquery, or if a + * subquery fails (due to abnormal termination), then + * propagate the error to the parent and rethrow the + * first cause error out of the subquery. + * + * Note: IHaltable#getCause() considers exceptions + * triggered by an interrupt to be normal termination. + * Such exceptions are NOT propagated here and WILL NOT + * cause the parent query to terminate. + */ + throw new RuntimeException(ControllerTask.this.context + .getRunningQuery().halt( + runningSubquery == null ? t + : runningSubquery.getCause())); + } + + return runningSubquery; + + } finally { + + try { + + // ensure subquery is halted. + if (runningSubquery != null) + runningSubquery + .cancel(true/* mayInterruptIfRunning */); + + } finally { + + // ensure the subquery solution iterator is closed. + if (subquerySolutionItr != null) + subquerySolutionItr.close(); + + } + + } + + } + + } // SubqueryTask + + } // ControllerTask + +} Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryOp.java 2011-03-30 17:08:25 UTC (rev 4350) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryOp.java 2011-03-30 18:01:55 UTC (rev 4351) @@ -36,13 +36,17 @@ import com.bigdata.bop.BOpContext; import com.bigdata.bop.BOpUtility; import com.bigdata.bop.IBindingSet; +import com.bigdata.bop.IPredicate; import com.bigdata.bop.NV; import com.bigdata.bop.PipelineOp; import com.bigdata.bop.engine.IRunningQuery; import com.bigdata.bop.engine.QueryEngine; +import com.bigdata.bop.join.PipelineJoin; import com.bigdata.relation.accesspath.IAsynchronousIterator; /** + * Pipelined join with subquery. + * <p> * For each binding set presented, this operator executes a subquery. Any * solutions produced by the subquery are copied to the default sink. If no * solutions are produced and {@link Annotations#OPTIONAL} is <code>true</code>, @@ -51,12 +55,14 @@ * the parent query is cancelled. * * @todo Parallel evaluation of subqueries is not implemented. What is the - * appropriate parallelism for this operator? More parallelism should reduce - * latency but could increase the memory burden. Review this decision once we - * have the RWStore operating as a binding set buffer on the Java process heap. + * appropriate parallelism for this operator? More parallelism should + * reduce latency but could increase the memory burden. Review this + * decision once we have the RWStore operating as a binding set buffer on + * the Java process heap. * + * @todo Rename as SubqueryPipelineJoinOp and support for SELECT and CONSTRAINTS. + * * @author <a href="mailto:tho...@us...">Bryan Thompson</a> - * @version $Id$ * * @see AbstractSubqueryOp */ @@ -69,11 +75,12 @@ public interface Annotations extends PipelineOp.Annotations { - /** - * The subquery to be evaluated for each binding sets presented to the - * {@link SubqueryOp} (required). This should be a - * {@link PipelineOp}. - */ + /** + * The subquery to be evaluated for each binding sets presented to the + * {@link SubqueryOp} (required). This should be a {@link PipelineOp}. + * (It is basically the equivalent of the {@link IPredicate} for a + * {@link PipelineJoin}). + */ String SUBQUERY = (SubqueryOp.class.getName() + ".subquery").intern(); /** @@ -170,13 +177,11 @@ // // } - /** - * Evaluates the arguments of the operator as subqueries. The arguments are - * evaluated in order. An {@link Executor} with limited parallelism to - * evaluate the arguments. If the controller operator is interrupted, then - * the subqueries are cancelled. If a subquery fails, then all subqueries - * are cancelled. - */ + /** + * Evaluates the subquery for each source binding set. If the controller + * operator is interrupted, then the subqueries are cancelled. If a subquery + * fails, then all subqueries are cancelled. + */ private static class ControllerTask implements Callable<Void> { // private final SubqueryOp controllerOp; Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/MemorySortOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/MemorySortOp.java 2011-03-30 17:08:25 UTC (rev 4350) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/solutions/MemorySortOp.java 2011-03-30 18:01:55 UTC (rev 4351) @@ -74,18 +74,8 @@ + getEvaluationContext()); } - // operator may not be broken into multiple tasks. - if (getMaxParallel() != 1) { - throw new UnsupportedOperationException(Annotations.MAX_PARALLEL - + "=" + getMaxParallel()); - } + assertAtOnceJavaHeapOp(); - // operator is "at-once" (not pipelined). - if (isPipelined()) { - throw new UnsupportedOperationException(Annotations.PIPELINED + "=" - + isPipelined()); - } - } public FutureTask<Void> eval(final BOpContext<IBindingSet> context) { Added: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/AbstractSubqueryTestCase.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/AbstractSubqueryTestCase.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/AbstractSubqueryTestCase.java 2011-03-30 18:01:55 UTC (rev 4351) @@ -0,0 +1,223 @@ +package com.bigdata.bop.controller; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import com.bigdata.bop.IBindingSet; +import com.bigdata.relation.accesspath.IAsynchronousIterator; +import com.bigdata.relation.accesspath.ThickAsynchronousIterator; +import com.bigdata.striterator.ICloseableIterator; + +import junit.framework.TestCase2; + +/** + * Abstract base class for subquery join test suites. + * + * @author thompsonbry + */ +abstract public class AbstractSubqueryTestCase extends TestCase2 { + + public AbstractSubqueryTestCase() { + } + + public AbstractSubqueryTestCase(String name) { + super(name); + } + + + /** + * Return an {@link IAsynchronousIterator} that will read a single, + * empty {@link IBindingSet}. + * + * @param bindingSet + * the binding set. + */ + protected ThickAsynchronousIterator<IBindingSet[]> newBindingSetIterator( + final IBindingSet bindingSet) { + + return new ThickAsynchronousIterator<IBindingSet[]>( + new IBindingSet[][] { new IBindingSet[] { bindingSet } }); + + } + + /** + * Return an {@link IAsynchronousIterator} that will read a single, chunk + * containing all of the specified {@link IBindingSet}s. + * + * @param bindingSets + * the binding sets. + */ + protected ThickAsynchronousIterator<IBindingSet[]> newBindingSetIterator( + final IBindingSet[] bindingSets) { + + return new ThickAsynchronousIterator<IBindingSet[]>( + new IBindingSet[][] { bindingSets }); + + } + + /** + * Return an {@link IAsynchronousIterator} that will read a single, chunk + * containing all of the specified {@link IBindingSet}s. + * + * @param bindingSetChunks + * the chunks of binding sets. + */ + protected ThickAsynchronousIterator<IBindingSet[]> newBindingSetIterator( + final IBindingSet[][] bindingSetChunks) { + + return new ThickAsynchronousIterator<IBindingSet[]>(bindingSetChunks); + + } + + /** + * Verify the expected solutions. + * + * @param expected + * @param itr + */ + static public void assertSameSolutions(final IBindingSet[] expected, + final IAsynchronousIterator<IBindingSet[]> itr) { + try { + int n = 0; + while (itr.hasNext()) { + final IBindingSet[] e = itr.next(); + if (log.isInfoEnabled()) + log.info(n + " : chunkSize=" + e.length); + for (int i = 0; i < e.length; i++) { + if (log.isInfoEnabled()) + log.info(n + " : " + e[i]); + if (n >= expected.length) { + fail("Willing to deliver too many solutions: n=" + n + + " : " + e[i]); + } + if (!expected[n].equals(e[i])) { + fail("n=" + n + ", expected=" + expected[n] + + ", actual=" + e[i]); + } + n++; + } + } + assertEquals("Wrong number of solutions", expected.length, n); + } finally { + itr.close(); + } + } + + /** + * Verifies that the iterator visits the specified objects in some arbitrary + * ordering and that the iterator is exhausted once all expected objects + * have been visited. The implementation uses a selection without + * replacement "pattern". + * <p> + * Note: If the objects being visited do not correctly implement hashCode() + * and equals() then this can fail even if the desired objects would be + * visited. When this happens, fix the implementation classes. + */ + static public <T> void assertSameSolutionsAnyOrder(final T[] expected, + final Iterator<T> actual) { + + assertSameSolutionsAnyOrder("", expected, actual); + + } + + /** + * Verifies that the iterator visits the specified objects in some arbitrary + * ordering and that the iterator is exhausted once all expected objects + * have been visited. The implementation uses a selection without + * replacement "pattern". + * <p> + * Note: If the objects being visited do not correctly implement hashCode() + * and equals() then this can fail even if the desired objects would be + * visited. When this happens, fix the implementation classes. + */ + static public <T> void assertSameSolutionsAnyOrder(final String msg, + final T[] expected, final Iterator<T> actual) { + + try { + + /* + * Populate a map that we will use to realize the match and + * selection without replacement logic. The map uses counters to + * handle duplicate keys. This makes it possible to write tests in + * which two or more binding sets which are "equal" appear. + */ + + final int nrange = expected.length; + + final java.util.Map<T, AtomicInteger> range = new java.util.LinkedHashMap<T, AtomicInteger>(); + + for (int j = 0; j < nrange; j++) { + + AtomicInteger count = range.get(expected[j]); + + if (count == null) { + + count = new AtomicInteger(); + + } + + range.put(expected[j], count); + + count.incrementAndGet(); + + } + + // Do selection without replacement for the objects visited by + // iterator. + + for (int j = 0; j < nrange; j++) { + + if (!actual.hasNext()) { + + fail(msg + + ": Iterator exhausted while expecting more object(s)" + + ": index=" + j); + + } + + final T actualObject = actual.next(); + + if (log.isInfoEnabled()) + log.info("visting: " + actualObject); + + AtomicInteger counter = range.get(actualObject); + + if (counter == null || counter.get() == 0) { + + fail("Object not expected" + ": index=" + j + ", object=" + + actualObject); + + } + + counter.decrementAndGet(); + + } + + if (actual.hasNext()) { + + final List<T> remainder = new LinkedList<T>(); + + while(actual.hasNext()) { + remainder.add(actual.next()); + } + + fail("Iterator will deliver too many objects: reminder(" + + remainder.size() + ")=" + remainder); + + } + + } finally { + + if (actual instanceof ICloseableIterator<?>) { + + ((ICloseableIterator<T>) actual).close(); + + } + + } + + } + +} Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestAll.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestAll.java 2011-03-30 17:08:25 UTC (rev 4350) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestAll.java 2011-03-30 18:01:55 UTC (rev 4351) @@ -67,6 +67,8 @@ // suite.addTestSuite(TestSteps.class); suite.addTestSuite(TestSubqueryOp.class); + + suite.addTestSuite(TestSubqueryHashJoinOp.class); // @todo test STAR (transitive closure). // suite.addTestSuite(TestStar.class); Added: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryHashJoinOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryHashJoinOp.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryHashJoinOp.java 2011-03-30 18:01:55 UTC (rev 4351) @@ -0,0 +1,940 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2010. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on March 30, 2011 + */ +package com.bigdata.bop.controller; + +import java.util.Map; +import java.util.Properties; +import java.util.UUID; + +import com.bigdata.bop.BOp; +import com.bigdata.bop.BOpEvaluationContext; +import com.bigdata.bop.Constant; +import com.bigdata.bop.IBindingSet; +import com.bigdata.bop.IConstant; +import com.bigdata.bop.IConstraint; +import com.bigdata.bop.IVariable; +import com.bigdata.bop.IVariableOrConstant; +import com.bigdata.bop.NV; +import com.bigdata.bop.PipelineOp; +import com.bigdata.bop.Var; +import com.bigdata.bop.IPredicate.Annotations; +import com.bigdata.bop.ap.E; +import com.bigdata.bop.ap.Predicate; +import com.bigdata.bop.ap.R; +import com.bigdata.bop.bindingSet.ArrayBindingSet; +import com.bigdata.bop.bindingSet.HashBindingSet; +import com.bigdata.bop.bset.ConditionalRoutingOp; +import com.bigdata.bop.bset.StartOp; +import com.bigdata.bop.constraint.Constraint; +import com.bigdata.bop.constraint.NEConstant; +import com.bigdata.bop.engine.BOpStats; +import com.bigdata.bop.engine.IChunkMessage; +import com.bigdata.bop.engine.IRunningQuery; +import com.bigdata.bop.engine.LocalChunkMessage; +import com.bigdata.bop.engine.QueryEngine; +import com.bigdata.bop.join.PipelineJoin; +import com.bigdata.bop.solutions.SliceOp; +import com.bigdata.journal.BufferMode; +import com.bigdata.journal.ITx; +import com.bigdata.journal.Journal; +import com.bigdata.striterator.ChunkedArrayIterator; +import com.bigdata.striterator.Dechunkerator; + +/** + * Unit tests for {@link SubqueryHashJoinOp}. + * + * @author thompsonbry + * + * TODO There should be a standard join test case (w/o optional) for + * both {@link SubqueryHashJoinOp} and {@link SubqueryOp}. + * + * TODO There should be a test case using constraints. + * + * TODO There should be a test case using SELECT to drop variables which + * are not required in the generated binding sets (and that feature + * should be part of the {@link SubqueryOp} as well). + */ +public class TestSubqueryHashJoinOp extends AbstractSubqueryTestCase { + + public TestSubqueryHashJoinOp() { + super(); + } + + public TestSubqueryHashJoinOp(final String name) { + super(name); + } + + @Override + public Properties getProperties() { + + final Properties p = new Properties(super.getProperties()); + + p.setProperty(Journal.Options.BUFFER_MODE, BufferMode.Transient + .toString()); + + return p; + + } + + static private final String namespace = "ns"; + private Journal jnl; + private QueryEngine queryEngine; + + public void setUp() throws Exception { + + jnl = new Journal(getProperties()); + + loadData(jnl); + + queryEngine = new QueryEngine(jnl); + + queryEngine.init(); + + } + + /** + * Create and populate relation in the {@link #namespace}. + */ + private void loadData(final Journal store) { + + // create the relation. + final R rel = new R(store, namespace, ITx.UNISOLATED, new Properties()); + rel.create(); + + // data to insert (in key order for convenience). + final E[] a = {// + new E("Paul", "Mary"),// [0] + new E("Paul", "Brad"),// [1] + + new E("John", "Mary"),// [2] + new E("John", "Brad"),// [3] + + new E("Mary", "Brad"),// [4] + + new E("Brad", "Fred"),// [5] + new E("Brad", "Leon"),// [6] + }; + + // insert data (the records are not pre-sorted). + rel.insert(new ChunkedArrayIterator<E>(a.length, a, null/* keyOrder */)); + + // Do commit since not scale-out. + store.commit(); + + } + + public void tearDown() throws Exception { + + if (queryEngine != null) { + queryEngine.shutdownNow(); + queryEngine = null; + } + + if (jnl != null) { + jnl.destroy(); + jnl = null; + } + + } + + /** + * TODO Verify required and optional arguments, including "at-once". + */ + public void test_ctor() { + + fail("write tests"); + + } + + /** + * Unit test for optional join group. Three joins are used and target a + * {@link SliceOp}. The 2nd and 3rd joins are embedded in an + * {@link SubqueryOp}. + * <P> + * The optional join group takes the form: + * + * <pre> + * (a b) + * optional { + * (b c) + * (c d) + * } + * </pre> + * + * The (a b) tail will match everything in the knowledge base. The join + * group takes us two hops out from ?b. There should be four solutions that + * succeed the optional join group: + * + * <pre> + * (paul mary brad fred) + * (paul mary brad leon) + * (john mary brad fred) + * (john mary brad leon) + * </pre> + * + * and five more that don't succeed the optional join group: + * + * <pre> + * (paul brad) * + * (john brad) * + * (mary brad) * + * (brad fred) + * (brad leon) + * </pre> + * + * In this cases marked with a <code>*</code>, ?c will become temporarily + * bound to fred and leon (since brad knows fred and leon), but the (c d) + * tail will fail since fred and leon don't know anyone else. At this point, + * the ?c binding must be removed from the solution. + */ + public void test_query_join2_optionals() throws Exception { + + // main query + final int startId = 1; // + final int joinId1 = 2; // : base join group. + final int predId1 = 3; // (a b) + final int joinGroup1 = 9; + final int sliceId = 8; // + + // subquery + final int joinId2 = 4; // : joinGroup1 + final int predId2 = 5; // (b c) + final int joinId3 = 6; // : joinGroup1 + final int predId3 = 7; // (c d) + + final IVariable<?> a = Var.var("a"); + final IVariable<?> b = Var.var("b"); + final IVariable<?> c = Var.var("c"); + final IVariable<?> d = Var.var("d"); + + final PipelineOp startOp = new StartOp(new BOp[] {}, + NV.asMap(new NV[] {// + new NV(Predicate.Annotations.BOP_ID, startId),// + new NV(SliceOp.Annotations.EVALUATION_CONTEXT, + BOpEvaluationContext.CONTROLLER),// + })); + + final Predicate<?> pred1Op = new Predicate<E>( + new IVariableOrConstant[] { a, b }, NV + .asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId1),// + new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// + })); + + final Predicate<?> pred2Op = new Predicate<E>( + new IVariableOrConstant[] { b, c }, NV + .asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId2),// + new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// + })); + + final Predicate<?> pred3Op = new Predicate<E>( + new IVariableOrConstant[] { c, d }, NV + .asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId3),// + new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// + })); + + final PipelineOp join1Op = new PipelineJoin<E>(// + new BOp[]{startOp},// + new NV(Predicate.Annotations.BOP_ID, joinId1),// + new NV(PipelineJoin.Annotations.PREDICATE,pred1Op)); + + final PipelineOp subQuery; + { + final PipelineOp join2Op = new PipelineJoin<E>(// + new BOp[] { /*join1Op*/ },// + new NV(Predicate.Annotations.BOP_ID, joinId2),// +// new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// + new NV(PipelineJoin.Annotations.PREDICATE, pred2Op)// +// // join is optional. +// new NV(PipelineJoin.Annotations.OPTIONAL, true),// +// // optional target is the same as the default target. +// new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) + ); + + final PipelineOp join3Op = new PipelineJoin<E>(// + new BOp[] { join2Op },// + new NV(Predicate.Annotations.BOP_ID, joinId3),// +// new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// + new NV(PipelineJoin.Annotations.PREDICATE, pred3Op)// +// // join is optional. +// new NV(PipelineJoin.Annotations.OPTIONAL, true),// +// // optional target is the same as the default target. +// new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) + ); + subQuery = join3Op; + } + + final PipelineOp joinGroup1Op = new SubqueryHashJoinOp(new BOp[]{join1Op}, + new NV(Predicate.Annotations.BOP_ID, joinGroup1),// +// new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// + new NV(SubqueryOp.Annotations.SUBQUERY, subQuery),// +// , new NV(BOp.Annotations.CONTROLLER,true)// +// new NV(BOp.Annotations.EVALUATION_CONTEXT, +// BOpEvaluationContext.CONTROLLER)// + // join is optional. + new NV(SubqueryOp.Annotations.OPTIONAL, true)// +// // optional target is the same as the default target. +// new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) + ); + + final PipelineOp sliceOp = new SliceOp(// + new BOp[]{joinGroup1Op}, + NV.asMap(new NV[] {// + new NV(BOp.Annotations.BOP_ID, sliceId),// + new NV(BOp.Annotations.EVALUATION_CONTEXT, + BOpEvaluationContext.CONTROLLER),// + new NV(PipelineOp.Annotations.SHARED_STATE,true),// + })); + + final PipelineOp query = sliceOp; + + // start the query. + final UUID queryId = UUID.randomUUID(); + final IChunkMessage<IBindingSet> initialChunkMessage; + { + + final IBindingSet initialBindings = new HashBindingSet(); + +// initialBindings.set(Var.var("x"), new Constant<String>("Mary")); + + initialChunkMessage = new LocalChunkMessage<IBindingSet>(queryEngine, + queryId, startId,// + -1, // partitionId + newBindingSetIterator(initialBindings)); + } + final IRunningQuery runningQuery = queryEngine.eval(queryId, query, + initialChunkMessage); + + // verify solutions. + { + + // the expected solutions. + final IBindingSet[] expected = new IBindingSet[] {// + // four solutions where the optional join succeeds. + new ArrayBindingSet(// + new IVariable[] { a, b, c, d },// + new IConstant[] { new Constant<String>("Paul"), + new Constant<String>("Mary"), + new Constant<String>("Brad"), + new Constant<String>("Fred") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b, c, d },// + new IConstant[] { new Constant<String>("Paul"), + new Constant<String>("Mary"), + new Constant<String>("Brad"), + new Constant<String>("Leon") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b, c, d },// + new IConstant[] { new Constant<String>("John"), + new Constant<String>("Mary"), + new Constant<String>("Brad"), + new Constant<String>("Fred") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b, c, d },// + new IConstant[] { new Constant<String>("John"), + new Constant<String>("Mary"), + new Constant<String>("Brad"), + new Constant<String>("Leon") }// + ), + // plus anything we read from the first access path which did not + // pass the optional join + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("Paul"), + new Constant<String>("Brad") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("John"), + new Constant<String>("Brad") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("Mary"), + new Constant<String>("Brad") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("Brad"), + new Constant<String>("Fred") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("Brad"), + new Constant<String>("Leon") }// + ) + }; + + /* + * junit.framework.AssertionFailedError: Iterator will deliver too + * many objects: reminder(3)=[{ a=John, b=Brad }, { a=Mary, b=Brad + * }, { a=Paul, b=Brad }]. + */ + assertSameSolutionsAnyOrder(expected, + new Dechunkerator<IBindingSet>(runningQuery.iterator())); + + } + + // Wait until the query is done. + runningQuery.get(); + final Map<Integer, BOpStats> statsMap = runningQuery.getStats(); + { + // validate the stats map. + assertNotNull(statsMap); + assertEquals(4, statsMap.size()); + if (log.isInfoEnabled()) + log.info(statsMap.toString()); + } + + } + + /** + * Unit test for optional join group with a filter. Three joins are used and + * target a {@link SliceOp}. The 2nd and 3rd joins are embedded in an + * optional join group. The optional join group contains a filter. + * <p> + * The optional join group takes the form: + * + * <pre> + * (a b) + * optional { + * (b c) + * (c d) + * filter(d != Leon) + * } + * </pre> + * + * The (a b) tail will match everything in the knowledge base. The join + * group takes us two hops out from ?b. There should be two solutions that + * succeed the optional join group: + * + * <pre> + * (paul mary brad fred) + * (john mary brad fred) + * </pre> + * + * and five more that don't succeed the optional join group: + * + * <pre> + * (paul brad) * + * (john brad) * + * (mary brad) * + * (brad fred) + * (brad leon) + * </pre> + * + * In the cases marked with a <code>*</code>, ?c will become temporarily + * bound to fred and leon (since brad knows fred and leon), but the (c d) + * tail will fail since fred and leon don't know anyone else. At this point, + * the ?c binding must be removed from the solution. + * <p> + * The filter (d != Leon) will prune the two solutions: + * + * <pre> + * (paul mary brad leon) + * (john mary brad leon) + * </pre> + * + * since ?d is bound to Leon in those cases. + */ + public void test_query_optionals_filter() throws Exception { + + // main query + final int startId = 1; + final int joinId1 = 2; // + final int predId1 = 3; // (a,b) + final int joinGroup1 = 9; + final int sliceId = 8; + + // subquery + final int joinId2 = 4; // : group1 + final int predId2 = 5; // (b,c) + final int joinId3 = 6; // : group1 + final int predId3 = 7; // (c,d) + + + final IVariable<?> a = Var.var("a"); + final IVariable<?> b = Var.var("b"); + final IVariable<?> c = Var.var("c"); + final IVariable<?> d = Var.var("d"); + + final PipelineOp startOp = new StartOp(new BOp[] {}, + NV.asMap(new NV[] {// + new NV(Predicate.Annotations.BOP_ID, startId),// + new NV(SliceOp.Annotations.EVALUATION_CONTEXT, + BOpEvaluationContext.CONTROLLER),// + })); + + final Predicate<?> pred1Op = new Predicate<E>( + new IVariableOrConstant[] { a, b }, NV + .asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId1),// + new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// + })); + + final Predicate<?> pred2Op = new Predicate<E>( + new IVariableOrConstant[] { b, c }, NV + .asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId2),// + new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// + })); + + final Predicate<?> pred3Op = new Predicate<E>( + new IVariableOrConstant[] { c, d }, NV + .asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId3),// + new NV(Annotations.TIMESTAMP, ITx.READ_COMMITTED),// + })); + + final PipelineOp join1Op = new PipelineJoin<E>(// + new BOp[]{startOp},// + new NV(Predicate.Annotations.BOP_ID, joinId1),// + new NV(PipelineJoin.Annotations.PREDICATE,pred1Op)); + + final PipelineOp subQuery; + { + final PipelineOp join2Op = new PipelineJoin<E>(// + new BOp[] { /*join1Op*/ },// + new NV(Predicate.Annotations.BOP_ID, joinId2),// +// new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// + new NV(PipelineJoin.Annotations.PREDICATE, pred2Op)// +// // join is optional. +// new NV(PipelineJoin.Annotations.OPTIONAL, true),// +// // optional target is the same as the default target. +// new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) + ); + + final PipelineOp join3Op = new PipelineJoin<E>(// + new BOp[] { join2Op },// + new NV(Predicate.Annotations.BOP_ID, joinId3),// +// new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// + new NV(PipelineJoin.Annotations.PREDICATE, pred3Op),// + // constraint d != Leon + new NV(PipelineJoin.Annotations.CONSTRAINTS, + new IConstraint[] { Constraint.wrap(new NEConstant(d, new Constant<String>("Leon"))) }) +// // join is optional. +// new NV(PipelineJoin.Annotations.OPTIONAL, true),// +// // optional target is the same as the default target. +// new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) + ); + + subQuery = join3Op; + } + + final PipelineOp joinGroup1Op = new SubqueryHashJoinOp(new BOp[]{join1Op}, + new NV(Predicate.Annotations.BOP_ID, joinGroup1),// +// new NV(PipelineOp.Annotations.CONDITIONAL_GROUP, joinGroup1),// + new NV(SubqueryOp.Annotations.SUBQUERY, subQuery),// +// new NV(BOp.Annotations.CONTROLLER,true)// +// new NV(BOp.Annotations.EVALUATION_CONTEXT, +// BOpEvaluationContext.CONTROLLER)// + // join is optional. + new NV(SubqueryOp.Annotations.OPTIONAL, true)// +// // optional target is the same as the default target. +// new NV(PipelineOp.Annotations.ALT_SINK_REF, sliceId) + ); + + final PipelineOp sliceOp = new SliceOp(// + new BOp[]{joinGroup1Op}, + NV.asMap(new NV[] {// + new NV(BOp.Annotations.BOP_ID, sliceId),// + new NV(BOp.Annotations.EVALUATION_CONTEXT, + BOpEvaluationContext.CONTROLLER),// + new NV(PipelineOp.Annotations.SHARED_STATE,true),// + })); + + final PipelineOp query = sliceOp; + + // start the query. + final UUID queryId = UUID.randomUUID(); + final IChunkMessage<IBindingSet> initialChunkMessage; + { + + final IBindingSet initialBindings = new HashBindingSet(); + +// initialBindings.set(Var.var("x"), new Constant<String>("Mary")); + + initialChunkMessage = new LocalChunkMessage<IBindingSet>(queryEngine, + queryId, startId,// + -1, // partitionId + newBindingSetIterator(initialBindings)); + } + final IRunningQuery runningQuery = queryEngine.eval(queryId, query, + initialChunkMessage); + + // verify solutions. + { + + // the expected solutions. + final IBindingSet[] expected = new IBindingSet[] {// + // two solutions where the optional join succeeds. + new ArrayBindingSet(// + new IVariable[] { a, b, c, d },// + new IConstant[] { new Constant<String>("Paul"), + new Constant<String>("Mary"), + new Constant<String>("Brad"), + new Constant<String>("Fred") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b, c, d },// + new IConstant[] { new Constant<String>("John"), + new Constant<String>("Mary"), + new Constant<String>("Brad"), + new Constant<String>("Fred") }// + ), + // plus anything we read from the first access path which did not + // pass the optional join + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("Paul"), + new Constant<String>("Brad") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("John"), + new Constant<String>("Brad") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("Mary"), + new Constant<String>("Brad") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("Brad"), + new Constant<String>("Fred") }// + ), + new ArrayBindingSet(// + new IVariable[] { a, b },// + new IConstant[] { new Constant<String>("Brad"), + new Constant<String>("Leon") }// + ) + }; + + assertSameSolutionsAnyOrder(expected, + new Dechunkerator<IBindingSet>(runningQuery.iterator())); + + } + + // Wait until the query is done. + runningQuery.get(); + final Map<Integer, BOpStats> statsMap = runningQuery.getStats(); + { + // validate the stats map. + assertNotNull(statsMap); + assertEquals(4, statsMap.size()); + if (log.isInfoEnabled()) + log.info(statsMap.toString()); + } + + } + + /** + * Unit test for optional join group with a filter on a variable outside the + * optional join group. Three joins are used and target a {@link SliceOp}. + * The 2nd and 3rd joins are in embedded an {@link SubqueryOp}. The + * optional join group contains a filter that uses a variable outside the + * optional join group. + * <P> + * The query takes the form: + * + * <pre> + * (a b) + * optional { + * (b c) + * (c d) + * filter(a != Paul) + * } + * </pre> + * + * The (a b) tail will match everything in the knowledge base. The join + * group takes us two hops out from ?b. There should be two solutions that + * succeed the optional join group: + * + * <pre> + * (john mary brad fred) + * (john mary brad leon) + * </pre> + * + * and six more that don't succeed the optional join group: + * + * <pre> + * (paul mary) * + * (paul brad) * + * (john brad) + * (mary brad) + * (brad fred) + * (brad leon) + * </pre> + * + * In the cases marked with a <code>*</code>, ?a is bound to Paul even + * though there is a filter that specifically prohibits a = Paul. This is + * because the filter is inside the optional join group, which means that + * solutions can still include a = Paul, but the optional join group should + * not run in that case. + */ + public void test_query_optionals_filter2() throws Exception { + + // main query + final int startId = 1; + final int joinId1 = 2; + final int predId1 = 3; // (a,b) + final int condId = 4; // (a != Paul) + f... [truncated message content] |
From: <tho...@us...> - 2011-03-31 17:53:47
|
Revision: 4358 http://bigdata.svn.sourceforge.net/bigdata/?rev=4358&view=rev Author: thompsonbry Date: 2011-03-31 17:53:40 +0000 (Thu, 31 Mar 2011) Log Message: ----------- Added support for SELECT and CONSTRAINTS to SubqueryOp. Added unit tests for simple subquery join, subquery join with SELECT, and subquery join with CONSTRAINTS. Modified the method signature for BOpUtility#copy(...) Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/bset/CopyOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/AbstractSubqueryOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryOp.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StandaloneChainedRunningQuery.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/joinGraph/rto/JoinGraph.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryOp.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java 2011-03-31 17:11:26 UTC (rev 4357) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/BOpUtility.java 2011-03-31 17:53:40 UTC (rev 4358) @@ -888,29 +888,35 @@ } - /** - * Copy binding sets from the source to the sink(s). - * - * @param source - * The source. - * @param sink - * The sink (required). - * @param sink2 - * Another sink (optional). - * @param constraints - * Binding sets which fail these constraints will NOT be copied - * (optional). - * @param stats - * The {@link BOpStats#chunksIn} and {@link BOpStats#unitsIn} - * will be updated during the copy (optional). - * - * @return The #of binding sets copied. - */ + /** + * Copy binding sets from the source to the sink(s). + * + * @param source + * The source. + * @param sink + * The sink (required). + * @param sink2 + * Another sink (optional). + * @param select + * The variables to be retained (optional). When not specified, + * all variables will be retained. + * @param constraints + * Binding sets which fail these constraints will NOT be copied + * (optional). + * @param stats + * The {@link BOpStats#chunksIn} and {@link BOpStats#unitsIn} + * will be updated during the copy (optional). + * + * @return The #of binding sets copied. + */ static public long copy( final IAsynchronousIterator<IBindingSet[]> source, final IBlockingBuffer<IBindingSet[]> sink, final IBlockingBuffer<IBindingSet[]> sink2, - final IConstraint[] constraints, final BOpStats stats) { + final IVariable<?>[] select,// + final IConstraint[] constraints, // + final BOpStats stats// + ) { long nout = 0; @@ -926,9 +932,10 @@ } - // apply optional constraints. - final IBindingSet[] tmp = applyConstraints(chunk,constraints); - + // apply optional constraints and optional SELECT. + final IBindingSet[] tmp = applyConstraints(chunk, select, + constraints); + // System.err.println("Copying: "+Arrays.toString(tmp)); // copy accepted binding sets to the default sink. @@ -955,18 +962,23 @@ * * @param chunk * A chunk of binding sets. + * @param select + * The variables to be retained (optional). When not specified, + * all variables will be retained. * @param constraints * The constraints (optional). * * @return The dense chunk of binding sets. */ static private IBindingSet[] applyConstraints(final IBindingSet[] chunk, + final IVariable<?>[] select, final IConstraint[] constraints) { - if (constraints == null) { + if (constraints == null && select == null) { /* - * No constraints, copy all binding sets. + * No constraints and everything is selected, so just return the + * caller's chunk. */ return chunk; @@ -983,14 +995,23 @@ for (int i = 0; i < chunk.length; i++) { - final IBindingSet bindingSet = chunk[i]; + IBindingSet bindingSet = chunk[i]; - if (BOpUtility.isConsistent(constraints, bindingSet)) { + if (constraints != null + && !BOpUtility.isConsistent(constraints, bindingSet)) { - t[j++] = bindingSet; + continue; } + if (select != null) { + + bindingSet = bindingSet.copy(select); + + } + + t[j++] = bindingSet; + } if (j != chunk.length) { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/bset/CopyOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/bset/CopyOp.java 2011-03-31 17:11:26 UTC (rev 4357) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/bset/CopyOp.java 2011-03-31 17:53:40 UTC (rev 4358) @@ -36,6 +36,7 @@ import com.bigdata.bop.BOpUtility; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.IConstraint; +import com.bigdata.bop.IVariable; import com.bigdata.bop.PipelineOp; import com.bigdata.bop.engine.BOpStats; import com.bigdata.bop.engine.IChunkAccessor; @@ -65,16 +66,22 @@ public interface Annotations extends PipelineOp.Annotations { /** + * An optional {@link IVariable}[] which specifies which variables will + * have their bindings copied. + */ + String SELECT = CopyOp.class.getName() + ".select"; + + /** * An optional {@link IConstraint}[] which places restrictions on the * legal patterns in the variable bindings. */ - String CONSTRAINTS = (CopyOp.class.getName() + ".constraints").intern(); + String CONSTRAINTS = CopyOp.class.getName() + ".constraints"; /** * An optional {@link IBindingSet}[] to be used <strong>instead</strong> * of the default source. */ - String BINDING_SETS = (CopyOp.class.getName() + ".bindingSets").intern(); + String BINDING_SETS = CopyOp.class.getName() + ".bindingSets"; } @@ -98,6 +105,15 @@ } /** + * @see Annotations#SELECT + */ + public IVariable<?>[] getSelect() { + + return getProperty(Annotations.SELECT, null/* defaultValue */); + + } + + /** * @see Annotations#CONSTRAINTS */ public IConstraint[] constraints() { @@ -156,6 +172,8 @@ final BOpStats stats = context.getStats(); + final IVariable<?>[] select = op.getSelect(); + final IConstraint[] constraints = op.constraints(); try { @@ -168,12 +186,13 @@ BOpUtility.copy( new ThickAsynchronousIterator<IBindingSet[]>( new IBindingSet[][] { bindingSets }), sink, - sink2, constraints, stats); + sink2, select, constraints, stats); } else { // copy binding sets from the source. - BOpUtility.copy(source, sink, sink2, constraints, stats); + BOpUtility.copy(source, sink, sink2, select, constraints, + stats); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/AbstractSubqueryOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/AbstractSubqueryOp.java 2011-03-31 17:11:26 UTC (rev 4357) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/AbstractSubqueryOp.java 2011-03-31 17:53:40 UTC (rev 4358) @@ -45,7 +45,6 @@ import com.bigdata.bop.engine.IRunningQuery; import com.bigdata.bop.engine.QueryEngine; import com.bigdata.relation.accesspath.IAsynchronousIterator; -import com.bigdata.service.IBigdataFederation; import com.bigdata.util.concurrent.LatchedExecutor; /** @@ -367,8 +366,8 @@ // Copy solutions from the subquery to the query. BOpUtility.copy(subquerySolutionItr, parentContext - .getSink(), null/* sink2 */, null/* constraints */, - null/* stats */); + .getSink(), null/* sink2 */, null/* select */, + null/* constraints */, null/* stats */); // wait for the subquery. runningSubquery.get(); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryOp.java 2011-03-31 17:11:26 UTC (rev 4357) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/controller/SubqueryOp.java 2011-03-31 17:53:40 UTC (rev 4358) @@ -35,11 +35,12 @@ import com.bigdata.bop.BOpContext; import com.bigdata.bop.BOpUtility; import com.bigdata.bop.IBindingSet; +import com.bigdata.bop.IConstraint; +import com.bigdata.bop.IVariable; import com.bigdata.bop.NV; import com.bigdata.bop.PipelineOp; import com.bigdata.bop.engine.IRunningQuery; import com.bigdata.bop.engine.QueryEngine; -import com.bigdata.bop.join.JoinAnnotations; import com.bigdata.relation.accesspath.IAsynchronousIterator; /** @@ -54,18 +55,13 @@ * * @todo Parallel evaluation of subqueries is not implemented. What is the * appropriate parallelism for this operator? More parallelism should - * reduce latency but could increase the memory burden. Review this - * decision once we have the RWStore operating as a binding set buffer on - * the Java process heap. + * reduce latency but could increase the memory burden. * - * @todo Rename as SubqueryPipelineJoinOp and support for SELECT and CONSTRAINTS. + * @todo Rename as SubqueryPipelineJoinOp. * * @author <a href="mailto:tho...@us...">Bryan Thompson</a> * * @see AbstractSubqueryOp - * - * FIXME Support {@link JoinAnnotations#SELECT} - * FIXME Support {@link JoinAnnotations#CONSTRAINTS} */ public class SubqueryOp extends PipelineOp { @@ -168,16 +164,23 @@ */ private static class ControllerTask implements Callable<Void> { -// private final SubqueryOp controllerOp; private final BOpContext<IBindingSet> context; // private final List<FutureTask<IRunningQuery>> tasks = new LinkedList<FutureTask<IRunningQuery>>(); // private final CountDownLatch latch; private final boolean optional; // private final int nparallel; +// /** The {@link SubqueryOp}. */ +// private final SubqueryOp controllerOp; + /** The subquery which is evaluated for each input binding set. */ private final PipelineOp subquery; + /** The selected variables for the output binding sets (optional). */ + private final IVariable<?>[] selectVars; + /** The optional constraints on the join. */ + private final IConstraint[] constraints; // private final Executor executor; - public ControllerTask(final SubqueryOp controllerOp, final BOpContext<IBindingSet> context) { + public ControllerTask(final SubqueryOp controllerOp, + final BOpContext<IBindingSet> context) { if (controllerOp == null) throw new IllegalArgumentException(); @@ -199,6 +202,12 @@ this.subquery = (PipelineOp) controllerOp .getRequiredProperty(Annotations.SUBQUERY); + this.selectVars = (IVariable<?>[]) controllerOp + .getProperty(Annotations.SELECT); + + this.constraints = (IConstraint[]) controllerOp + .getProperty(Annotations.CONSTRAINTS); + // this.executor = new LatchedExecutor(context.getIndexManager() // .getExecutorService(), nparallel); @@ -379,25 +388,6 @@ final QueryEngine queryEngine = parentContext.getRunningQuery() .getQueryEngine(); - -// final BOp startOp = BOpUtility.getPipelineStart(subQueryOp); -// -// final int startId = startOp.getId(); -// -// final UUID queryId = UUID.randomUUID(); -// -// // execute the subquery, passing in the source binding set. -// runningSubquery = queryEngine -// .eval( -// queryId, -// (PipelineOp) subQueryOp, -// new LocalChunkMessage<IBindingSet>( -// queryEngine, -// queryId, -// startId, -// -1 /* partitionId */, -// new ThickAsynchronousIterator<IBindingSet[]>( -// new IBindingSet[][] { new IBindingSet[] { bset } }))); runningSubquery = queryEngine.eval((PipelineOp) subQueryOp, bset); @@ -409,9 +399,10 @@ subquerySolutionItr = runningSubquery.iterator(); // Copy solutions from the subquery to the query. - ncopied = BOpUtility.copy(subquerySolutionItr, - parentContext.getSink(), null/* sink2 */, - null/* constraints */, null/* stats */); + ncopied = BOpUtility.copy(subquerySolutionItr, + parentContext.getSink(), null/* sink2 */, + selectVars, constraints, parentContext + .getStats()); // wait for the subquery to halt / test for errors. runningSubquery.get(); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StandaloneChainedRunningQuery.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StandaloneChainedRunningQuery.java 2011-03-31 17:11:26 UTC (rev 4357) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/engine/StandaloneChainedRunningQuery.java 2011-03-31 17:53:40 UTC (rev 4358) @@ -232,7 +232,8 @@ try { BOpUtility.copy(msg.getChunkAccessor().iterator(), sink, - null/* sink2 */, null/* constraints */, // + null/* sink2 */, null/* selectVars */, + null/* constraints */, // null/* stats */ ); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/joinGraph/rto/JoinGraph.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/joinGraph/rto/JoinGraph.java 2011-03-31 17:11:26 UTC (rev 4357) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/joinGraph/rto/JoinGraph.java 2011-03-31 17:53:40 UTC (rev 4358) @@ -362,9 +362,10 @@ subquerySolutionItr = runningQuery.iterator(); // Copy solutions from the subquery to the query. - final long nout = BOpUtility.copy(subquerySolutionItr, - parentContext.getSink(), null/* sink2 */, - null/* constraints */, null/* stats */); + final long nout = BOpUtility + .copy(subquerySolutionItr, parentContext.getSink(), + null/* sink2 */, null/* selectVars */, + null/* constraints */, null/* stats */); // System.out.println("nout=" + nout); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryOp.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryOp.java 2011-03-31 17:11:26 UTC (rev 4357) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/bop/controller/TestSubqueryOp.java 2011-03-31 17:53:40 UTC (rev 4358) @@ -27,6 +27,8 @@ package com.bigdata.bop.controller; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; @@ -51,6 +53,7 @@ import com.bigdata.bop.bset.ConditionalRoutingOp; import com.bigdata.bop.bset.StartOp; import com.bigdata.bop.constraint.Constraint; +import com.bigdata.bop.constraint.EQConstant; import com.bigdata.bop.constraint.NEConstant; import com.bigdata.bop.engine.BOpStats; import com.bigdata.bop.engine.IChunkMessage; @@ -164,6 +167,323 @@ } /** + * Unit test for a simple join. + */ + public void test_join() throws Exception { + +// final int startId = 1; + final int joinId = 2; + final int predId = 3; + final int subqueryId = 4; + + final IVariable<?> x = Var.var("x"); + final IVariable<?> y = Var.var("y"); + + final Predicate<E> predOp = new Predicate<E>(// + new IVariableOrConstant[] { // + new Constant<String>("John"), x }, // + NV.asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId),// + new NV(Annotations.TIMESTAMP, + ITx.READ_COMMITTED),// + })); + + // the subquery (basically, an access path read with "x" unbound). + final PipelineJoin<E> subquery = new PipelineJoin<E>( + new BOp[] { },// + new NV(Predicate.Annotations.BOP_ID, joinId),// + new NV(PipelineJoin.Annotations.PREDICATE, predOp)); + + // the hash-join against the subquery. + final SubqueryOp subqueryOp = new SubqueryOp( + new BOp[] {},// + new NV(Predicate.Annotations.BOP_ID, subqueryId),// + new NV(SubqueryOp.Annotations.SUBQUERY, subquery)// + ); + + final PipelineOp query = subqueryOp; + + // the expected solutions. + final IBindingSet[] expected = new IBindingSet[] {// + new ArrayBindingSet(// + new IVariable[] { x },// + new IConstant[] { new Constant<String>("Mary") }// + ),// + new ArrayBindingSet(// + new IVariable[] { x, y },// + new IConstant[] { new Constant<String>("Brad"), + new Constant<String>("Fred"), + }// + ),// + }; + + /* + * Setup the input binding sets. Each input binding set MUST provide + * binding for the join variable(s). + */ + final IBindingSet[] initialBindingSets; + { + final List<IBindingSet> list = new LinkedList<IBindingSet>(); + + IBindingSet tmp; + + tmp = new HashBindingSet(); + tmp.set(x, new Constant<String>("Brad")); + tmp.set(y, new Constant<String>("Fred")); + list.add(tmp); + + tmp = new HashBindingSet(); + tmp.set(x, new Constant<String>("Mary")); + list.add(tmp); + + initialBindingSets = list.toArray(new IBindingSet[0]); + + } + + final IRunningQuery runningQuery = queryEngine.eval(query, + initialBindingSets); + + TestQueryEngine.assertSameSolutionsAnyOrder(expected, runningQuery); + + { + final BOpStats stats = runningQuery.getStats().get( + subqueryId); + assertEquals(2L, stats.chunksIn.get()); + assertEquals(2L, stats.unitsIn.get()); + assertEquals(2L, stats.unitsOut.get()); + assertEquals(2L, stats.chunksOut.get()); + } + { + // // access path + // assertEquals(0L, stats.accessPathDups.get()); + // assertEquals(1L, stats.accessPathCount.get()); + // assertEquals(1L, stats.accessPathChunksIn.get()); + // assertEquals(2L, stats.accessPathUnitsIn.get()); + } + + assertTrue(runningQuery.isDone()); + assertFalse(runningQuery.isCancelled()); + runningQuery.get(); // verify nothing thrown. + + } + + /** + * Unit test for simple join with a constraint. + */ + public void test_joinWithConstraint() throws Exception { + +// final int startId = 1; + final int joinId = 2; + final int predId = 3; + final int subqueryId = 4; + + final IVariable<?> x = Var.var("x"); + final IVariable<?> y = Var.var("y"); + +// final IConstant<String>[] set = new IConstant[] {// +// new Constant<String>("Fred"),// +// }; + + final Predicate<E> predOp = new Predicate<E>(// + new IVariableOrConstant[] { // + new Constant<String>("John"), x }, // + NV.asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId),// + new NV(Annotations.TIMESTAMP, + ITx.READ_COMMITTED),// + })); + + // the subquery (basically, an access path read with "x" unbound). + final PipelineJoin<E> subquery = new PipelineJoin<E>( + new BOp[] { },// + new NV(Predicate.Annotations.BOP_ID, joinId),// + new NV(PipelineJoin.Annotations.PREDICATE, predOp)); + + // the hash-join against the subquery. + final SubqueryOp subqueryOp = new SubqueryOp( + new BOp[] {},// + new NV(Predicate.Annotations.BOP_ID, subqueryId),// + new NV(SubqueryOp.Annotations.SUBQUERY, subquery),// + new NV(SubqueryOp.Annotations.CONSTRAINTS, + new IConstraint[] { Constraint + .wrap(new EQConstant(x, new Constant<String>("Brad"))),// + })); + + final PipelineOp query = subqueryOp; + + // the expected solutions. + final IBindingSet[] expected = new IBindingSet[] {// +// new ArrayBindingSet(// +// new IVariable[] { x },// +// new IConstant[] { new Constant<String>("Mary") }// +// ),// + new ArrayBindingSet(// + new IVariable[] { x, y },// + new IConstant[] { new Constant<String>("Brad"), + new Constant<String>("Fred"), + }// + ),// + }; + + /* + * Setup the input binding sets. Each input binding set MUST provide + * binding for the join variable(s). + */ + final IBindingSet[] initialBindingSets; + { + final List<IBindingSet> list = new LinkedList<IBindingSet>(); + + IBindingSet tmp; + + tmp = new HashBindingSet(); + tmp.set(x, new Constant<String>("Brad")); + tmp.set(y, new Constant<String>("Fred")); + list.add(tmp); + + tmp = new HashBindingSet(); + tmp.set(x, new Constant<String>("Mary")); + list.add(tmp); + + initialBindingSets = list.toArray(new IBindingSet[0]); + + } + + final IRunningQuery runningQuery = queryEngine.eval(query, + initialBindingSets); + + TestQueryEngine.assertSameSolutionsAnyOrder(expected, runningQuery); + + { + final BOpStats stats = runningQuery.getStats().get( + subqueryId); + assertEquals(2L, stats.chunksIn.get()); + assertEquals(2L, stats.unitsIn.get()); + assertEquals(1L, stats.unitsOut.get()); + assertEquals(2L, stats.chunksOut.get()); + } + { + // // access path + // assertEquals(0L, stats.accessPathDups.get()); + // assertEquals(1L, stats.accessPathCount.get()); + // assertEquals(1L, stats.accessPathChunksIn.get()); + // assertEquals(2L, stats.accessPathUnitsIn.get()); + } + + assertTrue(runningQuery.isDone()); + assertFalse(runningQuery.isCancelled()); + runningQuery.get(); // verify nothing thrown. + + } + + /** + * Unit test for a simple join. + */ + public void test_join_selectOnly_x() throws Exception { + +// final int startId = 1; + final int joinId = 2; + final int predId = 3; + final int subqueryId = 4; + + final IVariable<?> x = Var.var("x"); + final IVariable<?> y = Var.var("y"); + + final Predicate<E> predOp = new Predicate<E>(// + new IVariableOrConstant[] { // + new Constant<String>("John"), x }, // + NV.asMap(new NV[] {// + new NV(Predicate.Annotations.RELATION_NAME, + new String[] { namespace }),// + new NV(Predicate.Annotations.BOP_ID, predId),// + new NV(Annotations.TIMESTAMP, + ITx.READ_COMMITTED),// + })); + + // the subquery (basically, an access path read with "x" unbound). + final PipelineJoin<E> subquery = new PipelineJoin<E>( + new BOp[] { },// + new NV(Predicate.Annotations.BOP_ID, joinId),// + new NV(PipelineJoin.Annotations.PREDICATE, predOp)); + + // the hash-join against the subquery. + final SubqueryOp subqueryOp = new SubqueryOp( + new BOp[] {},// + new NV(Predicate.Annotations.BOP_ID, subqueryId),// + new NV(SubqueryOp.Annotations.SELECT, new IVariable[]{x}),// + new NV(SubqueryOp.Annotations.SUBQUERY, subquery)// + ); + + final PipelineOp query = subqueryOp; + + // the expected solutions. + final IBindingSet[] expected = new IBindingSet[] {// + new ArrayBindingSet(// + new IVariable[] { x },// + new IConstant[] { new Constant<String>("Mary") }// + ),// + new ArrayBindingSet(// + new IVariable[] { x },// + new IConstant[] { new Constant<String>("Brad"), +// new Constant<String>("Fred"), + }// + ),// + }; + + /* + * Setup the input binding sets. Each input binding set MUST provide + * binding for the join variable(s). + */ + final IBindingSet[] initialBindingSets; + { + final List<IBindingSet> list = new LinkedList<IBindingSet>(); + + IBindingSet tmp; + + tmp = new HashBindingSet(); + tmp.set(x, new Constant<String>("Brad")); + tmp.set(y, new Constant<String>("Fred")); + list.add(tmp); + + tmp = new HashBindingSet(); + tmp.set(x, new Constant<String>("Mary")); + list.add(tmp); + + initialBindingSets = list.toArray(new IBindingSet[0]); + + } + + final IRunningQuery runningQuery = queryEngine.eval(query, + initialBindingSets); + + TestQueryEngine.assertSameSolutionsAnyOrder(expected, runningQuery); + + { + final BOpStats stats = runningQuery.getStats().get( + subqueryId); + assertEquals(2L, stats.chunksIn.get()); + assertEquals(2L, stats.unitsIn.get()); + assertEquals(2L, stats.unitsOut.get()); + assertEquals(2L, stats.chunksOut.get()); + } + { + // // access path + // assertEquals(0L, stats.accessPathDups.get()); + // assertEquals(1L, stats.accessPathCount.get()); + // assertEquals(1L, stats.accessPathChunksIn.get()); + // assertEquals(2L, stats.accessPathUnitsIn.get()); + } + + assertTrue(runningQuery.isDone()); + assertFalse(runningQuery.isCancelled()); + runningQuery.get(); // verify nothing thrown. + + } + + /** * Unit test for optional join group. Three joins are used and target a * {@link SliceOp}. The 2nd and 3rd joins are embedded in an * {@link SubqueryOp}. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mar...@us...> - 2011-04-19 15:11:00
|
Revision: 4415 http://bigdata.svn.sourceforge.net/bigdata/?rev=4415&view=rev Author: martyncutcher Date: 2011-04-19 15:10:52 +0000 (Tue, 19 Apr 2011) Log Message: ----------- Commits several modifications: 1) BufferedWriter interface to support write ellision 2) FutureTaskMon use to trace source of interrupted exceptions 3) AllocationContext refinements to support concurrent tasks Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/join/PipelineJoin.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/BufferedWrite.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/WriteCacheService.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/AbstractJournal.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/FileMetadata.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RWAddressManager.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RWStrategy.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RootBlockView.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/WriteExecutorService.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/AllocBlock.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/FixedAllocator.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWStore.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWWriteCacheService.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/StorageStats.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/TestRootBlockView.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/service/StressTestConcurrent.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/IBufferedWriter.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/join/PipelineJoin.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/join/PipelineJoin.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/bop/join/PipelineJoin.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -512,7 +512,7 @@ public FutureTask<Void> eval(final BOpContext<IBindingSet> context) { - return new FutureTask<Void>(new JoinTask<E>(this, context)); + return new FutureTaskMon<Void>(new JoinTask<E>(this, context)); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/BufferedWrite.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/BufferedWrite.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/BufferedWrite.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -59,7 +59,7 @@ * the slot. This can improve the IO efficiency When the slots are sized so * as to fall on multiples of sector boundaries. */ - private final RWStore m_store; + private final IBufferedWriter m_store; /** * The direct {@link ByteBuffer} used to combine writes which are contiguous @@ -95,7 +95,7 @@ private final CAT m_dataWrites = new CAT(); private final CAT m_fileWrites = new CAT(); - public BufferedWrite(final RWStore store) throws InterruptedException { + public BufferedWrite(final IBufferedWriter store) throws InterruptedException { if (store == null) throw new IllegalArgumentException(); Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/IBufferedWriter.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/IBufferedWriter.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/IBufferedWriter.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -0,0 +1,7 @@ +package com.bigdata.io.writecache; + +public interface IBufferedWriter { + + int getSlotSize(int data_len); + +} Property changes on: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/IBufferedWriter.java ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + Id Date Revision Author HeadURL Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/WriteCacheService.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/WriteCacheService.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/io/writecache/WriteCacheService.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -321,11 +321,11 @@ * * Toggle comment appropriately to activate/deactivate */ -/* final long[] addrsUsed = new long[4024 * 1024]; - int addrsUsedCurs = 0; - final char[] addrActions = new char[addrsUsed.length]; - final int[] addrLens = new int[addrsUsed.length]; -*/ private final long[] addrsUsed = null; +// final long[] addrsUsed = new long[4024 * 1024]; +// int addrsUsedCurs = 0; +// final char[] addrActions = new char[addrsUsed.length]; +// final int[] addrLens = new int[addrsUsed.length]; + private final long[] addrsUsed = null; private int addrsUsedCurs = 0; private final char[] addrActions = null; private final int[] addrLens = null; @@ -600,7 +600,7 @@ if (remoteWriteFuture != null) { try { remoteWriteFuture.get(); - } catch (ExecutionException ex) { + } catch (ExecutionException ex) { retrySend(quorum, cache, ex); } } @@ -769,6 +769,7 @@ // duplicate the write cache's buffer. final ByteBuffer b = cache.peek().duplicate(); + // final ByteBuffer b = ByteBuffer.allocate(0); // flip(limit=pos;pos=0) b.flip(); @@ -1812,6 +1813,9 @@ * When this method returns, the record will be on the disk and can * be read back safely from the disk. */ + if (log.isTraceEnabled()) + log.trace("FLUSHING LARGE RECORD"); + flush(false/* force */); // done. return true; @@ -1968,15 +1972,17 @@ * @param offset * the address to check */ - public void clearWrite(final long offset) { + public boolean clearWrite(final long offset) { try { final WriteCache cache = recordMap.remove(offset); if (cache == null) - return; + return false; final WriteCache cur = acquireForWriter(); // in case current debugAddrs(offset, 0, 'F'); try { cache.clearAddrMap(offset); + + return true; } finally { release(); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/AbstractJournal.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/AbstractJournal.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/AbstractJournal.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -833,12 +833,12 @@ lastCommitTime, commitCounter, commitRecordAddr, commitRecordIndexAddr, uuid, quorumToken, 0L, // metaStartAddr 0L, // metaStartBits StoreTypeEnum.WORM,// - createTime, closedTime, checker); + createTime, closedTime, RootBlockView.currentVersion, checker); final IRootBlockView rootBlock1 = new RootBlockView(false, offsetBits, nextOffset, firstCommitTime, lastCommitTime, commitCounter, commitRecordAddr, commitRecordIndexAddr, uuid, quorumToken, 0L, // metaStartAddr 0L, // metaStartBits StoreTypeEnum.WORM,// - createTime, closedTime, checker); + createTime, closedTime, RootBlockView.currentVersion, checker); _bufferStrategy.writeRootBlock(rootBlock0, ForceEnum.No); _bufferStrategy.writeRootBlock(rootBlock1, ForceEnum.No); @@ -1540,7 +1540,7 @@ metaBitsAddr, // old.getStoreType(), // old.getCreateTime(), closeTime, // - checker); + old.getVersion(), checker); /* * Write it on the store. @@ -2431,7 +2431,8 @@ lastCommitTime, newCommitCounter, commitRecordAddr, commitRecordIndexAddr, old.getUUID(), quorumToken, metaStartAddr, metaBitsAddr, old.getStoreType(), - old.getCreateTime(), old.getCloseTime(), checker); + old.getCreateTime(), old.getCloseTime(), + old.getVersion(), checker); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/FileMetadata.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/FileMetadata.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/FileMetadata.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -750,12 +750,12 @@ offsetBits, nextOffset, firstCommitTime, lastCommitTime, commitCounter, commitRecordAddr, commitRecordIndexAddr, uuid, quorumToken,// - 0L, 0L, stenum, createTime, closeTime, checker); + 0L, 0L, stenum, createTime, closeTime, version, checker); final IRootBlockView rootBlock1 = new RootBlockView(false, offsetBits, nextOffset, firstCommitTime, lastCommitTime, commitCounter, commitRecordAddr, commitRecordIndexAddr, uuid, quorumToken,// - 0L, 0L, stenum, createTime, closeTime, checker); + 0L, 0L, stenum, createTime, closeTime, version, checker); if (!temporary) { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RWAddressManager.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RWAddressManager.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RWAddressManager.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -1,6 +1,7 @@ package com.bigdata.journal; import com.bigdata.rawstore.IAddressManager; +import com.bigdata.rwstore.RWStore; /** * @@ -8,6 +9,11 @@ */ public class RWAddressManager implements IAddressManager { + RWStore m_store; + + public RWAddressManager(final RWStore store) { + m_store = store; + } public int getByteCount(final long addr) { return (int) (addr & 0xFFFFFFFFL); } @@ -21,8 +27,13 @@ } public String toString(final long addr) { - return "{off=" + getOffset(addr) + ",len=" + getByteCount(addr) - + "}"; + if (m_store == null) { + return "{off=NATIVE:" + getOffset(addr) + ",len=" + getByteCount(addr) + + "}"; + } else { + return "{off=" + m_store.physicalAddress((int) getOffset(addr)) + ",len=" + getByteCount(addr) + + "}"; + } } } \ No newline at end of file Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RWStrategy.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RWStrategy.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RWStrategy.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -76,7 +76,7 @@ private static final transient Logger log = Logger.getLogger(RWStrategy.class); - private final IAddressManager m_am = new RWAddressManager(); + private final IAddressManager m_am; /** * The backing store implementation. @@ -115,6 +115,8 @@ m_store = new RWStore(fileMetadata, quorum); + m_am = new RWAddressManager(m_store); + m_initialExtent = fileMetadata.file.length(); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RootBlockView.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RootBlockView.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/RootBlockView.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -279,7 +279,7 @@ 0L, // metaStartAddr 0L, // metaBitsAddr StoreTypeEnum.WORM, // - createTime, closeTime, checker); + createTime, closeTime, currentVersion, checker); } @@ -353,6 +353,7 @@ * used by the {@link ResourceManager} to indicate that a journal * is no longer available for writing (because it has been * superseded by another journal). + * @param version */ RootBlockView(// final boolean rootBlock0, final int offsetBits, @@ -365,7 +366,7 @@ final long metaBitsAddr, // VERSION1 final StoreTypeEnum storeTypeEnum, // VERSION1 final long createTime, final long closeTime, - final ChecksumUtility checker) + final int version, final ChecksumUtility checker) { // Note: There is a unit test specifically for this condition. @@ -383,7 +384,7 @@ case RW: { // @todo check metaStartAddr // @todo check metaBitsAddr - am = new RWAddressManager(); + am = new RWAddressManager(null); // @todo check nextOffset break; } @@ -647,7 +648,7 @@ switch (getStoreType()) { case RW: { - am = new RWAddressManager(); + am = new RWAddressManager(null); break; Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/WriteExecutorService.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/WriteExecutorService.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/journal/WriteExecutorService.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -913,6 +913,18 @@ final int nwrites = this.nwrites.incrementAndGet(); assert nwrites > 0; + + /* + * If an IsolatedActionJournal then we want to unravel any + * updates. + * + * So should we even be here doing a group commit? + */ +// IJournal jnl = r.getJournal(); +// if (jnl instanceof AbstractTask.IsolatedActionJournal) { +// // undo any journal writes prior to external commit +// ((AbstractTask.IsolatedActionJournal) jnl).abortContext(); +// } // add to the commit group. commitGroup.put(Thread.currentThread(), r); @@ -2300,6 +2312,7 @@ } // note: throws IllegalStateException if resource manager is not open. + // final AbstractJournal journal = resourceManager.getLiveJournal(); final AbstractJournal journal = resourceManager.getLiveJournal(); if(!journal.isOpen()) { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/AllocBlock.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/AllocBlock.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/AllocBlock.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -139,6 +139,10 @@ */ public boolean freeBit(final int bit, final boolean sessionProtect) { if (!RWStore.tstBit(m_live, bit)) { + + if (sessionProtect && RWStore.tstBit(m_transients, bit)) + return false; + throw new IllegalArgumentException("Freeing bit not set"); } @@ -293,13 +297,39 @@ * T 1100 1110 1111 1110 * C 1100 1100 1110 1100 */ - public void abortshadow() { + public void abortshadow(final RWWriteCacheService cache) { for (int i = 0; i < m_live.length; i++) { + final int startBit = i * 32; + final int chkbits = m_live[i] & ~m_commit[i]; + clearCacheBits(cache, startBit, chkbits); + m_live[i] &= m_commit[i]; m_transients[i] = m_live[i] | m_saveCommit[i]; } m_commit = m_saveCommit; } + + private int clearCacheBits(RWWriteCacheService cache, final int startBit, final int chkbits) { + int freebits = 0; + + if (chkbits != 0) { + // there are writes to clear + for (int b = 0; b < 32; b++) { + if ((chkbits & (1 << b)) != 0) { + long clr = RWStore.convertAddr(m_addr) + ((long) m_allocator.m_size * (startBit + b)); + + if (log.isTraceEnabled()) + log.trace("releasing address: " + clr); + + cache.clearWrite(clr); + + freebits++; + } + } + } + + return freebits; + } /** * When a session is active, the transient bits do not equate to an ORing @@ -325,21 +355,8 @@ chkbits &= ~m_transients[i]; final int startBit = i * 32; - if (chkbits != 0) { - // there are writes to clear - for (int b = 0; b < 32; b++) { - if ((chkbits & (1 << b)) != 0) { - long clr = RWStore.convertAddr(m_addr) + ((long) m_allocator.m_size * (startBit + b)); - - if (log.isTraceEnabled()) - log.trace("releasing address: " + clr); - - cache.clearWrite(clr); - - freebits++; - } - } - } + + freebits += clearCacheBits(cache, startBit, chkbits); } } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/FixedAllocator.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/FixedAllocator.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/FixedAllocator.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -153,7 +153,7 @@ } } - volatile private IAllocationContext m_context; + volatile IAllocationContext m_context; /** * Indicates whether session protection has been used to protect @@ -168,25 +168,34 @@ for (AllocBlock allocBlock : m_allocBlocks) { allocBlock.deshadow(); } - } else if (context != null & m_context == null) { + + // return to dirty list + m_store.addToCommit(this); + + } else if (context != null && m_context == null) { // restore commit bits in AllocBlocks for (AllocBlock allocBlock : m_allocBlocks) { allocBlock.shadow(); } + + // remove from dirty list if present! + // NO! m_store.removeFromCommit(this); } m_context = context; } /** - * Unwinds the allocations made within the context and clears + * Unwinds the allocations made within the context and clears the write + * cache of any associated data + * @param writeCacheService */ - public void abortAllocationContext(final IAllocationContext context) { - if (context != null && m_context == context) { + public void abortAllocationContext(final IAllocationContext context, RWWriteCacheService writeCacheService) { + if (m_context != null) { // restore commit bits in AllocBlocks for (AllocBlock allocBlock : m_allocBlocks) { - allocBlock.abortshadow(); + allocBlock.abortshadow(writeCacheService); } - m_context = null; + m_context = context; } else { throw new IllegalArgumentException(); } @@ -214,7 +223,11 @@ str.writeInt(block.m_addr); for (int i = 0; i < m_bitSize; i++) { - str.writeInt(block.m_live[i]); + if (m_context != null) { // shadowed + str.writeInt(block.m_transients[i]); + } else { + str.writeInt(block.m_live[i]); + } } if (!m_sessionActive) { @@ -513,19 +526,23 @@ final int block = offset/nbits; m_sessionActive = m_store.isSessionProtected(); - - if (((AllocBlock) m_allocBlocks.get(block)) - .freeBit(offset % nbits, m_sessionActive && !overideSession)) { // bit adjust + try { + if (((AllocBlock) m_allocBlocks.get(block)) + .freeBit(offset % nbits, m_sessionActive && !overideSession)) { // bit adjust + + m_freeBits++; + checkFreeList(); + } else { + m_freeTransients++; + } - m_freeBits++; - checkFreeList(); - } else { - m_freeTransients++; + if (m_statsBucket != null) { + m_statsBucket.delete(size); + } + } catch (IllegalArgumentException iae) { + // catch and rethrow with more information + throw new IllegalArgumentException("IAE with address: " + addr + ", size: " + size + ", context: " + (m_context == null ? -1 : m_context.hashCode()), iae); } - - if (m_statsBucket != null) { - m_statsBucket.delete(size); - } return true; } else if (addr >= m_startAddr && addr < m_endAddr) { @@ -797,6 +814,11 @@ return false; } } + + public boolean isUnsafeFree(final IAllocationContext context) { + // return m_context != null || context != null; // m_context != context; + return m_context != context; + } public void setBucketStats(Bucket b) { m_statsBucket = b; Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWStore.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWStore.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWStore.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -60,6 +60,7 @@ import com.bigdata.io.FileChannelUtility; import com.bigdata.io.IReopenChannel; import com.bigdata.io.writecache.BufferedWrite; +import com.bigdata.io.writecache.IBufferedWriter; import com.bigdata.io.writecache.WriteCache; import com.bigdata.io.writecache.WriteCacheService; import com.bigdata.journal.AbstractBufferStrategy; @@ -225,7 +226,7 @@ * space can be reclaimed). */ -public class RWStore implements IStore { +public class RWStore implements IStore, IBufferedWriter { private static final transient Logger log = Logger.getLogger(RWStore.class); @@ -1138,7 +1139,7 @@ m_allocs.add(allocator); if (m_storageStats != null) { - m_storageStats.register(allocator); + m_storageStats.register(allocator, true); } return allocator; @@ -1424,6 +1425,8 @@ c.release(); } } + } catch (PhysicalAddressResolutionException e) { + throw new IllegalArgumentException("Unable to read data: "+e, e); } catch (Throwable e) { /* * Note: ClosedByInterruptException can be thrown out of @@ -1432,8 +1435,6 @@ * error. */ // log.error(e,e); - -// throw new IllegalArgumentException("Unable to read data: "+e, e); throw new RuntimeException("addr=" + addr + " : cause=" + e, e); } @@ -1538,7 +1539,7 @@ free(laddr, sze, null/* AlocationContext */); } - + private long m_unsafeFrees = 0; /** * free * <p> @@ -1573,7 +1574,22 @@ freeBlob(addr, sze, context); } else { final FixedAllocator alloc = getBlockByAddress(addr); - /* + + /** + * If a request is made to free storage associated with some other + * allocation context, then it is unsafe to recycle. + */ + if (alloc.isUnsafeFree(context)) { + if ((++m_unsafeFrees % 5000) == 0 && log.isDebugEnabled()) { + log.debug("Unsafe frees : " + m_unsafeFrees + ", allocations: " + m_allocations + ", frees: " + m_frees); + StringBuilder sb = new StringBuilder(); + m_storageStats.showStats(sb); + log.debug(sb); + } + return; + } + + /* * There are a few conditions here. If the context owns the * allocator and the allocation was made by this context then it * can be freed immediately. The problem comes when the context @@ -1602,6 +1618,8 @@ immediateFree(addr, sze); } } else { + // if a free request is made within a context not managed by + // the allocator then it is not safe to free boolean alwaysDefer = m_activeTxCount > 0; if (!alwaysDefer) @@ -1733,14 +1751,15 @@ m_writeCache.clearWrite(pa); } m_frees++; + if (alloc.isAllocated(addrOffset)) throw new IllegalStateException("Reallocation problem with WriteCache"); - if (!m_commitList.contains(alloc)) { + if (alloc.m_context != null && !m_commitList.contains(alloc)) { m_commitList.add(alloc); - - m_recentAlloc = true; } + + m_recentAlloc = true; } finally { m_allocationLock.unlock(); } @@ -1837,7 +1856,7 @@ final int addr = allocator.alloc(this, size, context); - if (!m_commitList.contains(allocator)) { + if (allocator.m_context != null && !m_commitList.contains(allocator)) { m_commitList.add(allocator); } @@ -2169,7 +2188,7 @@ // return "RWStore " + s_version; // } - public void commitChanges(final Journal journal) { + public void commitChanges(final AbstractJournal journal) { assertOpen(); checkCoreAllocations(); @@ -2289,7 +2308,7 @@ * returns number of addresses freed */ /* public */int checkDeferredFrees(final boolean freeNow, - final Journal journal) { + final AbstractJournal journal) { // Note: Invoked from unit test w/o the lock... // if (!m_allocationLock.isHeldByCurrentThread()) @@ -2297,7 +2316,7 @@ if (journal != null) { - final JournalTransactionService transactionService = (JournalTransactionService) journal + final AbstractTransactionService transactionService = (AbstractTransactionService) journal .getLocalTransactionManager().getTransactionService(); // // the previous commit point. @@ -2492,8 +2511,6 @@ * the allocation blocks at the end of the file. */ int metaAlloc() { -// long lnextAlloc = convertAddr(m_nextAllocation); - int bit = fndMetabit(); if (bit < 0) { @@ -2910,7 +2927,7 @@ * latched2Physical **/ public long physicalAddress(final int addr) { - if (addr > 0) { + if (addr >= 0) { return addr & 0xFFFFFFE0; } else { final FixedAllocator allocator = getBlock(addr); @@ -3096,12 +3113,17 @@ // } // } - public void addToCommit(final Allocator allocator) { + void addToCommit(final Allocator allocator) { if (!m_commitList.contains(allocator)) { m_commitList.add(allocator); } } + + void removeFromCommit(final Allocator allocator) { + m_commitList.remove(allocator); + } + public Allocator getAllocator(final int i) { return (Allocator) m_allocs.get(i); } @@ -3138,61 +3160,40 @@ } - public FileChannel reopenChannel() throws IOException { + synchronized public FileChannel reopenChannel() throws IOException { - /* - * Note: This is basically a double-checked locking pattern. It is - * used to avoid synchronizing when the backing channel is already - * open. - */ - { - final RandomAccessFile tmp = raf; - if (tmp != null) { - final FileChannel channel = tmp.getChannel(); - if (channel.isOpen()) { - // The channel is still open. - return channel; - } - } - } - - synchronized(this) { + if (raf != null && raf.getChannel().isOpen()) { - if (raf != null) { - final FileChannel channel = raf.getChannel(); - if (channel.isOpen()) { - /* - * The channel is still open. If you are allowing - * concurrent reads on the channel, then this could - * indicate that two readers each found the channel - * closed and that one was able to re-open the channel - * before the other such that the channel was open again - * by the time the 2nd reader got here. - */ - return channel; - } - } + /* + * The channel is still open. If you are allowing concurrent + * reads on the channel, then this could indicate that two + * readers each found the channel closed and that one was able + * to re-open the channel before the other such that the channel + * was open again by the time the 2nd reader got here. + */ - // open the file. - this.raf = new RandomAccessFile(file, mode); + return raf.getChannel(); - // Update counters. - final StoreCounters<?> c = (StoreCounters<?>) storeCounters - .get().acquire(); - try { - c.nreopen++; - } finally { - c.release(); - } + } - return raf.getChannel(); + // open the file. + this.raf = new RandomAccessFile(file, mode); - } + // Update counters. + final StoreCounters<?> c = (StoreCounters<?>) storeCounters.get() + .acquire(); + try { + c.nreopen++; + } finally { + c.release(); + } + + return raf.getChannel(); } } - + /** * If the current file extent is different from the required extent then the * call is made to {@link #extendFile(int)}. @@ -3502,6 +3503,7 @@ final ContextAllocation alloc = m_contexts.remove(context); if (alloc != null) { + m_contextRemovals++; alloc.release(); } } finally { @@ -3525,6 +3527,7 @@ final ContextAllocation alloc = m_contexts.remove(context); if (alloc != null) { + m_contextRemovals++; alloc.abort(); } } finally { @@ -3592,12 +3595,13 @@ for (FixedAllocator f : m_allFixed) { f.setAllocationContext(pcontext); + // will add to free list if required f.setFreeList(freeFixed[m_store.fixedAllocatorIndex(f.m_size)]); } - for (int i = 0; i < m_freeFixed.length; i++) { - freeFixed[i].addAll(m_freeFixed[i]); - } +// for (int i = 0; i < m_freeFixed.length; i++) { +// freeFixed[i].addAll(m_freeFixed[i]); +// } // freeBlobs.addAll(m_freeBlobs); } @@ -3610,12 +3614,13 @@ : m_parent.m_context; for (FixedAllocator f : m_allFixed) { - f.abortAllocationContext(pcontext); + f.abortAllocationContext(pcontext, m_store.m_writeCache); + f.setFreeList(freeFixed[m_store.fixedAllocatorIndex(f.m_size)]); } - for (int i = 0; i < m_freeFixed.length; i++) { - freeFixed[i].addAll(m_freeFixed[i]); - } +// for (int i = 0; i < m_freeFixed.length; i++) { +// freeFixed[i].addAll(m_freeFixed[i]); +// } // freeBlobs.addAll(m_freeBlobs); } @@ -3625,8 +3630,7 @@ if (free.size() == 0) { final FixedAllocator falloc = establishFixedAllocator(i); falloc.setAllocationContext(m_context); - falloc.setFreeList(free); - free.add(falloc); + falloc.setFreeList(free); // will add to free list m_allFixed.add(falloc); } @@ -3656,6 +3660,8 @@ private final Map<IAllocationContext, ContextAllocation> m_contexts = new ConcurrentHashMap<IAllocationContext, ContextAllocation>(); + private int m_contextRequests = 0; + private int m_contextRemovals = 0; private ContextAllocation establishContextAllocation( final IAllocationContext context) { @@ -3668,7 +3674,7 @@ ContextAllocation ret = m_contexts.get(context); if (ret == null) { - + ret = new ContextAllocation(this, m_freeFixed.length, null, context); if (m_contexts.put(context, ret) != null) { @@ -3677,6 +3683,14 @@ } + if (log.isTraceEnabled()) + log.trace("Establish ContextAllocation: " + ret + + ", total: " + m_contexts.size() + + ", requests: " + ++m_contextRequests + + ", removals: " + m_contextRemovals + + ", allocators: " + m_allocs.size() ); + + if (log.isInfoEnabled()) log.info("Context: ncontexts=" + m_contexts.size() + ", context=" + context); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWWriteCacheService.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWWriteCacheService.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWWriteCacheService.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -42,7 +42,7 @@ * * @author mgc */ -public class RWWriteCacheService extends WriteCacheService { +public class RWWriteCacheService extends WriteCacheService implements IWriteCacheManager { protected static final Logger log = Logger.getLogger(RWWriteCacheService.class); @@ -74,5 +74,9 @@ (IReopenChannel<FileChannel>) opener, null); } + + public boolean removeWriteToAddr(long address) { + return clearWrite(address); + } } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/StorageStats.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/StorageStats.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/StorageStats.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -32,6 +32,8 @@ import java.math.RoundingMode; import java.util.ArrayList; +import com.bigdata.rwstore.sector.SectorAllocator; + /** * Maintains stats on the RWStore allocations, useful for tuning Allocator * sizes and tracking store efficiency. @@ -528,4 +530,9 @@ return used.divide(total, 2, RoundingMode.HALF_UP).floatValue(); } + + public void register(SectorAllocator allocator, boolean init) { + // TODO Auto-generated method stub + + } } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/TestRootBlockView.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/TestRootBlockView.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/TestRootBlockView.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -129,7 +129,7 @@ nextOffset, firstCommitTime, lastCommitTime, commitCounter, commitRecordAddr, commitRecordIndexAddr, uuid, quorum, metaStartAddr, metaBitsAddr, storeType, createTime, - closeTime, checker); + closeTime, RootBlockView.currentVersion, checker); if (log.isInfoEnabled()) log.info("pass=" + i + " of " + nrounds + " : challisField=" @@ -399,43 +399,46 @@ // legit. new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, - uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, closeTimeOk, checkerOk); + uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, closeTimeOk, + RootBlockView.currentVersion, checkerOk); // legit (firstCommitTimeOk2,lastCommitTimeOk2). new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk2, lastCommitTimeOk2, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, - uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, closeTimeOk, checkerOk); + uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, closeTimeOk, + RootBlockView.currentVersion, checkerOk); // legit (rootsAddr2, commitRecordIndex2) new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOk2, commitRecordAddrOk2, commitRecordIndexOk2, - uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, closeTimeOk, checkerOk); + uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, closeTimeOk, + RootBlockView.currentVersion, checkerOk); // legit (closeTime2) new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk2, checkerOk); + closeTimeOk2, RootBlockView.currentVersion, checkerOk); // legit (quorum) new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk2, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk2, checkerOk); + closeTimeOk2, RootBlockView.currentVersion, checkerOk); new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk3, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk2, checkerOk); + closeTimeOk2, RootBlockView.currentVersion, checkerOk); // FIXME do legit (metaStartAddr, metaBitsAddr) tests here. // legit (storeTypeEnum) new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk2, createTimeOk, - closeTimeOk2, checkerOk); + closeTimeOk2, RootBlockView.currentVersion, checkerOk); // bad offsetBits. try { new RootBlockView(rootBlock0, offsetBitsBad, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrBad, commitRecordIndexOk2, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -444,7 +447,7 @@ new RootBlockView(rootBlock0, offsetBitsBad2, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrBad, commitRecordIndexOk2, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -455,7 +458,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetBad, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -466,7 +469,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeBad1, lastCommitTimeBad1, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); } catch (IllegalArgumentException ex) { fail("Unexpected exception", ex); } @@ -474,7 +477,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeBad2, lastCommitTimeBad2, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -483,7 +486,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeBad3, lastCommitTimeBad3, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); } catch (IllegalArgumentException ex) { fail("Unexpected exception", ex); } @@ -491,7 +494,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeBad4, lastCommitTimeBad4, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); } catch (IllegalArgumentException ex) { fail("Unexpected exception", ex); } @@ -501,7 +504,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterBad, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -510,7 +513,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterBad2, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -522,7 +525,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOk2, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -532,7 +535,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOk2, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -542,7 +545,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOk2, commitRecordAddrOkZero, commitRecordIndexOk2, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -552,7 +555,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOk2, commitRecordAddrOk2, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -561,7 +564,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrBad, commitRecordIndexOk2, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -570,7 +573,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOk2, commitRecordIndexBad, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -583,7 +586,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOk2, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -594,7 +597,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrBad, commitRecordIndexOk2, uuidBad, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -605,7 +608,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrBad, commitRecordIndexOk2, uuidOk, quorumBad, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeBad, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -618,7 +621,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrBad, commitRecordIndexOk2, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeBad, createTimeBad, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -629,7 +632,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrBad, commitRecordIndexOk2, uuidBad, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeBad, - closeTimeOk, checkerOk); + closeTimeOk, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -640,7 +643,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrBad, commitRecordIndexOk2, uuidBad, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeBad, checkerOk); + closeTimeBad, RootBlockView.currentVersion, checkerOk); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); @@ -651,7 +654,7 @@ new RootBlockView(rootBlock0, offsetBitsOk, nextOffsetOk, firstCommitTimeOk, lastCommitTimeOk, commitCounterOkZero, commitRecordAddrOkZero, commitRecordIndexOkZero, uuidOk, quorumOk, metaStartAddr, metaBitsAddr, storeTypeOk, createTimeOk, - closeTimeOk, checkerBad); + closeTimeOk, RootBlockView.currentVersion, checkerBad); fail("Expecting: " + IllegalArgumentException.class); } catch (IllegalArgumentException ex) { if(log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -766,7 +766,7 @@ RWStore rw = bufferStrategy.getRWStore(); - final int tcount = 2000; // increase to ramp up stress levels + final int tcount = 5000; // increase to ramp up stress levels long numAllocs = rw.getTotalAllocations(); long startAllocations = rw.getTotalAllocationsSize(); @@ -820,6 +820,12 @@ } + public void notest_stressReallocationWithReadAndReopen() { + for (int i = 0; i < 20; i++) { + test_reallocationWithReadAndReopen(); + } + } + void showStore(Journal store) { RWStrategy bufferStrategy = (RWStrategy) store.getBufferStrategy(); @@ -896,7 +902,7 @@ try { - final int tcount = 2000; // increase to ramp up stress levels + final int tcount = 20000; // increase to ramp up stress levels RWStrategy bufferStrategy = (RWStrategy) store.getBufferStrategy(); @@ -1044,6 +1050,12 @@ } } + + public void notest_stressBlobReadBack() { + for (int i = 0; i < 100; i++) { + test_blob_readBack(); + } + } /** * Test of blob allocation and read-back, firstly from cache and then Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/service/StressTestConcurrent.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/service/StressTestConcurrent.java 2011-04-19 14:12:54 UTC (rev 4414) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/service/StressTestConcurrent.java 2011-04-19 15:10:52 UTC (rev 4415) @@ -134,7 +134,7 @@ final Properties properties = new Properties(super.getProperties()); // Make sure this test uses disk so that it can trigger overflows. - properties.setProperty(Options.BUFFER_MODE, BufferMode.Disk + properties.setProperty(Options.BUFFER_MODE, BufferMode.Disk .toString()); /* This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mar...@us...> - 2011-04-20 11:54:05
|
Revision: 4422 http://bigdata.svn.sourceforge.net/bigdata/?rev=4422&view=rev Author: martyncutcher Date: 2011-04-20 11:53:59 +0000 (Wed, 20 Apr 2011) Log Message: ----------- Fix transient reallocation logic related to AllocationContexts Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/FixedAllocator.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWStore.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/StressTestConcurrentUnisolatedIndices.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/FixedAllocator.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/FixedAllocator.java 2011-04-19 20:28:57 UTC (rev 4421) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/FixedAllocator.java 2011-04-20 11:53:59 UTC (rev 4422) @@ -114,7 +114,7 @@ final int bit = offset % allocBlockRange; // if (RWStore.tstBit(block.m_live, bit) -// || (m_sessionActive && RWStore.tstBit(block.m_transients, bit))) +// || (m_sessionActive && RWStore.tstBit(block.m_transients, bit))) { /* * Just check transients since there are case (eg CommitRecordIndex) * where committed data is accessed even if has been marked as ready to @@ -147,7 +147,7 @@ public void setFreeList(ArrayList list) { m_freeList = list; - if (hasFree()) { + if (!m_pendingContextCommit && hasFree()) { m_freeList.add(this); m_freeWaiting = false; } @@ -165,6 +165,9 @@ private boolean m_pendingContextCommit = false; public void setAllocationContext(final IAllocationContext context) { + if (m_pendingContextCommit) { + throw new IllegalStateException("Already pending commit"); + } if (context == null && m_context != null) { // restore commit bits in AllocBlocks for (AllocBlock allocBlock : m_allocBlocks) { @@ -193,6 +196,10 @@ * @param writeCacheService */ public void abortAllocationContext(final IAllocationContext context, RWWriteCacheService writeCacheService) { + if (m_pendingContextCommit) { + throw new IllegalStateException("Already pending commit"); + } + if (m_context != null) { // restore commit bits in AllocBlocks for (AllocBlock allocBlock : m_allocBlocks) { @@ -568,7 +575,7 @@ } private void checkFreeList() { - if (m_freeWaiting) { + if (m_freeWaiting && !m_pendingContextCommit) { if (m_freeBits > 0 && this instanceof DirectFixedAllocator) { m_freeWaiting = false; m_freeList.add(0, this); @@ -812,10 +819,12 @@ */ public boolean canImmediatelyFree(final int addr, final int size, final IAllocationContext context) { - if (context == m_context) { + if (context == m_context && !m_pendingContextCommit) { final int offset = ((-addr) & RWStore.OFFSET_BITS_MASK); // bit adjust - return !isCommitted(offset); + final boolean ret = !isCommitted(offset); + + return ret; } else { return false; } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWStore.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWStore.java 2011-04-19 20:28:57 UTC (rev 4421) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/rwstore/RWStore.java 2011-04-20 11:53:59 UTC (rev 4422) @@ -1755,7 +1755,7 @@ if (alloc.isAllocated(addrOffset)) throw new IllegalStateException("Reallocation problem with WriteCache"); - if (alloc.m_context != null && !m_commitList.contains(alloc)) { + if (!m_commitList.contains(alloc)) { m_commitList.add(alloc); } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/StressTestConcurrentUnisolatedIndices.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/StressTestConcurrentUnisolatedIndices.java 2011-04-19 20:28:57 UTC (rev 4421) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/journal/StressTestConcurrentUnisolatedIndices.java 2011-04-20 11:53:59 UTC (rev 4422) @@ -138,7 +138,7 @@ 20,// nresources 1, // minLocks 3, // maxLocks - 100,//100, // ntrials + 300,//100, // ntrials 3, // keyLen 1000, // nops .05//0.02d // failureRate Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java 2011-04-19 20:28:57 UTC (rev 4421) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/rwstore/TestRWJournal.java 2011-04-20 11:53:59 UTC (rev 4422) @@ -710,7 +710,7 @@ bufferStrategy = (RWStrategy) store.getBufferStrategy(); rw = bufferStrategy.getRWStore(); - reallocBatch(rw, 1000, 100, 10000); + reallocBatch(rw, 1000, 100, 1000); store.commit(); store.close(); @@ -718,7 +718,7 @@ bufferStrategy = (RWStrategy) store.getBufferStrategy(); rw = bufferStrategy.getRWStore(); - reallocBatch(rw, 1000, 100, 10000); + reallocBatch(rw, 1000, 100, 1000); store.commit(); store.close(); @@ -902,7 +902,7 @@ try { - final int tcount = 20000; // increase to ramp up stress levels + final int tcount = 1000; // increase to ramp up stress levels RWStrategy bufferStrategy = (RWStrategy) store.getBufferStrategy(); @@ -1518,7 +1518,7 @@ RWStore rw = bs.getRWStore(); long realAddr = 0; // allocBatch(store, 1, 32, 650, 100000000); - pureAllocBatch(store, 1, 32, rw.m_maxFixedAlloc - 4, 300000); // cover + pureAllocBatch(store, 1, 32, rw.m_maxFixedAlloc - 4, 30000); // cover // wider // range // of This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-05-01 20:43:21
|
Revision: 4436 http://bigdata.svn.sourceforge.net/bigdata/?rev=4436&view=rev Author: thompsonbry Date: 2011-05-01 20:43:15 +0000 (Sun, 01 May 2011) Log Message: ----------- Added method to print a bit string from a (signed or unsigned) byte[] and test suite for the same. Added method to take up to an int32 bit slice from an unsigned byte[] and test suite for the same. Added method to support indexing into a hash table based on an (at most) int32 bit slice. Note that a practical bit slices is 10 bits (4k page), etc. 32-bits is far more than we need for indexing into a hash table at a given level of the hash tree. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/bit-slice.xls Added: branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/bit-slice.xls =================================================================== (Binary files differ) Property changes on: branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/bit-slice.xls ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java 2011-04-29 22:11:44 UTC (rev 4435) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java 2011-05-01 20:43:15 UTC (rev 4436) @@ -968,8 +968,208 @@ return oldValue; } + + /** + * An array of 32-bit mask values. The index in the array is the #of bits of + * the hash code to be considered. The value at that index in the array is + * the mask to be applied to mask off to zero the high bits of the hash code + * which are to be ignored. + */ + static private final int[] masks32; + static { + + // Populate the array of masking values. + masks32 = new int[32]; + + for (int i = 0; i < 32; i++) + masks32[i] = getMSBMask(i); + + } /** + * Return a bit mask which reveals only the MSB (Most Significant Bits) N + * bits of an int32 value. + * + * @param nbits + * The #of bits to be revealed. + * + * @return The mask. + * + * @throws IllegalArgumentException + * if <i>nbits</i> is LT ZERO (0). + * @throws IllegalArgumentException + * if <i>nbits</i> is GT 32. + */ + static/* private */int getMSBMask(final int nbits) { + + if (nbits < 0 || nbits > 32) + throw new IllegalArgumentException(); + + final int limit = (32 - nbits); + int mask = 0; + + for (int i = 31; i >= limit; i--) { + + final int bit = (1 << i); + + mask |= bit; + + } + + return mask; + + } + + /** + * Mask off all but the MSB <i>nbits</i> of the hash value and shift them + * down such that the masked bits appear at bits (nbits:0] of the returned + * value. This is used to index into a dictionary page based on the revealed + * bits. + * + * @param h + * The hash value. + * @param nbits + * The #of bits already accounted for by the path from the root. + * + * @return The hash value considering only the MSB <i>nbits</i> and shifted + * down into an <i>nbits</i> integer. + */ + public static int maskOff(final int h, final int nbits) { + + if (nbits < 0 || nbits > 32) + throw new IllegalArgumentException(); + + final int v = h & masks32[nbits]; + + final int x = v >>> (32 - nbits); + + return x; + + } + + /** + * Return the n-bit integer corresponding to the inclusive bit range of the + * byte[]. Bit ZERO (0) is the Most Significant Bit (MSB). Bit positions + * increase from zero up to <code>a.length * 8 - 1</code>. The return value + * is an int32 and the bit range must not be greater than 32 bits. + * <p> + * For example, given the following data and the bit range (0,2) + * + * <pre> + * bit index: 01234567 + * ---------+---------- + * bit value: 10110000 + * </pre> + * + * TWO (2) bits starting at bit offset ZERO (0) would be extracted and + * returned as a 2-bit integer. For those data, the return value would be an + * int32 value whose binary representation was <code>10</code> (with leading + * zeros suppressed). + * <p> + * Note: This method is design for use with the unsigned byte[] keys in a + * bigdata hash index. All keys in bigdata are internally represented as + * unsigned byte[]s, which is why this method accepts a byte[] rather than + * an long[] for the bits. Also, while the length of an unsigned byte[] key + * can vary, they are never huge and an int32 value is sufficient to index + * into the bits in the byte[]. Finally, the return value is an int because + * it will be used in hash table designs to index into a hash table based on + * those bits in a hash code key which are masked as relevant to that hash + * table. 32bits is far more than we will need to index into a hash table. + * For an 8k page, we might expect a fan out of at most 1024 which is only + * 10 bits. + * + * @param a + * A byte[]. + * @param off + * The index of the first bit to be included. + * @param len + * The number of bits to be returned in [0:32]. However, a bit + * length of zero will always return zero. + * + * @return The integer extracted from the specified bit range. + */ + public static int getBits(final byte[] a, final int off, final int len) { + + if (a == null) + throw new IllegalArgumentException(); + if (off < 0) + throw new IllegalArgumentException(); + if (len < 0 || len > 32) + throw new IllegalArgumentException(); + if (len == 0) // zero length is always a zero. + return 0; + if (off + len > a.length * 8) + throw new IllegalArgumentException(); + + /* + * Build int32 value having the desired bits. + */ + + // byte in which the bit range begins. + final int fromByteOffset = byteIndexForBit(off); + + // byte in which the bit range ends (inclusive). + final int toByteOffset = byteIndexForBit(off + len - 1); + + /* + * The data are assembled into the int64 value by copying each byte in + * turn having data for the slice. This will copy at most 5 bytes. For + * example, when a 32-bit window starts in the middle of a byte. Once + * the bytes are assembled into the int64 buffer, they are shifted down + * to put the last bit extracted at bit index ZERO (0) of the int32 + * word. Finally, the unused high bits are cleared to zero using a mask. + */ + + long v = 0L; // buffer for up to 5 source bytes. + final int nbytes = toByteOffset - fromByteOffset + 1; + for (int i = fromByteOffset, j = 1; i <= toByteOffset; i++, j++) { + final byte x = a[i]; // next byte. + final int shift = ((nbytes - j) << 3); // + v |= (x << shift); // mask off high bits and shift into buf. + } // next byte in the byte[]. + final int last = off + len - 1; // index of the last bit (inclusive). + final int rshift = 7 - (last % 8); // final right shift to word align. + int w = (int) (v >>> rshift); // int32 result. + int mask = masks32[32 - len]; // lookup mask with [len] LSB ZEROs. + mask = ~mask; // flip bits to get [len] LSB ONEs. + w &= mask; // mask off the lower [len] bits (handles sign extension and + // starting offset within byte). + return w; + } + + /** + * Return the binary representation of the unsigned byte[]. + * + * @param a + * The unsigned byte[]. + * + * @return The representation of the bits in that unsigned byte[]. + * + * @throws IllegalArgumentException + * if the argument is <code>null</code>. + */ + public static String toBitString(final byte[] b) { + if (b == null)// Note: fromKey/toKey may be null; caller must check 1st + throw new IllegalArgumentException(); + final char[] chars = new char[b.length << 3]; // one char per bit. + int bitIndex = 0; // start at the msb. + for (int i = 0; i < b.length; i++) { + final byte x = b[i]; // next byte. + for (int withinByteIndex = 7; withinByteIndex >= 0; withinByteIndex--) { + final int mask = 1 << withinByteIndex; + final boolean bit = (x & mask) != 0; + chars[bitIndex++] = bits[bit ? 1 : 0]; + } // next bit in the current byte. + } // next byte in the byte[]. +// System.err.println("b[]=" + BytesUtil.toString(b) + ", chars=" +// + Arrays.toString(chars)); + return new String(chars); + } + + /** binary digits. */ + private final static char[] bits = { '0', '1' }; + + /** * Decode a string of the form <code>[0-9]+(k|kb|m|mb|g|gb)?</code>, * returning the number of bytes. When a suffix indicates kilobytes, * megabytes, or gigabytes then the returned value is scaled accordingly. @@ -1096,5 +1296,5 @@ return a; } - + } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java 2011-04-29 22:11:44 UTC (rev 4435) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java 2011-05-01 20:43:15 UTC (rev 4436) @@ -27,10 +27,13 @@ package com.bigdata.btree; +import java.util.Formatter; + import junit.framework.TestCase2; import com.bigdata.btree.keys.IKeyBuilder; import com.bigdata.btree.keys.KeyBuilder; +import com.bigdata.htree.HTreeUtil; /** * Test suite for low-level operations on variable length byte[]s. @@ -855,4 +858,465 @@ } + + /** + * Correct rejection tests for {@link HTreeUtil#getMSBMask(int)} + */ + public void test_getMaskBits_correctRejection() { + try { + BytesUtil.getMSBMask(-1); + fail("Expecting: "+IllegalArgumentException.class); + } catch(IllegalArgumentException ex) { + // ignore + } + try { + BytesUtil.getMSBMask(33); + fail("Expecting: "+IllegalArgumentException.class); + } catch(IllegalArgumentException ex) { + // ignore + } + } + + /** + * Unit test for {@link HTreeUtil#getMSBMask(int)} + */ + public void test_getMaskBits() { + + assertEquals(0x00000000, getMSBMask(0)); + assertEquals(0x80000000, getMSBMask(1)); + assertEquals(0xc0000000, getMSBMask(2)); + assertEquals(0xe0000000, getMSBMask(3)); + assertEquals(0xf0000000, getMSBMask(4)); + assertEquals(0xf8000000, getMSBMask(5)); + assertEquals(0xfc000000, getMSBMask(6)); + assertEquals(0xfe000000, getMSBMask(7)); + assertEquals(0xff000000, getMSBMask(8)); + assertEquals(0xff800000, getMSBMask(9)); + assertEquals(0xffc00000, getMSBMask(10)); + assertEquals(0xffe00000, getMSBMask(11)); + assertEquals(0xfff00000, getMSBMask(12)); + assertEquals(0xfff80000, getMSBMask(13)); + assertEquals(0xfffc0000, getMSBMask(14)); + assertEquals(0xfffe0000, getMSBMask(15)); + assertEquals(0xffff0000, getMSBMask(16)); + assertEquals(0xffff8000, getMSBMask(17)); + assertEquals(0xffffc000, getMSBMask(18)); + assertEquals(0xffffe000, getMSBMask(19)); + assertEquals(0xfffff000, getMSBMask(20)); + assertEquals(0xfffff800, getMSBMask(21)); + assertEquals(0xfffffc00, getMSBMask(22)); + assertEquals(0xfffffe00, getMSBMask(23)); + assertEquals(0xffffff00, getMSBMask(24)); + assertEquals(0xffffff80, getMSBMask(25)); + assertEquals(0xffffffc0, getMSBMask(26)); + assertEquals(0xffffffe0, getMSBMask(27)); + assertEquals(0xfffffff0, getMSBMask(28)); + assertEquals(0xfffffff8, getMSBMask(29)); + assertEquals(0xfffffffc, getMSBMask(30)); + assertEquals(0xfffffffe, getMSBMask(31)); + assertEquals(0xffffffff, getMSBMask(32)); + + } + + /** + * Test help logs the {@link HTreeUtil#getMSBMask(int)} results for + * inspection. + * + * @param nbits + * The #of bits whose MSB mask is desired. + * + * @return The MSB mask. + */ + private int getMSBMask(final int nbits) { + + final int mask = BytesUtil.getMSBMask(nbits); + + if(log.isInfoEnabled()) { + System.err.printf("%2d : [%32s]\n", nbits, Integer.toBinaryString(mask)); + } + + return mask; + + } + + /** + * Unit test for {@link BytesUtil#maskOff(int, int)} + */ + public void test_maskOff() { + + assertEquals(0x00000003, BytesUtil.maskOff(0xffffffff/* key */, 2/* nbits */)); + + assertEquals(0x00000003, BytesUtil.maskOff(0xc0000000/* key */, 2/* nbits */)); + + assertEquals(0x00000006, BytesUtil.maskOff(0xc0000000/* key */, 3/* nbits */)); + + assertEquals(0x0000000c, BytesUtil.maskOff(0xc0000000/* key */, 4/* nbits */)); + + assertEquals(0x00000018, BytesUtil.maskOff(0xc0000000/* key */, 5/* nbits */)); + + } + + /* + * toBitString() tests. + */ + + public void test_toBitString_correctRejection() { + + try { + BytesUtil.toBitString(null); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + public void test_toBitString_emptyByteArray() { + + // zero length byte[] => zero length bit string. + assertEquals("", BytesUtil.toBitString(new byte[0])); + + } + + public void test_toBitString_oneByte() { + + assertEquals("00000000", BytesUtil.toBitString(new byte[] { 0 })); + assertEquals("00000001", BytesUtil.toBitString(new byte[] { 1 << 0 })); + assertEquals("00000010", BytesUtil.toBitString(new byte[] { 1 << 1 })); + assertEquals("00000100", BytesUtil.toBitString(new byte[] { 1 << 2 })); + assertEquals("00001000", BytesUtil.toBitString(new byte[] { 1 << 3 })); + assertEquals("00010000", BytesUtil.toBitString(new byte[] { 1 << 4 })); + assertEquals("00100000", BytesUtil.toBitString(new byte[] { 1 << 5 })); + assertEquals("01000000", BytesUtil.toBitString(new byte[] { 1 << 6 })); + assertEquals("10000000", BytesUtil.toBitString(new byte[] { (byte) (1 << 7) })); + + } + + public void test_toBitString_twoBytes() { + + assertEquals("00000010" + "00100000", BytesUtil.toBitString(new byte[] { 1 << 1, + 1 << 5 })); + + } + + public void test_getBitsFromByteArray_correctRejection_nullArg() { + + try { + BytesUtil.getBits(null, 0, 0); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * offset may be zero, but not negative. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_01() { + + BytesUtil.getBits(new byte[1], 0, 0); // ok + try { + BytesUtil.getBits(new byte[1], -1, 0); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may be zero, but not negative. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_02() { + + BytesUtil.getBits(new byte[1], 0, 0); // ok + try { + BytesUtil.getBits(new byte[1], 0, -1); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may address all bits (8) in a single byte, but not the 9th bit. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_03() { + + BytesUtil.getBits(new byte[1], 0, 8); // ok + try { + BytesUtil.getBits(new byte[1], 0, 8 + 1); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may address all bits (32) in 4 bytes, but not 33 bits since the + * return value would be larger than an int32. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_04() { + + BytesUtil.getBits(new byte[4], 0, 32); // ok + try { + BytesUtil.getBits(new byte[4], 0, 33); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may address (32) bits in 5 bytes, but not 33 bits since the return + * value would be larger than an int32. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_05() { + + BytesUtil.getBits(new byte[5], 0, 32); // ok + try { + BytesUtil.getBits(new byte[5], 0, 33); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * You can request a zero length slice starting at bit zero of a zero length + * byte[]. + */ + public void test_getBitsFromByteArray_zeroLength() { + + assertEquals(0, BytesUtil.getBits(new byte[0], 0, 0)); + + } + + /** byte[4] (32-bits) with all bits zero. */ + public void test_getBitsFromByteArray_01() { + + final byte[] b = new byte[4]; + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 31/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 32/* len */)); + + } + + /** byte[4] (32-bits) with LSB ONE. */ + public void test_getBitsFromByteArray_02() { + + final byte[] b = new byte[4]; + + BytesUtil.setBit(b, 31/* off */, true); + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 31/* len */));//x + assertEquals(0x00000001, getBits(b, 0/* off */, 32/* len */)); + assertEquals(0x00000001, getBits(b, 31/* off */, 1/* len */));//x + assertEquals(0x00000001, getBits(b, 30/* off */, 2/* len */)); + + } + + /** + * byte[4] (32-bits) with bit ONE (1) set. + */ + public void test_getBitsFromByteArray_03() { + + final byte[] b = new byte[4]; + + BytesUtil.setBit(b, 1/* off */, true); + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 2/* len */)); + assertEquals(0x00000002, getBits(b, 1/* off */, 2/* len */)); + assertEquals(0x00000004, getBits(b, 1/* off */, 3/* len */)); + + assertEquals(0x00000001, getBits(b, 1/* off */, 1/* len */)); + + assertEquals(0x40000000, getBits(b, 0/* off */, 32/* len */)); + assertEquals(0x20000000, getBits(b, 0/* off */, 31/* len */)); + assertEquals(0x10000000, getBits(b, 0/* off */, 30/* len */)); + assertEquals(0x08000000, getBits(b, 0/* off */, 29/* len */)); + + } + + /** + * byte[4] (32-bits) with MSB ONE (this test case is the mostly likely to + * run a foul of a sign bit extension). + */ + public void test_getBitsFromByteArray_04() { + + final byte[] b = new byte[4]; + + BytesUtil.setBit(b, 0/* off */, true); + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000002, getBits(b, 0/* off */, 2/* len */)); + assertEquals(0x00000004, getBits(b, 0/* off */, 3/* len */)); + assertEquals(0x00000008, getBits(b, 0/* off */, 4/* len */)); + + assertEquals(0x00000000, getBits(b, 1/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 2/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 3/* len */)); + + } + + /** + * byte[4] (32-bits) with slice in the 2nd byte. + */ + public void test_getBitsFromByteArray_05() { + + final byte[] b = new byte[4]; + + // four bits on starting at bit 11. + BytesUtil.setBit(b, 11/* offset */, true); + BytesUtil.setBit(b, 12/* offset */, true); + BytesUtil.setBit(b, 13/* offset */, true); + BytesUtil.setBit(b, 14/* offset */, true); + + /* + * Test with a window extending from bit zero with a variety of bit + * lengths ranging from an end bit index one less than the first ONE bit + * through an end bit index beyond the last ONE bit. + */ + assertEquals(0x00000000, getBits(b, 0/* off */, 11/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 12/* len */)); + assertEquals(0x00000003, getBits(b, 0/* off */, 13/* len */)); + assertEquals(0x00000007, getBits(b, 0/* off */, 14/* len */)); + assertEquals(0x0000000f, getBits(b, 0/* off */, 15/* len */)); + assertEquals(0x0000001e, getBits(b, 0/* off */, 16/* len */)); + assertEquals(0x0000003c, getBits(b, 0/* off */, 17/* len */)); + assertEquals(0x00000078, getBits(b, 0/* off */, 18/* len */)); + assertEquals(0x000000f0, getBits(b, 0/* off */, 19/* len */)); + + /* + * Test with a 4-bit window that slides over the ONE bits. The initial + * window is to the left of the first ONE bit. The window slides right + * by one bit position for each assertion. + */ + assertEquals(0x00000000, getBits(b, 7/* off */, 4/* len */)); + assertEquals(0x00000001, getBits(b, 8/* off */, 4/* len */)); + assertEquals(0x00000003, getBits(b, 9/* off */, 4/* len */)); + assertEquals(0x00000007, getBits(b,10/* off */, 4/* len */)); + assertEquals(0x0000000f, getBits(b,11/* off */, 4/* len */)); + assertEquals(0x0000000e, getBits(b,12/* off */, 4/* len */)); + assertEquals(0x0000000c, getBits(b,13/* off */, 4/* len */)); + assertEquals(0x00000008, getBits(b,14/* off */, 4/* len */)); + assertEquals(0x00000000, getBits(b,15/* off */, 4/* len */)); + + } + + /** + * byte[2] (16-bits) + * + * @todo test slices from arrays with more than 4 bytes + */ + public void test_getBitsFromByteArray_06() { + + final byte[] b = new byte[2]; + + // four bits on starting at bit 11. + BytesUtil.setBit(b, 11/* offset */, true); + BytesUtil.setBit(b, 12/* offset */, true); + BytesUtil.setBit(b, 13/* offset */, true); + BytesUtil.setBit(b, 14/* offset */, true); + + /* + * Test with a window extending from bit zero with a variety of bit + * lengths ranging from an end bit index one less than the first ONE bit + * through an end bit index beyond the last ONE bit. + */ + assertEquals(0x00000000, getBits(b, 0/* off */, 11/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 12/* len */)); + assertEquals(0x00000003, getBits(b, 0/* off */, 13/* len */)); + assertEquals(0x00000007, getBits(b, 0/* off */, 14/* len */)); + assertEquals(0x0000000f, getBits(b, 0/* off */, 15/* len */)); + assertEquals(0x0000001e, getBits(b, 0/* off */, 16/* len */)); + + /* + * Now that we have reached the last legal length verify that length 17 + * is rejected. + */ + try { + getBits(b, 0/* off */, 17/* len */); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + /* + * Now increase the offset while decreasing the length and step through + * a few more slices. These all see the FOUR (4) ON bits plus one + * trailing ZERO (0) bit. + */ + assertEquals(0x0000001e, getBits(b, 1/* off */, 15/* len */)); + assertEquals(0x0000001e, getBits(b, 2/* off */, 14/* len */)); + assertEquals(0x0000001e, getBits(b, 3/* off */, 13/* len */)); + assertEquals(0x0000001e, getBits(b, 4/* off */, 12/* len */)); + assertEquals(0x0000001e, getBits(b, 5/* off */, 11/* len */)); + assertEquals(0x0000001e, getBits(b, 6/* off */, 10/* len */)); + assertEquals(0x0000001e, getBits(b, 7/* off */, 9/* len */)); + assertEquals(0x0000001e, getBits(b, 8/* off */, 8/* len */)); + assertEquals(0x0000001e, getBits(b, 9/* off */, 7/* len */)); + assertEquals(0x0000001e, getBits(b,10/* off */, 6/* len */)); + assertEquals(0x0000001e, getBits(b,11/* off */, 5/* len */)); + + /* + * Continue to increase the offset while decreasing the length, but now + * we will start to loose the ONE bits on both sides as the window keeps + * sliding and shrinking. + */ + assertEquals(0x0000000e, getBits(b,12/* off */, 4/* len */)); + assertEquals(0x00000006, getBits(b,13/* off */, 3/* len */)); + assertEquals(0x00000002, getBits(b,14/* off */, 2/* len */)); + assertEquals(0x00000000, getBits(b,15/* off */, 1/* len */)); + + /* + * This is also illegal (the starting offset is too large). + */ + try { + getBits(b,16/* off */, 1/* len */); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + private int getBits(final byte[] a,final int off,final int len) { + + final int ret = BytesUtil.getBits(a, off, len); + + if (log.isInfoEnabled()) { + final StringBuilder sb = new StringBuilder(); + final Formatter f = new Formatter(sb); + f.format("[%" + (a.length * 8) + "s] =(%2d:%2d)=> [%32s]", + BytesUtil.toBitString(a), off, len, Integer.toBinaryString(ret)); + log.info(sb.toString()); + } + + return ret; + + } + } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-05-07 15:44:34
|
Revision: 4462 http://bigdata.svn.sourceforge.net/bigdata/?rev=4462&view=rev Author: thompsonbry Date: 2011-05-07 15:44:27 +0000 (Sat, 07 May 2011) Log Message: ----------- Added a getBits(int32,off,len) method and test suite. This is intended for bit slices in a hash tree using native int32 keys rather than unsigned byte[] keys. It seems worth while to support this optimization for the hash tree since most of the applications of the hash tree will have int32 keys and it appears that the optimization can be achieved relatively easily within the implementation. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestAll.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestGetBitsFromByteArray.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestGetBitsFromInt32.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java 2011-05-06 15:55:01 UTC (rev 4461) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java 2011-05-07 15:44:27 UTC (rev 4462) @@ -1138,6 +1138,58 @@ } /** + * Return the n-bit integer corresponding to the inclusive bit range of the + * byte[]. Bit ZERO (0) is the Most Significant Bit (MSB). Bit positions + * increase from zero up to <code>31</code>. The return value is an int32 + * and the bit range must not be greater than 32 bits. + * <p> + * For example, given the following data and the bit range (0,2) + * + * <pre> + * bit index: 01234567 + * ---------+---------- + * bit value: 10110000 + * </pre> + * + * TWO (2) bits starting at bit offset ZERO (0) would be extracted and + * returned as a 2-bit integer. For those data, the return value would be an + * int32 value whose binary representation was <code>10</code> (with leading + * zeros suppressed). + * <p> + * Note: This method is design for use in a bigdata hash index having native + * int32 keys rather than unsigned byte[] keys. + * + * @param a + * An integer. + * @param off + * The index of the first bit to be included. + * @param len + * The number of bits to be returned in [0:32]. However, a bit + * length of zero will always return zero. + * + * @return The integer extracted from the specified bit range. + */ + public static int getBits(final int a, final int off, final int len) { + if (off < 0) + throw new IllegalArgumentException(); + if (len < 0 || len > 32) + throw new IllegalArgumentException(); + if (len == 0) // zero length is always a zero. + return 0; + if (off + len > 32) + throw new IllegalArgumentException(); + + final int last = off + len - 1; // index of the last bit (inclusive). + final int rshift = 31 - last; // right shift to word align. + int w = (int) (a >>> rshift); // int32 result. + int mask = masks32[32 - len]; // lookup mask with [len] LSB ZEROs. + mask = ~mask; // flip bits to get [len] LSB ONEs. + w &= mask; // mask off the lower [len] bits (handles sign extension and + // starting offset within byte). + return w; + } + + /** * Return the binary representation of the unsigned byte[]. * * @param a Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestAll.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestAll.java 2011-05-06 15:55:01 UTC (rev 4461) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestAll.java 2011-05-07 15:44:27 UTC (rev 4462) @@ -60,6 +60,8 @@ // test low level variable length byte[] operations. suite.addTestSuite(TestBytesUtil.class); + suite.addTestSuite(TestGetBitsFromByteArray.class); + suite.addTestSuite(TestGetBitsFromInt32.class); // unsigned byte[] key encoding and decoding. suite.addTest(com.bigdata.btree.keys.TestAll.suite()); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java 2011-05-06 15:55:01 UTC (rev 4461) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java 2011-05-07 15:44:27 UTC (rev 4462) @@ -27,8 +27,6 @@ package com.bigdata.btree; -import java.util.Formatter; - import junit.framework.TestCase2; import com.bigdata.btree.keys.IKeyBuilder; @@ -999,323 +997,4 @@ } - public void test_getBitsFromByteArray_correctRejection_nullArg() { - - try { - BytesUtil.getBits(null, 0, 0); - fail("Expecting: " + IllegalArgumentException.class); - } catch (IllegalArgumentException ex) { - if (log.isInfoEnabled()) - log.info("Ignoring expected exception: " + ex); - } - - } - - /** - * offset may be zero, but not negative. - */ - public void test_getBitsFromByteArray_correctRejection_off_and_len_01() { - - BytesUtil.getBits(new byte[1], 0, 0); // ok - try { - BytesUtil.getBits(new byte[1], -1, 0); // no good - fail("Expecting: " + IllegalArgumentException.class); - } catch (IllegalArgumentException ex) { - if (log.isInfoEnabled()) - log.info("Ignoring expected exception: " + ex); - } - - } - - /** - * length may be zero, but not negative. - */ - public void test_getBitsFromByteArray_correctRejection_off_and_len_02() { - - BytesUtil.getBits(new byte[1], 0, 0); // ok - try { - BytesUtil.getBits(new byte[1], 0, -1); // no good - fail("Expecting: " + IllegalArgumentException.class); - } catch (IllegalArgumentException ex) { - if (log.isInfoEnabled()) - log.info("Ignoring expected exception: " + ex); - } - - } - - /** - * length may address all bits (8) in a single byte, but not the 9th bit. - */ - public void test_getBitsFromByteArray_correctRejection_off_and_len_03() { - - BytesUtil.getBits(new byte[1], 0, 8); // ok - try { - BytesUtil.getBits(new byte[1], 0, 8 + 1); // no good - fail("Expecting: " + IllegalArgumentException.class); - } catch (IllegalArgumentException ex) { - if (log.isInfoEnabled()) - log.info("Ignoring expected exception: " + ex); - } - - } - - /** - * length may address all bits (32) in 4 bytes, but not 33 bits since the - * return value would be larger than an int32. - */ - public void test_getBitsFromByteArray_correctRejection_off_and_len_04() { - - BytesUtil.getBits(new byte[4], 0, 32); // ok - try { - BytesUtil.getBits(new byte[4], 0, 33); // no good - fail("Expecting: " + IllegalArgumentException.class); - } catch (IllegalArgumentException ex) { - if (log.isInfoEnabled()) - log.info("Ignoring expected exception: " + ex); - } - - } - - /** - * length may address (32) bits in 5 bytes, but not 33 bits since the return - * value would be larger than an int32. - */ - public void test_getBitsFromByteArray_correctRejection_off_and_len_05() { - - BytesUtil.getBits(new byte[5], 0, 32); // ok - try { - BytesUtil.getBits(new byte[5], 0, 33); // no good - fail("Expecting: " + IllegalArgumentException.class); - } catch (IllegalArgumentException ex) { - if (log.isInfoEnabled()) - log.info("Ignoring expected exception: " + ex); - } - - } - - /** - * You can request a zero length slice starting at bit zero of a zero length - * byte[]. - */ - public void test_getBitsFromByteArray_zeroLength() { - - assertEquals(0, BytesUtil.getBits(new byte[0], 0, 0)); - - } - - /** byte[4] (32-bits) with all bits zero. */ - public void test_getBitsFromByteArray_01() { - - final byte[] b = new byte[4]; - - assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); - assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); - assertEquals(0x00000000, getBits(b, 0/* off */, 31/* len */)); - assertEquals(0x00000000, getBits(b, 0/* off */, 32/* len */)); - - } - - /** byte[4] (32-bits) with LSB ONE. */ - public void test_getBitsFromByteArray_02() { - - final byte[] b = new byte[4]; - - BytesUtil.setBit(b, 31/* off */, true); - - assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); - assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); - assertEquals(0x00000000, getBits(b, 0/* off */, 31/* len */));//x - assertEquals(0x00000001, getBits(b, 0/* off */, 32/* len */)); - assertEquals(0x00000001, getBits(b, 31/* off */, 1/* len */));//x - assertEquals(0x00000001, getBits(b, 30/* off */, 2/* len */)); - - } - - /** - * byte[4] (32-bits) with bit ONE (1) set. - */ - public void test_getBitsFromByteArray_03() { - - final byte[] b = new byte[4]; - - BytesUtil.setBit(b, 1/* off */, true); - - assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); - assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); - assertEquals(0x00000001, getBits(b, 0/* off */, 2/* len */)); - assertEquals(0x00000002, getBits(b, 1/* off */, 2/* len */)); - assertEquals(0x00000004, getBits(b, 1/* off */, 3/* len */)); - - assertEquals(0x00000001, getBits(b, 1/* off */, 1/* len */)); - - assertEquals(0x40000000, getBits(b, 0/* off */, 32/* len */)); - assertEquals(0x20000000, getBits(b, 0/* off */, 31/* len */)); - assertEquals(0x10000000, getBits(b, 0/* off */, 30/* len */)); - assertEquals(0x08000000, getBits(b, 0/* off */, 29/* len */)); - - } - - /** - * byte[4] (32-bits) with MSB ONE (this test case is the mostly likely to - * run a foul of a sign bit extension). - */ - public void test_getBitsFromByteArray_04() { - - final byte[] b = new byte[4]; - - BytesUtil.setBit(b, 0/* off */, true); - - assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); - assertEquals(0x00000001, getBits(b, 0/* off */, 1/* len */)); - assertEquals(0x00000002, getBits(b, 0/* off */, 2/* len */)); - assertEquals(0x00000004, getBits(b, 0/* off */, 3/* len */)); - assertEquals(0x00000008, getBits(b, 0/* off */, 4/* len */)); - - assertEquals(0x00000000, getBits(b, 1/* off */, 0/* len */)); - assertEquals(0x00000000, getBits(b, 1/* off */, 1/* len */)); - assertEquals(0x00000000, getBits(b, 1/* off */, 2/* len */)); - assertEquals(0x00000000, getBits(b, 1/* off */, 3/* len */)); - - } - - /** - * byte[4] (32-bits) with slice in the 2nd byte. - */ - public void test_getBitsFromByteArray_05() { - - final byte[] b = new byte[4]; - - // four bits on starting at bit 11. - BytesUtil.setBit(b, 11/* offset */, true); - BytesUtil.setBit(b, 12/* offset */, true); - BytesUtil.setBit(b, 13/* offset */, true); - BytesUtil.setBit(b, 14/* offset */, true); - - /* - * Test with a window extending from bit zero with a variety of bit - * lengths ranging from an end bit index one less than the first ONE bit - * through an end bit index beyond the last ONE bit. - */ - assertEquals(0x00000000, getBits(b, 0/* off */, 11/* len */)); - assertEquals(0x00000001, getBits(b, 0/* off */, 12/* len */)); - assertEquals(0x00000003, getBits(b, 0/* off */, 13/* len */)); - assertEquals(0x00000007, getBits(b, 0/* off */, 14/* len */)); - assertEquals(0x0000000f, getBits(b, 0/* off */, 15/* len */)); - assertEquals(0x0000001e, getBits(b, 0/* off */, 16/* len */)); - assertEquals(0x0000003c, getBits(b, 0/* off */, 17/* len */)); - assertEquals(0x00000078, getBits(b, 0/* off */, 18/* len */)); - assertEquals(0x000000f0, getBits(b, 0/* off */, 19/* len */)); - - /* - * Test with a 4-bit window that slides over the ONE bits. The initial - * window is to the left of the first ONE bit. The window slides right - * by one bit position for each assertion. - */ - assertEquals(0x00000000, getBits(b, 7/* off */, 4/* len */)); - assertEquals(0x00000001, getBits(b, 8/* off */, 4/* len */)); - assertEquals(0x00000003, getBits(b, 9/* off */, 4/* len */)); - assertEquals(0x00000007, getBits(b,10/* off */, 4/* len */)); - assertEquals(0x0000000f, getBits(b,11/* off */, 4/* len */)); - assertEquals(0x0000000e, getBits(b,12/* off */, 4/* len */)); - assertEquals(0x0000000c, getBits(b,13/* off */, 4/* len */)); - assertEquals(0x00000008, getBits(b,14/* off */, 4/* len */)); - assertEquals(0x00000000, getBits(b,15/* off */, 4/* len */)); - - } - - /** - * byte[2] (16-bits) - * - * @todo test slices from arrays with more than 4 bytes - */ - public void test_getBitsFromByteArray_06() { - - final byte[] b = new byte[2]; - - // four bits on starting at bit 11. - BytesUtil.setBit(b, 11/* offset */, true); - BytesUtil.setBit(b, 12/* offset */, true); - BytesUtil.setBit(b, 13/* offset */, true); - BytesUtil.setBit(b, 14/* offset */, true); - - /* - * Test with a window extending from bit zero with a variety of bit - * lengths ranging from an end bit index one less than the first ONE bit - * through an end bit index beyond the last ONE bit. - */ - assertEquals(0x00000000, getBits(b, 0/* off */, 11/* len */)); - assertEquals(0x00000001, getBits(b, 0/* off */, 12/* len */)); - assertEquals(0x00000003, getBits(b, 0/* off */, 13/* len */)); - assertEquals(0x00000007, getBits(b, 0/* off */, 14/* len */)); - assertEquals(0x0000000f, getBits(b, 0/* off */, 15/* len */)); - assertEquals(0x0000001e, getBits(b, 0/* off */, 16/* len */)); - - /* - * Now that we have reached the last legal length verify that length 17 - * is rejected. - */ - try { - getBits(b, 0/* off */, 17/* len */); - fail("Expecting: " + IllegalArgumentException.class); - } catch (IllegalArgumentException ex) { - if (log.isInfoEnabled()) - log.info("Ignoring expected exception: " + ex); - } - - /* - * Now increase the offset while decreasing the length and step through - * a few more slices. These all see the FOUR (4) ON bits plus one - * trailing ZERO (0) bit. - */ - assertEquals(0x0000001e, getBits(b, 1/* off */, 15/* len */)); - assertEquals(0x0000001e, getBits(b, 2/* off */, 14/* len */)); - assertEquals(0x0000001e, getBits(b, 3/* off */, 13/* len */)); - assertEquals(0x0000001e, getBits(b, 4/* off */, 12/* len */)); - assertEquals(0x0000001e, getBits(b, 5/* off */, 11/* len */)); - assertEquals(0x0000001e, getBits(b, 6/* off */, 10/* len */)); - assertEquals(0x0000001e, getBits(b, 7/* off */, 9/* len */)); - assertEquals(0x0000001e, getBits(b, 8/* off */, 8/* len */)); - assertEquals(0x0000001e, getBits(b, 9/* off */, 7/* len */)); - assertEquals(0x0000001e, getBits(b,10/* off */, 6/* len */)); - assertEquals(0x0000001e, getBits(b,11/* off */, 5/* len */)); - - /* - * Continue to increase the offset while decreasing the length, but now - * we will start to loose the ONE bits on both sides as the window keeps - * sliding and shrinking. - */ - assertEquals(0x0000000e, getBits(b,12/* off */, 4/* len */)); - assertEquals(0x00000006, getBits(b,13/* off */, 3/* len */)); - assertEquals(0x00000002, getBits(b,14/* off */, 2/* len */)); - assertEquals(0x00000000, getBits(b,15/* off */, 1/* len */)); - - /* - * This is also illegal (the starting offset is too large). - */ - try { - getBits(b,16/* off */, 1/* len */); - fail("Expecting: " + IllegalArgumentException.class); - } catch (IllegalArgumentException ex) { - if (log.isInfoEnabled()) - log.info("Ignoring expected exception: " + ex); - } - - } - - private int getBits(final byte[] a,final int off,final int len) { - - final int ret = BytesUtil.getBits(a, off, len); - - if (log.isInfoEnabled()) { - final StringBuilder sb = new StringBuilder(); - final Formatter f = new Formatter(sb); - f.format("[%" + (a.length * 8) + "s] =(%2d:%2d)=> [%32s]", - BytesUtil.toBitString(a), off, len, Integer.toBinaryString(ret)); - log.info(sb.toString()); - } - - return ret; - - } - } Added: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestGetBitsFromByteArray.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestGetBitsFromByteArray.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestGetBitsFromByteArray.java 2011-05-07 15:44:27 UTC (rev 4462) @@ -0,0 +1,367 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2007. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on May 7, 2011 + */ +package com.bigdata.btree; + +import java.util.Formatter; + +import junit.framework.TestCase2; + +/** + * Unit tests for {@link BytesUtil#getBits(byte[], int, int)} + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id$ + */ +public class TestGetBitsFromByteArray extends TestCase2 { + + public TestGetBitsFromByteArray() { + } + + public TestGetBitsFromByteArray(String name) { + super(name); + } + + public void test_getBitsFromByteArray_correctRejection_nullArg() { + + try { + BytesUtil.getBits(null, 0, 0); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * offset may be zero, but not negative. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_01() { + + BytesUtil.getBits(new byte[1], 0, 0); // ok + try { + BytesUtil.getBits(new byte[1], -1, 0); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may be zero, but not negative. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_02() { + + BytesUtil.getBits(new byte[1], 0, 0); // ok + try { + BytesUtil.getBits(new byte[1], 0, -1); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may address all bits (8) in a single byte, but not the 9th bit. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_03() { + + BytesUtil.getBits(new byte[1], 0, 8); // ok + try { + BytesUtil.getBits(new byte[1], 0, 8 + 1); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may address all bits (32) in 4 bytes, but not 33 bits since the + * return value would be larger than an int32. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_04() { + + BytesUtil.getBits(new byte[4], 0, 32); // ok + try { + BytesUtil.getBits(new byte[4], 0, 33); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may address (32) bits in 5 bytes, but not 33 bits since the return + * value would be larger than an int32. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_05() { + + BytesUtil.getBits(new byte[5], 0, 32); // ok + try { + BytesUtil.getBits(new byte[5], 0, 33); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * You can request a zero length slice starting at bit zero of a zero length + * byte[]. + */ + public void test_getBitsFromByteArray_zeroLength() { + + assertEquals(0, BytesUtil.getBits(new byte[0], 0, 0)); + + } + + /** byte[4] (32-bits) with all bits zero. */ + public void test_getBitsFromByteArray_01() { + + final byte[] b = new byte[4]; + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 31/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 32/* len */)); + + } + + /** byte[4] (32-bits) with LSB ONE. */ + public void test_getBitsFromByteArray_02() { + + final byte[] b = new byte[4]; + + BytesUtil.setBit(b, 31/* off */, true); + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 31/* len */));//x + assertEquals(0x00000001, getBits(b, 0/* off */, 32/* len */)); + assertEquals(0x00000001, getBits(b, 31/* off */, 1/* len */));//x + assertEquals(0x00000001, getBits(b, 30/* off */, 2/* len */)); + + } + + /** + * byte[4] (32-bits) with bit ONE (1) set. + */ + public void test_getBitsFromByteArray_03() { + + final byte[] b = new byte[4]; + + BytesUtil.setBit(b, 1/* off */, true); + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 2/* len */)); + assertEquals(0x00000002, getBits(b, 1/* off */, 2/* len */)); + assertEquals(0x00000004, getBits(b, 1/* off */, 3/* len */)); + + assertEquals(0x00000001, getBits(b, 1/* off */, 1/* len */)); + + assertEquals(0x40000000, getBits(b, 0/* off */, 32/* len */)); + assertEquals(0x20000000, getBits(b, 0/* off */, 31/* len */)); + assertEquals(0x10000000, getBits(b, 0/* off */, 30/* len */)); + assertEquals(0x08000000, getBits(b, 0/* off */, 29/* len */)); + + } + + /** + * byte[4] (32-bits) with MSB ONE (this test case is the mostly likely to + * run a foul of a sign bit extension). + */ + public void test_getBitsFromByteArray_04() { + + final byte[] b = new byte[4]; + + BytesUtil.setBit(b, 0/* off */, true); + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000002, getBits(b, 0/* off */, 2/* len */)); + assertEquals(0x00000004, getBits(b, 0/* off */, 3/* len */)); + assertEquals(0x00000008, getBits(b, 0/* off */, 4/* len */)); + + assertEquals(0x00000000, getBits(b, 1/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 2/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 3/* len */)); + + } + + /** + * byte[4] (32-bits) with slice in the 2nd byte. + */ + public void test_getBitsFromByteArray_05() { + + final byte[] b = new byte[4]; + + // four bits on starting at bit 11. + BytesUtil.setBit(b, 11/* offset */, true); + BytesUtil.setBit(b, 12/* offset */, true); + BytesUtil.setBit(b, 13/* offset */, true); + BytesUtil.setBit(b, 14/* offset */, true); + + /* + * Test with a window extending from bit zero with a variety of bit + * lengths ranging from an end bit index one less than the first ONE bit + * through an end bit index beyond the last ONE bit. + */ + assertEquals(0x00000000, getBits(b, 0/* off */, 11/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 12/* len */)); + assertEquals(0x00000003, getBits(b, 0/* off */, 13/* len */)); + assertEquals(0x00000007, getBits(b, 0/* off */, 14/* len */)); + assertEquals(0x0000000f, getBits(b, 0/* off */, 15/* len */)); + assertEquals(0x0000001e, getBits(b, 0/* off */, 16/* len */)); + assertEquals(0x0000003c, getBits(b, 0/* off */, 17/* len */)); + assertEquals(0x00000078, getBits(b, 0/* off */, 18/* len */)); + assertEquals(0x000000f0, getBits(b, 0/* off */, 19/* len */)); + + /* + * Test with a 4-bit window that slides over the ONE bits. The initial + * window is to the left of the first ONE bit. The window slides right + * by one bit position for each assertion. + */ + assertEquals(0x00000000, getBits(b, 7/* off */, 4/* len */)); + assertEquals(0x00000001, getBits(b, 8/* off */, 4/* len */)); + assertEquals(0x00000003, getBits(b, 9/* off */, 4/* len */)); + assertEquals(0x00000007, getBits(b,10/* off */, 4/* len */)); + assertEquals(0x0000000f, getBits(b,11/* off */, 4/* len */)); + assertEquals(0x0000000e, getBits(b,12/* off */, 4/* len */)); + assertEquals(0x0000000c, getBits(b,13/* off */, 4/* len */)); + assertEquals(0x00000008, getBits(b,14/* off */, 4/* len */)); + assertEquals(0x00000000, getBits(b,15/* off */, 4/* len */)); + + } + + /** + * byte[2] (16-bits) + * + * @todo test slices from arrays with more than 4 bytes + */ + public void test_getBitsFromByteArray_06() { + + final byte[] b = new byte[2]; + + // four bits on starting at bit 11. + BytesUtil.setBit(b, 11/* offset */, true); + BytesUtil.setBit(b, 12/* offset */, true); + BytesUtil.setBit(b, 13/* offset */, true); + BytesUtil.setBit(b, 14/* offset */, true); + + /* + * Test with a window extending from bit zero with a variety of bit + * lengths ranging from an end bit index one less than the first ONE bit + * through an end bit index beyond the last ONE bit. + */ + assertEquals(0x00000000, getBits(b, 0/* off */, 11/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 12/* len */)); + assertEquals(0x00000003, getBits(b, 0/* off */, 13/* len */)); + assertEquals(0x00000007, getBits(b, 0/* off */, 14/* len */)); + assertEquals(0x0000000f, getBits(b, 0/* off */, 15/* len */)); + assertEquals(0x0000001e, getBits(b, 0/* off */, 16/* len */)); + + /* + * Now that we have reached the last legal length verify that length 17 + * is rejected. + */ + try { + getBits(b, 0/* off */, 17/* len */); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + /* + * Now increase the offset while decreasing the length and step through + * a few more slices. These all see the FOUR (4) ON bits plus one + * trailing ZERO (0) bit. + */ + assertEquals(0x0000001e, getBits(b, 1/* off */, 15/* len */)); + assertEquals(0x0000001e, getBits(b, 2/* off */, 14/* len */)); + assertEquals(0x0000001e, getBits(b, 3/* off */, 13/* len */)); + assertEquals(0x0000001e, getBits(b, 4/* off */, 12/* len */)); + assertEquals(0x0000001e, getBits(b, 5/* off */, 11/* len */)); + assertEquals(0x0000001e, getBits(b, 6/* off */, 10/* len */)); + assertEquals(0x0000001e, getBits(b, 7/* off */, 9/* len */)); + assertEquals(0x0000001e, getBits(b, 8/* off */, 8/* len */)); + assertEquals(0x0000001e, getBits(b, 9/* off */, 7/* len */)); + assertEquals(0x0000001e, getBits(b,10/* off */, 6/* len */)); + assertEquals(0x0000001e, getBits(b,11/* off */, 5/* len */)); + + /* + * Continue to increase the offset while decreasing the length, but now + * we will start to loose the ONE bits on both sides as the window keeps + * sliding and shrinking. + */ + assertEquals(0x0000000e, getBits(b,12/* off */, 4/* len */)); + assertEquals(0x00000006, getBits(b,13/* off */, 3/* len */)); + assertEquals(0x00000002, getBits(b,14/* off */, 2/* len */)); + assertEquals(0x00000000, getBits(b,15/* off */, 1/* len */)); + + /* + * This is also illegal (the starting offset is too large). + */ + try { + getBits(b,16/* off */, 1/* len */); + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + private int getBits(final byte[] a,final int off,final int len) { + + final int ret = BytesUtil.getBits(a, off, len); + + if (log.isInfoEnabled()) { + final StringBuilder sb = new StringBuilder(); + final Formatter f = new Formatter(sb); + f.format("[%" + (a.length * 8) + "s] =(%2d:%2d)=> [%32s]", + BytesUtil.toBitString(a), off, len, Integer.toBinaryString(ret)); + log.info(sb.toString()); + } + + return ret; + + } + +} Added: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestGetBitsFromInt32.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestGetBitsFromInt32.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestGetBitsFromInt32.java 2011-05-07 15:44:27 UTC (rev 4462) @@ -0,0 +1,241 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2007. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on May 7, 2011 + */ +package com.bigdata.btree; + +import java.util.Formatter; + +import junit.framework.TestCase2; + +/** + * Unit tests for {@link BytesUtil#getBits(int, int, int) + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id$ + */ +public class TestGetBitsFromInt32 extends TestCase2 { + + /** + * + */ + public TestGetBitsFromInt32() { + } + + /** + * @param name + */ + public TestGetBitsFromInt32(String name) { + super(name); + } + + /** + * offset may be zero, but not negative. + */ + public void test_getBitsFromInt32_correctRejection_off_and_len_01() { + + BytesUtil.getBits(0x0, 0, 0); // ok + try { + BytesUtil.getBits(0x0, -1, 0); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may be zero, but not negative. + */ + public void test_getBitsFromInt32_correctRejection_off_and_len_02() { + + BytesUtil.getBits(0x0, 0, 0); // ok + try { + BytesUtil.getBits(0x0, 0, -1); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * length may address all bits (32), but not 33 bits. + */ + public void test_getBitsFromByteArray_correctRejection_off_and_len_04() { + + BytesUtil.getBits(0x0, 0, 32); // ok + try { + BytesUtil.getBits(0x0, 0, 33); // no good + fail("Expecting: " + IllegalArgumentException.class); + } catch (IllegalArgumentException ex) { + if (log.isInfoEnabled()) + log.info("Ignoring expected exception: " + ex); + } + + } + + /** + * You can request a zero length slice starting at bit zero. + */ + public void test_getBitsFromInt32_zeroLength() { + + assertEquals(0, BytesUtil.getBits(0x0, 0, 0)); + + } + + /** all bits zero. */ + public void test_getBitsFromInt32_01() { + + final int b = 0x0; + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 31/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 32/* len */)); + + } + + /** LSB ONE. */ + public void test_getBitsFromInt32_02() { + + final int b = 1; + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 31/* len */));//x + assertEquals(0x00000001, getBits(b, 0/* off */, 32/* len */)); + assertEquals(0x00000001, getBits(b, 31/* off */, 1/* len */));//x + assertEquals(0x00000001, getBits(b, 30/* off */, 2/* len */)); + + } + + /** + * Bit ONE (1) set (remember, MSB is bit ZERO (0)). + */ + public void test_getBitsFromByteArray_03() { + + final int b = 1<<30; + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 2/* len */)); + assertEquals(0x00000002, getBits(b, 1/* off */, 2/* len */)); + assertEquals(0x00000004, getBits(b, 1/* off */, 3/* len */)); + + assertEquals(0x00000001, getBits(b, 1/* off */, 1/* len */)); + + assertEquals(0x40000000, getBits(b, 0/* off */, 32/* len */)); + assertEquals(0x20000000, getBits(b, 0/* off */, 31/* len */)); + assertEquals(0x10000000, getBits(b, 0/* off */, 30/* len */)); + assertEquals(0x08000000, getBits(b, 0/* off */, 29/* len */)); + + } + + /** + * MSB ONE (this test case is the mostly likely to run a foul of a sign bit + * extension). + */ + public void test_getBitsFromByteArray_04() { + + final int b = 1<<31; + + assertEquals(0x00000000, getBits(b, 0/* off */, 0/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 1/* len */)); + assertEquals(0x00000002, getBits(b, 0/* off */, 2/* len */)); + assertEquals(0x00000004, getBits(b, 0/* off */, 3/* len */)); + assertEquals(0x00000008, getBits(b, 0/* off */, 4/* len */)); + + assertEquals(0x00000000, getBits(b, 1/* off */, 0/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 1/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 2/* len */)); + assertEquals(0x00000000, getBits(b, 1/* off */, 3/* len */)); + + } + + /** + * Slice in the 2nd byte. + */ + public void test_getBitsFromByteArray_05() { + + /* + * Four bits on starting at bit 11 (where MSB is bit zero). + * + * Note: For normal Java bit indexing with MSB 31, this is bit indices + * 20,19,18,17. + */ + final int b = (1 << 20) | (1 << 19) | (1 << 18) | (1 << 17); + + /* + * Test with a window extending from bit zero with a variety of bit + * lengths ranging from an end bit index one less than the first ONE bit + * through an end bit index beyond the last ONE bit. + */ + assertEquals(0x00000000, getBits(b, 0/* off */, 11/* len */)); + assertEquals(0x00000001, getBits(b, 0/* off */, 12/* len */)); + assertEquals(0x00000003, getBits(b, 0/* off */, 13/* len */)); + assertEquals(0x00000007, getBits(b, 0/* off */, 14/* len */)); + assertEquals(0x0000000f, getBits(b, 0/* off */, 15/* len */)); + assertEquals(0x0000001e, getBits(b, 0/* off */, 16/* len */)); + assertEquals(0x0000003c, getBits(b, 0/* off */, 17/* len */)); + assertEquals(0x00000078, getBits(b, 0/* off */, 18/* len */)); + assertEquals(0x000000f0, getBits(b, 0/* off */, 19/* len */)); + + /* + * Test with a 4-bit window that slides over the ONE bits. The initial + * window is to the left of the first ONE bit. The window slides right + * by one bit position for each assertion. + */ + assertEquals(0x00000000, getBits(b, 7/* off */, 4/* len */)); + assertEquals(0x00000001, getBits(b, 8/* off */, 4/* len */)); + assertEquals(0x00000003, getBits(b, 9/* off */, 4/* len */)); + assertEquals(0x00000007, getBits(b,10/* off */, 4/* len */)); + assertEquals(0x0000000f, getBits(b,11/* off */, 4/* len */)); + assertEquals(0x0000000e, getBits(b,12/* off */, 4/* len */)); + assertEquals(0x0000000c, getBits(b,13/* off */, 4/* len */)); + assertEquals(0x00000008, getBits(b,14/* off */, 4/* len */)); + assertEquals(0x00000000, getBits(b,15/* off */, 4/* len */)); + + } + + private int getBits(final int a, final int off, final int len) { + + final int ret = BytesUtil.getBits(a, off, len); + + if (log.isInfoEnabled()) { + final StringBuilder sb = new StringBuilder(); + final Formatter f = new Formatter(sb); + f.format("[%32s] =(%2d:%2d)=> [%32s]", Integer.toBinaryString(a), + off, len, Integer.toBinaryString(ret)); + log.info(sb.toString()); + } + + return ret; + + } + +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-05-08 14:15:59
|
Revision: 4463 http://bigdata.svn.sourceforge.net/bigdata/?rev=4463&view=rev Author: thompsonbry Date: 2011-05-08 14:15:50 +0000 (Sun, 08 May 2011) Log Message: ----------- Continued work on the htree. At this point I believe that I have all of the relevant bit math working with appropriate unit tests (except for maskOffLSB). This commit includes a refactoring of the internal interfaces for the B+Tree to support some of the differences between the BTree and the HTree. The next steps on the HTree are to write the recursive descent and tuple management logic, followed by handling splits in the buddy buckets and buddy hash tables and the gradual deepening of the hash tree. Some of the mechanisms for persistence have been incorporated into the hash tree, but there needs to be a reconcile between the HTree and the BTree at some point so they can share more code (abstract classes) and interfaces for generalized persistent indices. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/IndexSegmentBuilder.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/UnisolatedReadWriteIndex.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IAbstractNodeData.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/ILeafData.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/INodeData.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HashBucket.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HashTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/data/IBucketData.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/data/IDirectoryData.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/AbstractBTreeTestCase.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/btree/TestBytesUtil.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestAll.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestExtensibleHashing.java Added Paths: ----------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IChildData.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IKeysData.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/ISpannedTupleCountData.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/AbstractHTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTreeUtil.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/MutableDirectoryPageData.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTreeUtil.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTree.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTree.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -867,21 +867,21 @@ return counterSet; } - - /** - * @param store - * The persistence store. - * @param nodeFactory - * Object that provides a factory for node and leaf objects. - * @param addrSer - * Object that knows how to (de-)serialize the child addresses in - * an {@link INodeData}. - * @param readOnly - * <code>true</code> IFF it is <em>known</em> that the - * {@link AbstractBTree} is read-only. - * @param metadata - * The {@link IndexMetadata} object for this B+Tree. - */ + + /** + * @param store + * The persistence store. + * @param nodeFactory + * Object that provides a factory for node and leaf objects. + * @param readOnly + * <code>true</code> IFF it is <em>known</em> that the + * {@link AbstractBTree} is read-only. + * @param metadata + * The {@link IndexMetadata} object for this B+Tree. + * @param recordCompressorFactory + * Object that knows how to (de-)compress the serialized data + * records. + */ protected AbstractBTree(// final IRawStore store,// final INodeFactory nodeFactory,// @@ -3761,40 +3761,40 @@ if (addr == IRawStore.NULL) throw new IllegalArgumentException(); - final Long addr2 = Long.valueOf(addr); - - if (storeCache != null) { - - // test cache : will touch global LRU iff found. - final IAbstractNodeData data = (IAbstractNodeData) storeCache - .get(addr); - - if (data != null) { - - // Node and Leaf MUST NOT make it into the global LRU or store - // cache! - assert !(data instanceof AbstractNode<?>); - - final AbstractNode<?> node; - - if (data.isLeaf()) { - - node = nodeSer.nodeFactory.allocLeaf(this, addr, - (ILeafData) data); - - } else { - - node = nodeSer.nodeFactory.allocNode(this, addr, - (INodeData) data); - - } - - // cache hit. - return node; - - } - - } +// final Long addr2 = Long.valueOf(addr); +// +// if (storeCache != null) { +// +// // test cache : will touch global LRU iff found. +// final IAbstractNodeData data = (IAbstractNodeData) storeCache +// .get(addr); +// +// if (data != null) { +// +// // Node and Leaf MUST NOT make it into the global LRU or store +// // cache! +// assert !(data instanceof AbstractNode<?>); +// +// final AbstractNode<?> node; +// +// if (data.isLeaf()) { +// +// node = nodeSer.nodeFactory.allocLeaf(this, addr, +// (ILeafData) data); +// +// } else { +// +// node = nodeSer.nodeFactory.allocNode(this, addr, +// (INodeData) data); +// +// } +// +// // cache hit. +// return node; +// +// } +// +// } final ByteBuffer tmp; { @@ -3851,21 +3851,21 @@ } - if (storeCache != null) { - - // update cache : will touch global LRU iff cache is modified. - final IAbstractNodeData data2 = (IAbstractNodeData) storeCache - .putIfAbsent(addr2, data); +// if (storeCache != null) { +// +// // update cache : will touch global LRU iff cache is modified. +// final IAbstractNodeData data2 = (IAbstractNodeData) storeCache +// .putIfAbsent(addr2, data); +// +// if (data2 != null) { +// +// // concurrent insert, use winner's value. +// data = data2; +// +// } +// +// } - if (data2 != null) { - - // concurrent insert, use winner's value. - data = data2; - - } - - } - // wrap as Node or Leaf. final AbstractNode<?> node = nodeSer.wrap(this, addr, data); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -35,6 +35,8 @@ import org.apache.log4j.Logger; import com.bigdata.btree.data.IAbstractNodeData; +import com.bigdata.btree.data.IKeysData; +import com.bigdata.btree.data.ISpannedTupleCountData; import com.bigdata.btree.filter.EmptyTupleIterator; import com.bigdata.btree.raba.IRaba; import com.bigdata.btree.raba.MutableKeyBuffer; @@ -55,7 +57,7 @@ * DO-NOT-USE-GENERIC-HERE. The compiler will fail under Linux (JDK 1.6.0_14, * _16). */ -> extends PO implements IAbstractNode, IAbstractNodeData { +> extends PO implements IAbstractNode, IAbstractNodeData, IKeysData, ISpannedTupleCountData { /** * Log for node and leaf operations. Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BTree.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BTree.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -1162,46 +1162,62 @@ assertNotTransient(); assertNotReadOnly(); - /* - * Note: Acquiring this lock provides for atomicity of the checkpoint of - * the BTree during the commit protocol. Without this lock, users of the - * UnisolatedReadWriteIndex could be concurrently modifying the BTree - * while we are attempting to snapshot it for the commit. - * - * @see https://sourceforge.net/apps/trac/bigdata/ticket/288 - * @see https://sourceforge.net/apps/trac/bigdata/ticket/278 - */ - final Lock lock = new UnisolatedReadWriteIndex(this).writeLock(); - try { - - if (/*autoCommit &&*/ needsCheckpoint()) { + /* + * Note: Acquiring this lock provides for atomicity of the checkpoint of + * the BTree during the commit protocol. Without this lock, users of the + * UnisolatedReadWriteIndex could be concurrently modifying the BTree + * while we are attempting to snapshot it for the commit. + * + * Note: An alternative design would declare a global read/write lock + * for mutation of the indices in addition to the per-BTree read/write + * lock provided by UnisolatedReadWriteIndex. Rather than taking the + * per-BTree write lock here, we would take the global write lock in the + * AbstractJournal's commit protocol, e.g., commitNow(). The global read + * lock would be taken by UnisolatedReadWriteIndex before taking the + * per-BTree write lock. This is effectively a hierarchical locking + * scheme and could provide a workaround if deadlocks are found to occur + * due to lock ordering problems with the acquisition of the + * UnisolatedReadWriteIndex lock (the absence of lock ordering problems + * really hinges around UnisolatedReadWriteLocks not being taken for + * more than one index at a time.) + * + * @see https://sourceforge.net/apps/trac/bigdata/ticket/288 + * + * @see https://sourceforge.net/apps/trac/bigdata/ticket/278 + */ + final Lock lock = new UnisolatedReadWriteIndex(this).writeLock(); + try { - /* - * Flush the btree, write a checkpoint record, and return the - * address of that checkpoint record. The [checkpoint] reference - * is also updated. - */ + if (/* autoCommit && */needsCheckpoint()) { - return writeCheckpoint(); + /* + * Flush the btree, write a checkpoint record, and return the + * address of that checkpoint record. The [checkpoint] reference + * is also updated. + */ - } + return writeCheckpoint(); - /* - * There have not been any writes on this btree or auto-commit is - * disabled. - * - * Note: if the application has explicitly invoked writeCheckpoint() - * then the returned address will be the address of that checkpoint - * record and the BTree will have a new checkpoint address made - * restart safe on the backing store. - */ - - return checkpoint.addrCheckpoint; + } - } finally { - lock.unlock(); - } + /* + * There have not been any writes on this btree or auto-commit is + * disabled. + * + * Note: if the application has explicitly invoked writeCheckpoint() + * then the returned address will be the address of that checkpoint + * record and the BTree will have a new checkpoint address made + * restart safe on the backing store. + */ + return checkpoint.addrCheckpoint; + + } finally { + + lock.unlock(); + + } + } /** Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/BytesUtil.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -1034,8 +1034,8 @@ * @return The hash value considering only the MSB <i>nbits</i> and shifted * down into an <i>nbits</i> integer. */ - public static int maskOff(final int h, final int nbits) { - + public static int maskOffMSB(final int h, final int nbits) { + if (nbits < 0 || nbits > 32) throw new IllegalArgumentException(); @@ -1048,6 +1048,29 @@ } /** + * Mask off all but the LSB <i>nbits</i> of the hash value. + * + * @param h + * The hash value. + * @param nbits + * The #of LSB bits to be retained. + * + * @return The LSB <i>nbits</i>. + * + * TODO unit test. + */ + public static int maskOffLSB(final int h, final int nbits) { + + if (nbits < 0 || nbits > 32) + throw new IllegalArgumentException(); + + final int v = h & ~masks32[nbits]; + + return v; + + } + + /** * Return the n-bit integer corresponding to the inclusive bit range of the * byte[]. Bit ZERO (0) is the Most Significant Bit (MSB). Bit positions * increase from zero up to <code>a.length * 8 - 1</code>. The return value Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/IndexSegmentBuilder.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/IndexSegmentBuilder.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/IndexSegmentBuilder.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -48,6 +48,7 @@ import com.bigdata.btree.data.IAbstractNodeData; import com.bigdata.btree.data.ILeafData; import com.bigdata.btree.data.INodeData; +import com.bigdata.btree.data.ISpannedTupleCountData; import com.bigdata.btree.raba.IRaba; import com.bigdata.btree.raba.MutableKeyBuffer; import com.bigdata.btree.raba.MutableValueBuffer; @@ -3284,7 +3285,7 @@ * Thompson</a> */ abstract protected static class AbstractSimpleNodeData implements - IAbstractNodeData { + IAbstractNodeData, ISpannedTupleCountData { /** * The level in the output tree for this node or leaf (origin zero). The Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/UnisolatedReadWriteIndex.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/UnisolatedReadWriteIndex.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/UnisolatedReadWriteIndex.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -45,7 +45,6 @@ import com.bigdata.btree.proc.IResultHandler; import com.bigdata.btree.proc.ISimpleIndexProcedure; import com.bigdata.btree.view.FusedView; -import com.bigdata.concurrent.LockManager; import com.bigdata.counters.ICounterSet; import com.bigdata.journal.ConcurrencyManager; import com.bigdata.journal.IConcurrencyManager; @@ -112,22 +111,19 @@ * require a means to correctly interleave access to the unisolated index, which * is the purpose of this class. * <p> - * While the {@link LockManager} could be modified to support Share vs Exclusive - * locks and to use Share locks for readers and Exclusive locks for writers, - * writers would still block until the next commit so the throughput (e.g., when + * While the lock manager could be modified to support Share vs Exclusive locks + * and to use Share locks for readers and Exclusive locks for writers, writers + * would still block until the next commit so the throughput (e.g., when * computing the fix point of a rule set) is significantly lower. * * @author <a href="mailto:tho...@us...">Bryan Thompson</a> - * @version $Id$ + * @version $Id: UnisolatedReadWriteIndex.java 4054 2011-01-05 13:51:25Z + * thompsonbry $ */ public class UnisolatedReadWriteIndex implements IIndex { - protected static final Logger log = Logger.getLogger(UnisolatedReadWriteIndex.class); - -// protected static final boolean INFO = log.isInfoEnabled(); - - protected static final boolean DEBUG = log.isDebugEnabled(); - + private static final Logger log = Logger.getLogger(UnisolatedReadWriteIndex.class); + /** * The #of milliseconds that the class will wait for a read or write lock. A * (wrapped) {@link InterruptedException} will be thrown if this timeout is @@ -158,7 +154,7 @@ try { - if(DEBUG) { + if(log.isDebugEnabled()) { log.debug(ndx.toString()); @@ -198,7 +194,7 @@ try { - if(DEBUG) { + if(log.isDebugEnabled()) { log.debug(ndx.toString() // , new RuntimeException() @@ -233,7 +229,7 @@ * * @return The acquired lock. */ - private Lock lock(IIndexProcedure proc) { + private Lock lock(final IIndexProcedure proc) { if (proc == null) throw new IllegalArgumentException(); @@ -248,11 +244,11 @@ } - private void unlock(Lock lock) { + private void unlock(final Lock lock) { lock.unlock(); - if(DEBUG) { + if(log.isDebugEnabled()) { log.debug(ndx.toString()); @@ -264,7 +260,7 @@ * The unisolated index partition. This is either a {@link BTree} or a * {@link FusedView}. */ - final private IIndex ndx; + final private BTree ndx; /** * The {@link ReadWriteLock} used to permit concurrent readers on an @@ -343,23 +339,41 @@ this.ndx = ndx; - synchronized (locks) { + this.readWriteLock = getReadWriteLock(ndx); - ReadWriteLock readWriteLock = locks.get(ndx); + } - if (readWriteLock == null) { + /** + * Canonicalizing factory for the {@link ReadWriteLock} for a {@link BTree}. + * + * @param btree + * The btree. + * @return The lock. + * + * @throws IllegalArgumentException + * if the argument is <code>null</code>. + */ + private ReadWriteLock getReadWriteLock(final BTree btree) { - readWriteLock = new ReentrantReadWriteLock(false/* fair */); + if (ndx == null) + throw new IllegalArgumentException(); + + synchronized (locks) { - locks.put(ndx, readWriteLock); + ReadWriteLock readWriteLock = locks.get(ndx); - } + if (readWriteLock == null) { - this.readWriteLock = readWriteLock; - - } - - } + readWriteLock = new ReentrantReadWriteLock(false/* fair */); + + locks.put(ndx, readWriteLock); + + } + + return readWriteLock; + } + + } public String toString() { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IAbstractNodeData.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IAbstractNodeData.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IAbstractNodeData.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -27,7 +27,6 @@ package com.bigdata.btree.data; -import com.bigdata.btree.raba.IRaba; import com.bigdata.io.AbstractFixedByteArrayBuffer; import com.bigdata.io.IDataRecordAccess; @@ -93,28 +92,28 @@ */ long getMaximumVersionTimestamp(); - /** - * The #of tuples spanned by this node or leaf. For a leaf this is always - * the #of keys. - * - * @see INodeData#getChildEntryCounts() - */ - int getSpannedTupleCount(); +// /** +// * The #of tuples spanned by this node or leaf. For a leaf this is always +// * the #of keys. +// * +// * @see INodeData#getChildEntryCounts() +// */ +// int getSpannedTupleCount(); - /** - * Return the #of keys in the node or leaf. A node has <code>nkeys+1</code> - * children. A leaf has <code>nkeys</code> keys and values. The maximum #of - * keys for a node is one less than the branching factor of the B+Tree. The - * maximum #of keys for a leaf is the branching factor of the B+Tree. For a - * hash bucket, this is the #of entries in the bucket. - * - * @return The #of defined keys. - */ - int getKeyCount(); +// /** +// * Return the #of keys in the node or leaf. A node has <code>nkeys+1</code> +// * children. A leaf has <code>nkeys</code> keys and values. The maximum #of +// * keys for a node is one less than the branching factor of the B+Tree. The +// * maximum #of keys for a leaf is the branching factor of the B+Tree. For a +// * hash bucket, this is the #of entries in the bucket. +// * +// * @return The #of defined keys. +// */ +// int getKeyCount(); +// +// /** +// * The object used to contain and manage the keys. +// */ +// IRaba getKeys(); - /** - * The object used to contain and manage the keys. - */ - IRaba getKeys(); - } Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IChildData.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IChildData.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IChildData.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -0,0 +1,57 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2007. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on May 2nd, 2011 + */ +package com.bigdata.btree.data; + +/** + * Interface for data access to children of an index node. + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id: ILeafData.java 4388 2011-04-11 13:35:47Z thompsonbry $ + */ +public interface IChildData { + + /** + * The #of children of this node. Either all children will be nodes or all + * children will be leaves. The #of children of a node MUST be + * <code>{@link IAbstractNodeData#getKeyCount()}+1</code> + * + * @return The #of children of this node. + */ + public int getChildCount(); + + /** + * Return the persistent addresses of the specified child node. + * + * @param index + * The index of the child in [0:nkeys]. + * + * @return The persistent child address -or- zero(0L) if the child is not + * persistent. + */ + public long getChildAddr(int index); + +} Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IKeysData.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IKeysData.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/IKeysData.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -0,0 +1,56 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2007. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on May 2nd, 2011 + */ +package com.bigdata.btree.data; + +import com.bigdata.btree.raba.IRaba; + +/** + * Interface for access to the keys {@link IRaba} of a node or leaf in an index + * data structure. + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id: ILeafData.java 4388 2011-04-11 13:35:47Z thompsonbry $ + */ +public interface IKeysData { + + /** + * Return the #of keys in the node or leaf. A node has <code>nkeys+1</code> + * children. A leaf has <code>nkeys</code> keys and values. The maximum #of + * keys for a node is one less than the branching factor of the B+Tree. The + * maximum #of keys for a leaf is the branching factor of the B+Tree. For a + * hash bucket, this is the #of entries in the bucket. + * + * @return The #of defined keys. + */ + int getKeyCount(); + + /** + * The object used to contain and manage the keys. + */ + IRaba getKeys(); + +} Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/ILeafData.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/ILeafData.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/ILeafData.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -37,7 +37,7 @@ * @author <a href="mailto:tho...@us...">Bryan Thompson</a> * @version $Id$ */ -public interface ILeafData extends IAbstractNodeData { +public interface ILeafData extends IAbstractNodeData, IKeysData, ISpannedTupleCountData { /** * The #of values in the leaf (this MUST be equal to the #of keys for a Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/INodeData.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/INodeData.java 2011-05-07 15:44:27 UTC (rev 4462) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/INodeData.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -27,36 +27,35 @@ package com.bigdata.btree.data; - /** * Interface for low-level data access for the non-leaf nodes of a B+-Tree. * * @author <a href="mailto:tho...@us...">Bryan Thompson</a> * @version $Id$ */ -public interface INodeData extends IAbstractNodeData { +public interface INodeData extends IAbstractNodeData, IKeysData, IChildData, ISpannedTupleCountData { - /** - * The #of children of this node. Either all children will be nodes or all - * children will be leaves. The #of children of a node MUST be - * <code>{@link IAbstractNodeData#getKeyCount()}+1</code> - * - * @return The #of children of this node. - */ - public int getChildCount(); +// /** +// * The #of children of this node. Either all children will be nodes or all +// * children will be leaves. The #of children of a node MUST be +// * <code>{@link IAbstractNodeData#getKeyCount()}+1</code> +// * +// * @return The #of children of this node. +// */ +// public int getChildCount(); +// +// /** +// * Return the persistent addresses of the specified child node. +// * +// * @param index +// * The index of the child in [0:nkeys]. +// * +// * @return The persistent child address -or- zero(0L) if the child is not +// * persistent. +// */ +// public long getChildAddr(int index); /** - * Return the persistent addresses of the specified child node. - * - * @param index - * The index of the child in [0:nkeys]. - * - * @return The persistent child address -or- zero(0L) if the child is not - * persistent. - */ - public long getChildAddr(int index); - - /** * Return the #of tuples spanned by the indicated child of this node. The * sum of the values returned by this method across the children of the node * should always equal the value returned by {@link #getSpannedTupleCount()} Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/ISpannedTupleCountData.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/ISpannedTupleCountData.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/data/ISpannedTupleCountData.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -0,0 +1,47 @@ +/** + +Copyright (C) SYSTAP, LLC 2006-2007. All rights reserved. + +Contact: + SYSTAP, LLC + 4501 Tower Road + Greensboro, NC 27410 + lic...@bi... + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + * Created on Dec 15, 2006 + */ + +package com.bigdata.btree.data; + +/** + * Interface for low-level data access to the #of tuples spanned by a node or + * leaf of an index. + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id: ILeafData.java 4388 2011-04-11 13:35:47Z thompsonbry $ + */ +public interface ISpannedTupleCountData { + + /** + * The #of tuples spanned by this node or leaf. For a leaf this is always + * the #of keys. + * + * @see INodeData#getChildEntryCounts() + */ + int getSpannedTupleCount(); + +} Added: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/AbstractHTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/AbstractHTree.java (rev 0) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/AbstractHTree.java 2011-05-08 14:15:50 UTC (rev 4463) @@ -0,0 +1,918 @@ +package com.bigdata.htree; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.util.HashMap; + +import org.apache.log4j.Logger; + +import com.bigdata.Banner; +import com.bigdata.LRUNexus; +import com.bigdata.btree.AbstractBTree; +import com.bigdata.btree.AbstractNode; +import com.bigdata.btree.BTree; +import com.bigdata.btree.BTreeCounters; +import com.bigdata.btree.DefaultEvictionListener; +import com.bigdata.btree.IndexMetadata; +import com.bigdata.btree.IndexSegment; +import com.bigdata.btree.Leaf; +import com.bigdata.btree.Node; +import com.bigdata.btree.NodeSerializer; +import com.bigdata.btree.PO; +import com.bigdata.btree.data.IAbstractNodeData; +import com.bigdata.btree.data.ILeafData; +import com.bigdata.btree.data.INodeData; +import com.bigdata.cache.HardReferenceQueue; +import com.bigdata.cache.HardReferenceQueueWithBatchingUpdates; +import com.bigdata.cache.IHardReferenceQueue; +import com.bigdata.cache.RingBuffer; +import com.bigdata.htree.HTree.AbstractPage; +import com.bigdata.htree.HTree.DirectoryPage; +import com.bigdata.rawstore.IRawStore; +import com.bigdata.resources.IndexManager; +import com.bigdata.service.DataService; + +/** + * Abstract base class for a persistence capable extensible hash tree. + * + * @author <a href="mailto:tho...@us...">Bryan Thompson</a> + * @version $Id$ + */ +abstract public class AbstractHTree { + + /** + * The index is already closed. + */ + protected static final String ERROR_CLOSED = "Closed"; + + /** + * A parameter was less than zero. + */ + protected static final String ERROR_LESS_THAN_ZERO = "Less than zero"; + + /** + * A parameter was too large. + */ + protected static final String ERROR_TOO_LARGE = "Too large"; + + /** + * The index is read-only but a mutation operation was requested. + */ + final protected static String ERROR_READ_ONLY = "Read-only"; + + /** + * The index is transient (not backed by persistent storage) but an + * operation that requires persistence was requested. + */ + final protected static String ERROR_TRANSIENT = "Transient"; + + private static final transient Logger log = Logger.getLogger(AbstractHTree.class); + + /** + * Counters tracking various aspects of the btree. + * <p> + * Note: This is <code>volatile</code> to avoid the need for + * synchronization in order for changes in the reference to be visible to + * threads. + */ + private volatile BTreeCounters btreeCounters = new BTreeCounters(); + + /** + * Counters tracking various aspects of the btree. + */ + final public BTreeCounters getBtreeCounters() { + + return btreeCounters; + + } + + /** + * Replace the {@link BTreeCounters}. + * <p> + * Note: This is used by the {@link IndexManager} to ensure that an index + * loaded from its backing store uses the {@link BTreeCounters} associated + * with that index since the {@link DataService} was last (re-)started. + * + * @param btreeCounters + * The counters to be used. + * + * @throws IllegalArgumentException + * if the argument is <code>null</code>. + */ + final public void setBTreeCounters(final BTreeCounters btreeCounters) { + + if (btreeCounters == null) + throw new IllegalArgumentException(); + + /* + * Note: synchronized NOT required since reference is volatile. + */ + + this.btreeCounters = btreeCounters; + + } + + /** + * The backing store. + */ + protected final IRawStore store; + + /** + * The #of bits in the address space for a directory page (from the + * constructor). This constant is specified when the hash tree is created. A + * directory page has <code>2^addressBits</code> entries. Those entries are + * divided up among one or more buddy hash tables on that page. + */ + protected final int addressBits; + + /** + * The #of entries in a directory bucket, which is 2^{@link #addressBits} + * (aka <code>1<<addressBits</code>). + */ + protected final int branchingFactor; + + /** + * The root node. + */ + protected volatile AbstractPage root; + + /** + * Nodes (that is nodes or leaves) are added to a hard reference queue when + * they are created or read from the store. On eviction from the queue a + * dirty node is serialized by a listener against the {@link IRawStore}. + * The nodes and leaves refer to their parent with a {@link WeakReference}s. + * Likewise, nodes refer to their children with a {@link WeakReference}. + * The hard reference queue in combination with {@link #touch(AbstractNode)} + * and with hard references held on the stack ensures that the parent and/or + * children remain reachable during operations. Once the node is no longer + * strongly reachable weak references to that node may be cleared by the VM - + * in this manner the node will become unreachable by navigation from its + * ancestors in the btree. The special role of the hard reference queue is + * to further ensure that dirty nodes remain dirty by defering persistence + * until the reference count for the node is zero during an eviction from + * the queue. + * <p> + * Note that nodes are evicted as new nodes are added to the hard reference + * queue. This occurs in two situations: (1) when a new node is created + * during a split of an existing node; and (2) when a node is read in from + * the store. Inserts on this hard reference queue always drive evictions. + * Incremental writes basically make it impossible for the commit set to get + * "too large" - the maximum #of nodes to be written is bounded by the size + * of the hard reference queue. This helps to ensure fast commit operations + * on the store. + * <p> + * The minimum capacity for the hard reference queue is two (2) so that a + * split may occur without forcing eviction of either node participating in + * the split. + * <p> + * Note: The code in {@link Node#postOrderNodeIterator(boolean, boolean)} and + * {@link DirtyChildIterator} MUST NOT touch the hard reference queue since + * those iterators are used when persisting a node using a post-order + * traversal. If a hard reference queue eviction drives the serialization of + * a node and we touch the hard reference queue during the post-order + * traversal then we break down the semantics of + * {@link HardReferenceQueue#add(Object)} as the eviction does not + * necessarily cause the queue to reduce in length. Another way to handle + * this is to have {@link HardReferenceQueue#add(Object)} begin to evict + * objects before is is actually at capacity, but that is also a bit + * fragile. + * <p> + * Note: The {@link #writeRetentionQueue} uses a {@link HardReferenceQueue}. + * This is based on a {@link RingBuffer} and is very fast. It does not use a + * {@link HashMap} because we can resolve the {@link WeakReference} to the + * child {@link Node} or {@link Leaf} using top-down navigation as long as + * the {@link Node} or {@link Leaf} remains strongly reachable (that is, as + * long as it is on the {@link #writeRetentionQueue} or otherwise strongly + * held). This means that lookup in a map is not required for top-down + * navigation. + * <p> + * The {@link LRUNexus} provides an {@link INodeData} / {@link ILeafData} + * data record cache based on a hash map with lookup by the address of the + * node or leaf. This is tested when the child {@link WeakReference} was + * never set or has been cleared. This cache is also used by the + * {@link IndexSegment} for the linked-leaf traversal pattern, which does + * not use top-down navigation. + * + * @todo consider a policy that dynamically adjusts the queue capacities + * based on the height of the btree in order to maintain a cache that + * can contain a fixed percentage, e.g., 5% or 10%, of the nodes in + * the btree. The minimum and maximum size of the cache should be + * bounded. Bounding the minimum size gives better performance for + * small trees. Bounding the maximum size is necessary when the trees + * grow very large. (Partitioned indices may be used for very large + * indices and they can be distributed across a cluster of machines.) + * <p> + * There is a discussion of some issues regarding such a policy in the + * code inside of + * {@link Node#Node(BTree btree, AbstractNode oldRoot, int nentries)}. + */ + final protected IHardReferenceQueue<PO> writeRetentionQueue; + + /** + * The #of distinct nodes and leaves on the {@link #writeRetentionQueue}. + */ + protected int ndistinctOnWriteRetentionQueue; + + /** + * Return <code>true</code> iff this B+Tree is read-only. + */ + abstract public boolean isReadOnly(); + + /** + * + * @throws UnsupportedOperationException + * if the B+Tree is read-only. + * + * @see #isReadOnly() + */ + final protected void assertNotReadOnly() { + + if(isReadOnly()) { + + throw new UnsupportedOperationException(ERROR_READ_ONLY); + + } + + } + +// /** +// * The metadata record for the index. This data rarely changes during the +// * life of the {@link HTree} object, but it CAN be changed. +// */ +// protected IndexMetadata metadata; + + /** + * Used to serialize and de-serialize the nodes and leaves of the tree. + */ + final protected NodeSerializer nodeSer = null; + + /** + * The backing store. + */ + public IRawStore getStore() { + return store; + } + + /** + * The #of bits in the address space for a hash directory page. This + * constant is specified to the constructor. The #of child pages is + * <code>2^addressBits</code>. When <i>addressBits</i> is <code>10</code> we + * have <code>2^10 := 1024</code>. If the size of a child address is 4 + * bytes, then a 10 bit address space implies a 4k page size. + */ + public final int getAddressBits() { + return addressBits; + } + + /** + * @param store + * The persistence store. + * @param nodeFactory + * Object that provides a factory for node and leaf objects. + * @param readOnly + * <code>true</code> IFF it is <em>known</em> that the + * {@link AbstractBTree} is read-only. + * @param metadata + * The {@link IndexMetadata} object for this B+Tree. + * @param recordCompressorFactory + * Object that knows how to (de-)compress the serialized data + * records. + */ + protected AbstractHTree(// + final IRawStore store,// +// final INodeFactory nodeFactory,// + final boolean readOnly, + final int addressBits // TODO Move into IndexMetadata? +// final IndexMetadata metadata,// +// final IRecordCompressorFactory<?> recordCompressorFactory + ) { + + // show the copyright banner during startup. + Banner.banner(); + + if (store == null) + throw new IllegalArgumentException(); + + if (addressBits <= 0) + throw new IllegalArgumentException(); + + if (addressBits > 32) + throw new IllegalArgumentException(); + +// if (nodeFactory == null) +// throw new IllegalArgumentException(); + + // Note: MAY be null (implies a transient BTree). +// assert store != null; + +// if (metadata == null) +// throw new IllegalArgumentException(); +// +// // save a reference to the immutable metadata record. +// this.metadata = metadata; + +// this.writeTuple = new Tuple(this, KEYS | VALS); + + this.store = store; + this.addressBits = addressBits; + this.branchingFactor = 1 << addressBits; + +// /* +// * Compute the minimum #of children/values. This is the same whether +// * this is a Node or a Leaf. +// */ +// minChildren = (branchingFactor + 1) >> 1; + +//// /* +//// * The Memoizer is not used by the mutable B+Tree since it is not safe +//// * for concurrent operations. +//// */ +//// memo = !readOnly ? null : new ChildMemoizer(loadChild); +// /* +// * Note: The Memoizer pattern is now used for both mutable and read-only +// * B+Trees. This is because the real constraint on the mutable B+Tree is +// * that mutation may not be concurrent with any other operation but +// * concurrent readers ARE permitted. The UnisolatedReadWriteIndex +// * explicitly permits concurrent read operations by virtue of using a +// * ReadWriteLock rather than a single lock. +// */ +// memo = new ChildMemoizer(loadChild); + + /* + * Setup buffer for Node and Leaf objects accessed via top-down + * navigation. While a Node or a Leaf remains on this buffer the + * parent's WeakReference to the Node or Leaf will not be cleared and it + * will remain reachable. + */ + this.writeRetentionQueue = newWriteRetentionQueue(readOnly); + +// this.nodeSer = new NodeSerializer(// +// store, // addressManager +// nodeFactory,// +// branchingFactor,// +// 0, //initialBufferCapacity +// metadata,// +// readOnly,// +// recordCompressorFactory +// ); + +// if (store == null) { +// +// /* +// * Transient BTree. +// * +// * Note: The write retention queue controls how long nodes remain +// * mutable. On eviction, they are coded but not written onto the +// * backing store (since there is none for a transient BTree). +// * +// * The readRetentionQueue is not used for a transient BTree since +// * the child nodes and the parents are connected using hard links +// * rather than weak references. +// */ +// +// this.storeCache = null; +// +//// this.globalLRU = null; +// +//// this.readRetentionQueue = null; +// +// } else { +// +// /* +// * Persistent BTree. +// * +// * The global LRU is used to retain recently used node/leaf data +// * records in memory and the per-store cache provides random access +// * to those data records. Only the INodeData or ILeafData is stored +// * in the cache. This allows reuse of the data records across B+Tree +// * instances since the data are read-only and the data records +// * support concurrent read operations. The INodeData or ILeafData +// * will be wrapped as a Node or Leaf by the owning B+Tree instance. +// */ +// +// /* +// * FIXME if the LRUNexus is disabled, then use a +// * ConcurrentWeakValueCacheWithTimeout to buffer the leaves of an +// * IndexSegment. Essentially, a custom cache. Otherwise we lose some +// * of the performance of the leaf iterator for the index segment +// * since leaves are not recoverable by random access without a +// * cache. +// */ +// this.storeCache = LRUNexus.getCache(store); +// +//// this.readRetentionQueue = newReadRetentionQueue(); +// +// } + + } + + /** + * Note: Method is package private since it must be overridden for some unit + * tests. + */ + IHardReferenceQueue<PO> newWriteRetentionQueue(final boolean readOnly) { + + // FIXME Restore use of the [metadata] object. + final int writeRetentionQueueCapacity = 1000;//metadata.getWriteRetentionQueueCapacity(); + final int writeRetentionQueueScan = 5;//metadata.getWriteRetentionQueueScan(); + + if(readOnly) { + + /* + * This provisions an alternative hard reference queue using thread + * local queues to collect hard references which are then batched + * through to the backing hard reference queue in order to reduce + * contention for the lock required to write on the backing hard + * reference queue (no lock is required for the thread-local + * queues). + * + * The danger with a true thread-local design is that a thread can + * come in and do some work, get some updates buffered in its + * thread-local array, and then never visit again. In this case + * those updates would remain buffered on the thread and would not + * in fact cause the access order to be updated in a timely manner. + * Worse, if you are relying on WeakReference semantics, the + * buffered updates would remain strongly reachable and the + * corresponding objects would be wired into the cache. + * + * I've worked around this issue by scoping the buffers to the + * AbstractBTree instance. When the B+Tree container is closed, all + * buffered updates were discarded. This nicely eliminated the + * problems with "escaping" threads. This approach also has the + * maximum concurrency since there is no blocking when adding a + * touch to the thread-local buffer. + * + * Another approach is to use striped locks. The infinispan BCHM + * does this. In this approach, the Segment is guarded by a lock and + * the array buffering the touches is inside of the Segment. Since + * the Segment is selected by the hash of the key, all Segments will + * be visited in a timely fashion for any reasonable workload. This + * ensures that updates can not "escape" and will be propagated to + * the shared backing buffer in a timely manner. + */ + + return new HardReferenceQueueWithBatchingUpdates<PO>(// + new HardReferenceQueue<PO>(new DefaultEvictionListener(), + writeRetentionQueueCapacity, 0/* nscan */), +// new DefaultEvictionListener(),// +// metadata.getWriteRetentionQueueCapacity(),// shared capacity + writeRetentionQueueScan,// thread local + 128,//64, // thread-local queue capacity @todo config + 64, //32 // thread-local tryLock size @todo config + null // batched updates listener. + ); + + } + + return new HardReferenceQueue<PO>(// + new DefaultEvictionListener(),// + writeRetentionQueueCapacity,// + writeRetentionQueueScan// + ); + + } + + /** + * <p> + * This method is responsible for putting the node or leaf onto the ring + * buffer which controls (a) how long we retain a hard reference to the node + * or leaf; and (b) for writes, when the node or leaf is evicted with a zero + * reference count and made persistent (along with all dirty children). The + * concurrency requirements and the implementation behavior and guarentees + * differ depending on whether the B+Tree is read-only or mutable for two + * reasons: For writers, the B+Tree is single-threaded so there is no + * contention. For readers, every touch on the B+Tree goes through this + * point, so it is vital to make this method non-blocking. + * </p> + * <h3>Writers</h3> + * <p> + * This method guarantees that the specified node will NOT be synchronously + * persisted as a side effect and thereby made immutable. (Of course, the + * node may be already immutable.) + * </p> + * <p> + * In conjunction with {@link DefaultEvictionListener}, this method + * guarentees that the reference counter for the node will reflect the #of + * times that the node is actually present on the + * {@link #writeRetentionQueue}. + * </p> + * <p> + * If the node is not found on a scan of the head of the queue, then it is + * appended to the queue and its {@link AbstractNode#referenceCount} is + * incremented. If a node is being appended to the queue and the queue is at + * capacity, then this will cause a reference to be evicted from the queue. + * If the reference counter for the evicted node or leaf is zero and the + * evicted node or leaf is dirty, then a data record will be coded for the + * evicted node or leaf and written onto the backing store. A subsequent + * attempt to modify the node or leaf will force copy-on-write for that node + * or leaf. Regardless of whether or not the node or leaf is dirty, it is + * touched on the {@link LRUNexus#getGlobalLRU()} when it is evicted from + * the write retention queue. + * </p> + * <p> + * For the mutable B+Tree we also track the #of references to the node/leaf + * on the ring buffer. When that reference count reaches zero we do an + * eviction and the node/leaf is written onto the backing store if it is + * dirty. Those reference counting games DO NOT matter for read-only views + * so we can take a code path which does not update the per-node/leaf + * reference count and we do not need to use either synchronization or + * atomic counters to track the reference counts. + * </p> + * <h3>Readers</h3> + * <p> + * In order to reduce contention for the lock required to update the backing + * queue, the {@link #writeRetentionQueue} is configured to collect + * references for touched nodes or leaves in a thread-local queue and then + * batch those references through to the backing hard reference queue while + * holding the lock. + * </p> + * + * @param node + * The node or leaf. + * + * @todo The per-node/leaf reference counts and + * {@link #ndistinctOnWriteRetentionQueue} fields are not guaranteed + * to be consistent for of concurrent readers since no locks are held + * when those fields are updated. Since the reference counts only + * effect when a node is made persistent (assuming its dirty) and + * since we already require single threaded access for writes on the + * btree, this does not cause a problem but can lead to unexpected + * values for the reference counters and + * {@link #ndistinctOnWriteRetentionQueue}. + * + * @todo This might be abstract here and final in BTree and in IndexSegment. + * For {@link IndexSegment}, we know it is read-only so we do not need + * to test anything. For {@link BTree}, we can break encapsulation and + * check whether or not the {@link BTree} is read-only more readily + * than we can in this class. + */ +// synchronized +// final + protected void touch(final AbstractPage node) { + + assert node != null; + + /* + * Note: DO NOT update the last used timestamp for the B+Tree here! This + * is a huge performance penalty! + */ +// touch(); + + if (isReadOnly()) { + + doTouch(node); + + return; + + } + + /* + * Note: Synchronization appears to be necessary for the mutable BTree. + * Presumably this provides safe publication when the application is + * invoking operations on the same mutable BTree instance from different + * threads but it coordinating those threads in order to avoid + * concurrent operations, e.g., by using the UnisolatedReadWriteIndex + * wrapper class. The error which is prevented by synchronization is + * RingBuffer#add(ref) reporting that size == capacity, which indicates + * that the size was not updated consistently and hence is basically a + * concurrency problem. + * + * @todo Actually, I think that this is just a fence post in ringbuffer + * beforeOffer() method and the code might work without the synchronized + * block if the fence post was fixed. + * + * @see https://sourceforge.net/apps/trac/bigdata/ticket/201 + */ + + synchronized (this) { + + doTouch(node); + + } + + } + + private final void doTouch(final AbstractPage node) { + + /* + * We need to guarantee that touching this node does not cause it to be + * made persistent. The condition of interest would arise if the queue + * is full and the referenceCount on the node is zero before this method + * was called. Under those circumstances, simply appending the node to + * the queue would cause it to be evicted and made persistent. + * + * We avoid this by incrementing the reference counter before we touch + * the queue. Since the reference counter will therefore be positive if + * the node is selected for eviction, eviction will not cause the node + * to be made persistent. + * + * Note: Only mutable BTrees may have dirty nodes and the mutable BTree + * is NOT thread-safe so we do not need to use synchronization or an + * AtomicInteger for the referenceCount field. + * + * Note: The reference counts and the #of distinct nodes or leaves on + * the writeRetentionQueue are not exact for a read-only B+Tree because + * neither synchronization nor atomic counters are used to track that + * information. + */ + +// assert isReadOnly() || ndistinctOnWriteRetentionQueue > 0; + + node.referenceCount++; + + if (!writeRetentionQueue.add(node)) { + + /* + * A false return indicates that the node was found on a scan of the + * tail of the queue. In this case we do NOT want the reference + * counter to be incremented since we have not actually added + * another reference to this node onto the queue. Therefore we + * decrement the counter (since we incremented it above) for a net + * change of zero(0) across this method. + */ + + node.referenceCount--; + + } else { + + /* + * Since we just added a node or leaf to the hard reference queue we + * now update the #of distinct nodes and leaves on the hard + * reference queue. + * + * Also see {@link DefaultEvictionListener}. + */ + + if (node.referenceCount == 1) { + + ndistinctOnWriteRetentionQueue++; + + } + + } + +// if (useFinger && node instanceof ILeafData) { + // +// if (finger == null || finger.get() != node) { + // +// ... [truncated message content] |
From: <tho...@us...> - 2011-05-10 19:05:42
|
Revision: 4477 http://bigdata.svn.sourceforge.net/bigdata/?rev=4477&view=rev Author: thompsonbry Date: 2011-05-10 19:05:34 +0000 (Tue, 10 May 2011) Log Message: ----------- Added HTree support for lookupFirst() and lookupAll(), insert() with split of the bucket (but not yet introduction of a new directory level), and contains() along with basic unit tests for the same. Next steps will be: 1. A split of a bucket page consisting of a single buddy bucket (globalDepth==localDepth). This requires the introduction of a new directory page. 2. Remove. 3. More detailed tests, stress tests, etc. Note that the data structure does not yet support persistence. I am working to get the core logic for the HTree maintence correct and tested first. I can then bring in the additional infrastructure to support incremental writes, checkpoint records, etc. Much of this is already stubbed out, but not yet implemented. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTreeTupleCursor.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractTuple.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/PO.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/AbstractHTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTreeUtil.java branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/MutableDirectoryPageData.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTreeTupleCursor.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTreeTupleCursor.java 2011-05-10 16:25:02 UTC (rev 4476) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractBTreeTupleCursor.java 2011-05-10 19:05:34 UTC (rev 4477) @@ -1492,7 +1492,7 @@ } - public Tuple<E> get(Tuple<E> tuple) { + public Tuple<E> get(final Tuple<E> tuple) { relocateLeaf(); Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java 2011-05-10 16:25:02 UTC (rev 4476) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractNode.java 2011-05-10 19:05:34 UTC (rev 4477) @@ -163,7 +163,7 @@ * Return the delegate {@link IAbstractNodeData} object. */ abstract IAbstractNodeData getDelegate(); - + public void delete() { if( deleted ) { @@ -1300,29 +1300,4 @@ */ abstract public boolean dump(Level level, PrintStream out, int height, boolean recursive); - /** - * Returns a string that may be used to indent a dump of the nodes in - * the tree. - * - * @param height - * The height. - * - * @return A string suitable for indent at that height. - */ - protected static String indent(final int height) { - - if( height == -1 ) { - - // The height is not defined. - - return ""; - - } - - return ws.substring(0, height * 4); - - } - - private static final transient String ws = " "; - } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractTuple.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractTuple.java 2011-05-10 16:25:02 UTC (rev 4476) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/AbstractTuple.java 2011-05-10 19:05:34 UTC (rev 4477) @@ -31,6 +31,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; +import com.bigdata.btree.data.ILeafData; import com.bigdata.io.ByteArrayBuffer; import com.bigdata.io.DataInputBuffer; import com.bigdata.io.DataOutputBuffer; @@ -341,7 +342,7 @@ * and {@link ITuple#getSourceIndex()} should be implemented by this * class (or maybe add a setSourceIndex() to be more flexible). */ - public void copy(final int index, final Leaf leaf) { + public void copy(final int index, final ILeafData leaf) { nvisited++; @@ -385,8 +386,28 @@ } else { + if(!(leaf instanceof Leaf)) { + + /* + * TODO Raw record support for the HTree will + * require an API shared by Leaf and BucketPage + * which reaches back to the owning index and + * its readRawRecord() method. When doing this, + * it would be best if there were a common base + * or interface for the HTree buckets and BTree + * leaves such that no casts are required, but + * that is not essential. [The argument to the + * method used to be Leaf, not ILeafData, but + * that means that we could not invoke this + * method for a BucketPage.] + */ + + throw new UnsupportedOperationException(); + + } + // materialize from the backing store. - final ByteBuffer tmp = leaf.btree + final ByteBuffer tmp = ((Leaf)leaf).btree .readRawRecord(addr); // and copy into the value buffer. Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/PO.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/PO.java 2011-05-10 16:25:02 UTC (rev 4476) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/btree/PO.java 2011-05-10 19:05:34 UTC (rev 4477) @@ -181,4 +181,30 @@ } } + + /** + * Returns a string that may be used to indent a dump of the nodes in + * the tree. + * + * @param height + * The height. + * + * @return A string suitable for indent at that height. + */ + protected static String indent(final int height) { + + if( height == -1 ) { + + // The height is not defined. + + return ""; + + } + + return ws.substring(0, height * 4); + + } + + private static final transient String ws = " "; + } Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/AbstractHTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/AbstractHTree.java 2011-05-10 16:25:02 UTC (rev 4476) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/AbstractHTree.java 2011-05-10 19:05:34 UTC (rev 4477) @@ -1,11 +1,13 @@ package com.bigdata.htree; +import java.io.PrintStream; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.util.HashMap; +import org.apache.log4j.Level; import org.apache.log4j.Logger; import com.bigdata.Banner; @@ -29,6 +31,7 @@ import com.bigdata.cache.IHardReferenceQueue; import com.bigdata.cache.RingBuffer; import com.bigdata.htree.HTree.AbstractPage; +import com.bigdata.htree.HTree.BucketPage; import com.bigdata.htree.HTree.DirectoryPage; import com.bigdata.rawstore.IRawStore; import com.bigdata.resources.IndexManager; @@ -127,16 +130,16 @@ */ protected final int addressBits; - /** - * The #of entries in a directory bucket, which is 2^{@link #addressBits} - * (aka <code>1<<addressBits</code>). - */ - protected final int branchingFactor; +// /** +// * The #of entries in a directory bucket, which is 2^{@link #addressBits} +// * (aka <code>1<<addressBits</code>). +// */ +// protected final int branchingFactor; /** - * The root node. + * The root directory. */ - protected volatile AbstractPage root; + protected volatile DirectoryPage root; /** * Nodes (that is nodes or leaves) are added to a hard reference queue when @@ -266,7 +269,24 @@ return addressBits; } + /** + * The #of {@link DirectoryPage}s in the {@link HTree} (not buddy hash + * tables, but the pages on which they appear). + */ + abstract public int getNodeCount(); + + /** + * The #of {@link BucketPage}s in the {@link HTree} (not buddy hash buckets, + * but the pages on which they appear). + */ + abstract public int getLeafCount(); + /** + * The #of tuples in the {@link HTree}. + */ + abstract public int getEntryCount(); + + /** * @param store * The persistence store. * @param nodeFactory @@ -317,7 +337,7 @@ this.store = store; this.addressBits = addressBits; - this.branchingFactor = 1 << addressBits; +// this.branchingFactor = 1 << addressBits; // /* // * Compute the minimum #of children/values. This is the same whether @@ -474,6 +494,51 @@ } + /** + * Recursive dump of the tree. + * + * @param out + * The dump is written on this stream. + * + * @return true unless an inconsistency is detected. + */ + public boolean dump(final PrintStream out) { + + return dump(BTree.dumpLog.getEffectiveLevel(), out, false/* materialize */); + + } + + public boolean dump(final Level level, final PrintStream out, + final boolean materialize) { + + // True iff we will write out the node structure. + final boolean info = level.toInt() <= Level.INFO.toInt(); + +// final IBTreeUtilizationReport utils = getUtilization(); + + if (info) { + + out.print("addressBits=" + addressBits); + out.print(", (2^addressBits)=" + (1 << addressBits)); + out.print(", #nodes=" + getNodeCount()); + out.print(", #leaves=" + getLeafCount()); + out.print(", #entries=" + getEntryCount()); + out.println(); +// + ", nodeUtil=" +// + utils.getNodeUtilization() + "%, leafUtil=" +// + utils.getLeafUtilization() + "%, utilization=" +// + utils.getTotalUtilization() + "%" + } + + if (root != null) { + + return root.dump(level, out, 0, true, materialize); + + } else + return true; + + } + /** * <p> * This method is responsible for putting the node or leaf onto the ring @@ -494,7 +559,7 @@ * </p> * <p> * In conjunction with {@link DefaultEvictionListener}, this method - * guarentees that the reference counter for the node will reflect the #of + * guarantees that the reference counter for the node will reflect the #of * times that the node is actually present on the * {@link #writeRetentionQueue}. * </p> Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-10 16:25:02 UTC (rev 4476) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-10 19:05:34 UTC (rev 4477) @@ -27,20 +27,33 @@ package com.bigdata.htree; +import java.io.PrintStream; import java.lang.ref.Reference; +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Set; +import org.apache.log4j.Level; import org.apache.log4j.Logger; +import com.bigdata.btree.AbstractTuple; import com.bigdata.btree.BTree; import com.bigdata.btree.BytesUtil; -import com.bigdata.btree.ISimpleBTree; +import com.bigdata.btree.IRangeQuery; +import com.bigdata.btree.ITuple; +import com.bigdata.btree.ITupleIterator; +import com.bigdata.btree.ITupleSerializer; import com.bigdata.btree.Leaf; +import com.bigdata.btree.LeafTupleIterator; import com.bigdata.btree.MutableLeafData; import com.bigdata.btree.Node; import com.bigdata.btree.PO; +import com.bigdata.btree.data.DefaultLeafCoder; import com.bigdata.btree.data.IAbstractNodeData; import com.bigdata.btree.data.ILeafData; import com.bigdata.btree.raba.IRaba; +import com.bigdata.btree.raba.MutableKeyBuffer; +import com.bigdata.btree.raba.MutableValueBuffer; import com.bigdata.cache.HardReferenceQueue; import com.bigdata.htree.data.IDirectoryData; import com.bigdata.io.AbstractFixedByteArrayBuffer; @@ -64,9 +77,9 @@ * int32 keys. */ public class HTree extends AbstractHTree - implements +// implements // IIndex, - ISimpleBTree//, IAutoboxBTree, ILinearList, IBTreeStatistics, ILocalBTreeView +// ISimpleBTree//, IAutoboxBTree, ILinearList, IBTreeStatistics, ILocalBTreeView { private static final transient Logger log = Logger.getLogger(HTree.class); @@ -80,20 +93,54 @@ private final boolean deleteMarkers; private final boolean rawRecords; + /** + * The #of {@link DirectoryPage} in the {@link HTree}. This is ONE (1) for a + * new {@link HTree}. + */ + protected int nnodes; + + /** + * The #of {@link BucketPage}s in the {@link HTree}. This is one (1) for a + * new {@link HTree} (one directory page and one bucket page). + */ + protected int nleaves; + + /** + * The #of entries in the {@link HTree}. This is ZERO (0) for a new + * {@link HTree}. + */ + protected int nentries; + + final public int getNodeCount() { + + return nnodes; + + } + + final public int getLeafCount() { + + return nleaves; + + } + + final public int getEntryCount() { + + return nentries; + + } + public boolean isReadOnly() { return false; // TODO set by ctor. } - /** - * The root of the btree. This is initially a leaf until the leaf is split, - * at which point it is replaced by a node. The root is also replaced each - * time copy-on-write triggers a cascade of updates. - * <p> - * The hard reference to the root node is cleared if the index is - * {@link #close() closed}. This method automatically {@link #reopen()}s - * the index if it is closed, making it available for use. - */ - final protected AbstractPage getRoot() { + /** + * The root of the {@link HTree}. This is always a {@link DirectoryPage}. + * <p> + * The hard reference to the root node is cleared if the index is + * {@link #close() closed}. This method automatically {@link #reopen()}s the + * index if it is closed, making it available for use. + */ + final protected DirectoryPage getRoot() { // make sure that the root is defined. if (root == null) @@ -169,6 +216,7 @@ this,// the owning htree instance addressBits // the global depth of the root. ); + nnodes++; assert r.getSlotsPerBuddy() == 1; assert r.getNumBuddies() == (1 << addressBits); @@ -177,13 +225,17 @@ // Initial bucket. final BucketPage b = new BucketPage(this, 0/* globalDepth */); + nleaves++; final int nslots = r.getSlotsPerBuddy() * r.getNumBuddies(); for (int i = 0; i < nslots; i++) { - r.refs[i] = (Reference)b.self; - rdata.childAddr[i] = 0L; // TODO addr(b) plus ref of [b]. + b.parent = (Reference) r.self; // TODO who has responsibility to set the parent reference? + + r.childRefs[i] = (Reference) b.self; + + rdata.childAddr[i] = 0L; } @@ -204,49 +256,562 @@ * * TODO The hash tree will normally be used with int32 keys, and those keys * will typically be obtained from Object#hashCode(). Special methods should - * be provided for this purpose. E.g., ISimpleHTree. Custom serialization + * be provided for this purpose. E.g., ISimpleHTree. Custom serialization * and even a custom mutable leaf data record should be used with this case. + * + * TODO insert() return for htree is never old value since always adds() a + * new tuple never replaces() an existing tuple. Further, if the key and + * val are equals() then we might even define the semantics as a NOP rather + * than appending a duplicate item into the bucket (it's worth thinking on + * this further as scanning for equals is more work and sometimes we might + * want to allow fully duplicated items into the bucket.). */ - public boolean contains(byte[] key) { - // TODO Auto-generated method stub - return false; - } - - public void insert(final Object obj) { - insert(obj.hashCode(), SerializerUtil.serialize(obj)); + /** + * Convert an int32 hash code key into an <code>unsigned byte[4]</code>. + */ + private byte[] i2k(final int key) { + final byte[] buf = new byte[4]; + buf[0] = (byte) (key >>> 24); + buf[1] = (byte) (key >>> 16); + buf[2] = (byte) (key >>> 8); + buf[3] = (byte) (key >>> 0); + return buf; } - public void insert(final int key, final byte[] val) { - // TODO code path for native int32 keys. + // TODO contains with an Object clear needs to test for the Object, not just + // the key. Maybe do this as a wrapper similar to BigdataMap? + public boolean contains(final Object obj) { + //contains(obj.hashCode()); throw new UnsupportedOperationException(); } - public byte[] insert(byte[] key, byte[] value) { - // TODO Auto-generated method stub - return null; + public boolean contains(final int key) { + return contains(i2k(key)); } - public void lookup(final Object obj) { - lookup(obj.hashCode()); + /** + * Return <code>true</code> iff there is at least one tuple in the hash tree + * having the specified <i>key</i>. + * + * @param key + * The key. + * @return <code>true</code> iff the hash tree contains at least one tuple + * with that <i>key</i>. + * @throws IllegalArgumentException + * if the <i>key</i> is <code>null</code>. + * + * TODO Parallel to insert(), consider a contains() signature + * which permits testing for a specific value as well. Maybe + * in a wrapper class? + */ + public boolean contains(final byte[] key) { + if (key == null) + throw new IllegalArgumentException(); + DirectoryPage current = getRoot(); // start at the root. + int prefixLength = 0;// prefix length of the root is always zero. + int buddyOffset = 0; // buddyOffset of the root is always zero. + while (true) { + // skip prefixLength bits and then extract globalDepth bits. + final int hashBits = current.getLocalHashCode(key, prefixLength); + // find the child directory page or bucket page. + final AbstractPage child = current.getChild(hashBits, buddyOffset); + if (child.isLeaf()) { + /* + * Found the bucket page, update it. + */ + final BucketPage bucketPage = (BucketPage) child; + if(!bucketPage.contains(key, buddyOffset)) { + return false; + } + return true; + } + /* + * Recursive descent into a child directory page. We have to update + * the prefixLength and compute the offset of the buddy hash table + * within the child before descending into the child. + */ + prefixLength = prefixLength + child.globalDepth; + buddyOffset = HTreeUtil + .getBuddyOffset(hashBits, current.globalDepth, + child.globalDepth/* localDepthOfChild */); + current = (DirectoryPage) child; + } + } + +// public void lookup(final Object obj) { +// lookup(obj.hashCode()); +// } + + /** + * Return the first value for the key. + * + * @param key + * The key. + * + * @return The first value for the key -or- <code>null</code> if there are + * no tuples in the index having that key. Note that the return + * value is not diagnostic if the application allows + * <code>null</code> values into the index. + */ + public byte[] lookupFirst(final int key) { + return lookupFirst(i2k(key)); } - public void lookup(final int key) { - // TODO code path for native int32 keys. - throw new UnsupportedOperationException(); + /** + * Return the first value for the key. + * + * @param key + * The key. + * + * @return The first value for the key -or- <code>null</code> if there are + * no tuples in the index having that key. Note that the return + * value is not diagnostic if the application allows + * <code>null</code> values into the index. + */ + public byte[] lookupFirst(final byte[] key) { + if (key == null) + throw new IllegalArgumentException(); + DirectoryPage current = getRoot(); // start at the root. + int prefixLength = 0;// prefix length of the root is always zero. + int buddyOffset = 0; // buddyOffset of the root is always zero. + while (true) { + // skip prefixLength bits and then extract globalDepth bits. + final int hashBits = current.getLocalHashCode(key, prefixLength); + // find the child directory page or bucket page. + final AbstractPage child = current.getChild(hashBits, buddyOffset); + if (child.isLeaf()) { + /* + * Found the bucket page, search it for a match. + */ + final BucketPage bucketPage = (BucketPage) child; + return bucketPage.lookupFirst(key, buddyOffset); + } + /* + * Recursive descent into a child directory page. We have to update + * the prefixLength and compute the offset of the buddy hash table + * within the child before descending into the child. + */ + prefixLength = prefixLength + child.globalDepth; + buddyOffset = HTreeUtil + .getBuddyOffset(hashBits, current.globalDepth, + child.globalDepth/* localDepthOfChild */); + current = (DirectoryPage) child; + } + } + + /** + * Return an iterator which will visit each tuple in the index having the + * specified key. + * + * @param key + * The key. + * + * @return The iterator and never <code>null</code>. + */ + public ITupleIterator lookupAll(final int key) { + return lookupAll(i2k(key)); + } + + /** + * Return an iterator which will visit each tuple in the index having the + * specified key. + * + * @param key + * The key. + * + * @return The iterator and never <code>null</code>. + */ + public ITupleIterator lookupAll(final byte[] key) { + if (key == null) + throw new IllegalArgumentException(); + DirectoryPage current = getRoot(); // start at the root. + int prefixLength = 0;// prefix length of the root is always zero. + int buddyOffset = 0; // buddyOffset of the root is always zero. + while (true) { + // skip prefixLength bits and then extract globalDepth bits. + final int hashBits = current.getLocalHashCode(key, prefixLength); + // find the child directory page or bucket page. + final AbstractPage child = current.getChild(hashBits, buddyOffset); + if (child.isLeaf()) { + /* + * Found the bucket page, search it for a match. + */ + final BucketPage bucketPage = (BucketPage) child; + return bucketPage.lookupAll(key, buddyOffset); + } + /* + * Recursive descent into a child directory page. We have to update + * the prefixLength and compute the offset of the buddy hash table + * within the child before descending into the child. + */ + prefixLength = prefixLength + child.globalDepth; + buddyOffset = HTreeUtil + .getBuddyOffset(hashBits, current.globalDepth, + child.globalDepth/* localDepthOfChild */); + current = (DirectoryPage) child; + } + } + + public void insert(final Object obj) { + insert(obj.hashCode(), SerializerUtil.serialize(obj)); } - public byte[] lookup(byte[] key) { - // TODO Return 1st match or null iff not found. - return null; + public void insert(final int key, final byte[] val) { + insert(i2k(key), val); } - public byte[] remove(byte[] key) { + /** + * Insert a tuple into the hash tree. Tuples with duplicate keys and even + * tuples with duplicate keys and values are allowed and will result in + * multiple tuples. + * + * @param key + * The key. + * @param value + * The value. + * @return <code>null</code> (always). + * + * TODO If the application wants to restrict the hash tree to such + * that it does not contain duplicate tuples then it must first + * search in the tree for an exact match (key and value). It is + * easier to do that from within the insert logic so expand the + * method signature to pass an insert enum {ALLDUPS,DUPKEYS,NODUPS}. + */ + public byte[] insert(final byte[] key, final byte[] value) { + if (key == null) + throw new IllegalArgumentException(); + DirectoryPage current = getRoot(); // start at the root. + int prefixLength = 0;// prefix length of the root is always zero. + int buddyOffset = 0; // buddyOffset of the root is always zero. + while (true) { + // skip prefixLength bits and then extract globalDepth bits. + final int hashBits = current.getLocalHashCode(key, prefixLength); + // find the child directory page or bucket page. + final AbstractPage child = current.getChild(hashBits, buddyOffset); + if (child.isLeaf()) { + /* + * Found the bucket page, update it. + */ + final BucketPage bucketPage = (BucketPage) child; + // attempt to insert the tuple into the bucket. + if(!bucketPage.insert(key, value, current, buddyOffset)) { + + // TODO if(parent.isReadOnly()) parent = copyOnWrite(); + + if (current.globalDepth == child.globalDepth) { + + /* + * There is only one buddy hash bucket on the page. To + * split the page, we have to introduce a new directory + * page above it. + * + * TODO Introduce new directory page if sole buddy + * bucket is full. + */ + + throw new UnsupportedOperationException(); + + } + + // globalDepth >= localDepth + splitBucketsOnPage(current, buddyOffset, bucketPage); + + // Try again. The children have changed. + continue; + } + return null; // TODO should be Void return? or depends on enum controlling dups behavior? + } + /* + * Recursive descent into a child directory page. We have to update + * the prefixLength and compute the offset of the buddy hash table + * within the child before descending into the child. + */ + prefixLength = prefixLength + child.globalDepth; + buddyOffset = HTreeUtil + .getBuddyOffset(hashBits, current.globalDepth, + child.globalDepth/* localDepthOfChild */); + current = (DirectoryPage) child; + } + } + + public byte[] remove(final byte[] key) { // TODO Remove 1st match, returning value. - return null; + throw new UnsupportedOperationException(); } /** + * Handle split if buddy bucket is full but localDepth LT globalDepth (so + * there is more than one buddy bucket on the page). This will allocate a + * new bucket page; update the references in the parent, and then + * redistribute buddy buckets among the old and new bucket page. The depth + * of the child will be increased by one. As a post-condition, the depth of + * the new child will be the same as the then current depth of the original + * child. Note that this doubles the size of each buddy bucket, thus always + * creating room for additional tuples. + * + * @param parent + * The parent {@link DirectoryPage}. + * @param buddyOffset + * The buddyOffset within the <i>parent</i>. This identifies + * which buddy hash table in the parent must be its pointers + * updated such that it points to both the original child and new + * child. + * @param oldBucket + * The child {@link BucketPage}. + * + * @throws IllegalArgumentException + * if any argument is <code>null</code>. + * @throws IllegalStateException + * if the depth of the child is GTE the depth of the parent. + * @throws IllegalStateException + * if the <i>parent<i/> is read-only. + * @throws IllegalStateException + * if the <i>oldBucket</i> is read-only. + * @throws IllegalStateException + * if the parent of the <oldBucket</i> is not the given + * <i>parent</i>. + */ + private void splitBucketsOnPage(final DirectoryPage parent, + final int buddyOffset, final BucketPage oldBucket) { + + if (parent == null) + throw new IllegalArgumentException(); + if (oldBucket == null) + throw new IllegalArgumentException(); + if (oldBucket.globalDepth >= parent.globalDepth) { + // In this case we have to introduce a new directory page instead. + throw new IllegalStateException(); + } + if (buddyOffset < 0) + throw new IllegalArgumentException(); + if (buddyOffset >= (1 << addressBits)) { + /* + * Note: This check is against the maximum possible slot index. The + * actual max buddyOffset depends on parent.globalBits also since + * (1<<parent.globalBits) gives the #of slots per buddy and the + * allowable buddyOffset values must fall on an buddy hash table + * boundary. + */ + throw new IllegalArgumentException(); + } + if(parent.isReadOnly()) // must be mutable. + throw new IllegalStateException(); + if(oldBucket.isReadOnly()) // must be mutable. + throw new IllegalStateException(); + if (oldBucket.parent != parent.self) // must be same Reference. + throw new IllegalStateException(); + + final int oldDepth = oldBucket.globalDepth; + final int newDepth = oldDepth + 1; + + // Allocate a new bucket page (globalDepth is increased by one). + final BucketPage newBucket = new BucketPage(this, oldBucket.globalDepth + 1); + + assert newBucket.isDirty(); + + // Set the parent reference on the new bucket. + newBucket.parent = (Reference) parent.self; + + // Increase global depth on the old page also. + oldBucket.globalDepth++; + + nleaves++; // One more bucket page in the hash tree. + + // #of slots on the bucket page (invariant given addressBits). + final int slotsOnPage = (1 << addressBits); + + /* + * Update pointers in buddy hash table in the parent. + * + * Note: The upper 1/2 of the pointers in the buddy hash table in the + * parent are rewritten to point to the new child. + */ + { + + // #of address slots in the parent buddy hash table. + final int slotsPerBuddy = (1 << parent.globalDepth); + + // Must be at least two slots per buddy for us to redistribute + // pointers. + assert slotsPerBuddy > 1 : "slotsPerBuddy=" + slotsPerBuddy; + + // The first slot in the upper 1/2 of the buddy hash table. + final int firstSlot = buddyOffset + (slotsPerBuddy >> 1); + + // The last slot in the buddy hash table. + final int lastSlot = buddyOffset + slotsPerBuddy; + + for (int i = firstSlot; i < lastSlot; i++) { + + // update the references to the new bucket. + parent.childRefs[i] = (Reference) newBucket.self; + + // clear address since newBucket is dirty. + ((MutableDirectoryPageData) parent.data).childAddr[i] = 0L; + + } + + } + + /* + * Redistribute the buddy buckets. + * + * Note: We are not changing the #of buddy buckets, just their size and + * the page on which they are found. Any tuples in a source bucket will + * wind up in the same bucket afterwards, but the page and offset on the + * page of the buddy bucket may have been changed. + * + * TODO So we could proceed backwards, moving the upper half of the + * buddy buckets to the new bucket page first and then spreading out the + * lower half of the source page among the new bucket boundaries on the + * page. Again, we have to move backwards through the buddy buckets on + * the source page to avoid overwrites of data which has not yet been + * copied. + */ + { + + // #of address slots in each old buddy hash bucket. + final int slotsPerOldBuddy = (1 << oldDepth); + + // #of address slots in each new buddy hash bucket. + final int slotsPerNewBuddy = (1 << newDepth); + + // #of buddy tables on the old bucket page. + final int oldBuddyCount = (slotsOnPage) / slotsPerOldBuddy; + + // #of buddy tables on the bucket pages after the split. + final int newBuddyCount = (slotsOnPage) / slotsPerNewBuddy; + + final BucketPage srcPage = oldBucket; + final MutableKeyBuffer srcKeys = (MutableKeyBuffer)oldBucket.getKeys(); + final MutableValueBuffer srcVals = (MutableValueBuffer)oldBucket.getValues(); + + /* + * Move top 1/2 of the buddy buckets from the child to the new page. + */ + { + + // target is the new page. + final BucketPage dstPage = newBucket; + final MutableKeyBuffer dstKeys = (MutableKeyBuffer)dstPage.getKeys(); + final MutableValueBuffer dstVals = (MutableValueBuffer)dstPage.getValues(); + + // index (vs offset) of first buddy in upper half of src page. + final int firstSrcBuddyIndex = (oldBuddyCount >> 1); + + // exclusive upper bound for index (vs offset) of last buddy in + // upper half of src page. + final int lastSrcBuddyIndex = oldBuddyCount; + + // exclusive upper bound for index (vs offset) of last buddy in + // upper half of target page. + final int lastDstBuddyIndex = newBuddyCount; + + // work backwards over buddy buckets to avoid stomping data! + for (int srcBuddyIndex = lastSrcBuddyIndex - 1, dstBuddyIndex = lastDstBuddyIndex - 1; // + srcBuddyIndex >= firstSrcBuddyIndex; // + srcBuddyIndex--, dstBuddyIndex--// + ) { + + final int firstSrcSlot = srcBuddyIndex * slotsPerOldBuddy; + + final int lastSrcSlot = (srcBuddyIndex + 1) + * slotsPerOldBuddy; + + final int firstDstSlot = dstBuddyIndex * slotsPerNewBuddy; + + for (int srcSlot = firstSrcSlot, dstSlot = firstDstSlot; srcSlot < lastSrcSlot; srcSlot++, dstSlot++) { + + if (log.isTraceEnabled()) + log.trace("moving: page(" + srcPage.toShortString() + + "=>" + dstPage.toShortString() + ")" + + ", buddyIndex(" + srcBuddyIndex + "=>" + + dstBuddyIndex + ")" + ", slot(" + srcSlot + + "=>" + dstSlot + ")"); + + // Move the tuple at that slot TODO move metadata also. + dstKeys.keys[dstSlot] = srcKeys.keys[srcSlot]; + dstVals.values[dstSlot] = srcVals.values[srcSlot]; + srcKeys.keys[srcSlot] = null; + srcVals.values[srcSlot] = null; + + } + + } + + } + + /* + * Reposition the bottom 1/2 of the buddy buckets on the old page. + */ + { + + // target is the old page. + final BucketPage dstPage = oldBucket; + final MutableKeyBuffer dstKeys = (MutableKeyBuffer)dstPage.getKeys(); + final MutableValueBuffer dstVals = (MutableValueBuffer)dstPage.getValues(); + + // index (vs offset) of first buddy in lower half of src page. + final int firstSrcBuddyIndex = 0; + + // exclusive upper bound for index (vs offset) of last buddy in + // lower half of src page. + final int lastSrcBuddyIndex = (oldBuddyCount >> 1); + + // exclusive upper bound for index (vs offset) of last buddy in + // upper half of target page (which is also the source page). + final int lastDstBuddyIndex = newBuddyCount; + + /* + * Work backwards over buddy buckets to avoid stomping data! + * + * Note: The slots for first buddy in the lower half of the + * source page DO NOT MOVE. The offset of that buddy in the + * first page remains unchanged. Only the size of the buddy is + * changed (it is doubled, just like all the other buddies on + * the page). + */ + for (int srcBuddyIndex = lastSrcBuddyIndex - 1, dstBuddyIndex = lastDstBuddyIndex - 1; // + srcBuddyIndex > firstSrcBuddyIndex; // + srcBuddyIndex--, dstBuddyIndex--// + ) { + + final int firstSrcSlot = srcBuddyIndex * slotsPerOldBuddy; + + final int lastSrcSlot = (srcBuddyIndex + 1) + * slotsPerOldBuddy; + + final int firstDstSlot = dstBuddyIndex * slotsPerNewBuddy; + + for (int srcSlot = firstSrcSlot, dstSlot = firstDstSlot; srcSlot < lastSrcSlot; srcSlot++, dstSlot++) { + + if (log.isTraceEnabled()) + log.trace("moving: page(" + srcPage.toShortString() + + "=>" + dstPage.toShortString() + ")" + + ", buddyIndex(" + srcBuddyIndex + "=>" + + dstBuddyIndex + ")" + ", slot(" + srcSlot + + "=>" + dstSlot + ")"); + + // Move the tuple at that slot TODO move metadata also. + dstKeys.keys[dstSlot] = srcKeys.keys[srcSlot]; + dstVals.values[dstSlot] = srcVals.values[srcSlot]; + srcKeys.keys[srcSlot] = null; + srcVals.values[srcSlot] = null; + + } + + } + + } + + } + + // TODO assert invariants? + + } + + /** * Persistence capable abstract base class for HTree pages. * * @author thompsonbry @@ -377,8 +942,9 @@ /** * Return the bits from the key which are relevant to the current - * directory page. This depends on the <i>prefixLength</i> to be - * ignored, the <i>globalDepth</i> of this directory page, and the key. + * directory page (varient for unsigned byte[] keys). This depends on + * the <i>prefixLength</i> to be ignored, the <i>globalDepth</i> of this + * directory page, and the key. * * @param key * The key. @@ -387,17 +953,41 @@ * this level of the hash tree. This is computed dynamically * during recursive traversal of the hash tree. This is ZERO * (0) for the root directory. It is incremented by - * <i>globalDepth</i> at each level of recursion for insert, - * lookup, etc. + * <i>globalDepth</i> (the #of address bits used by a given + * node) at each level of recursion for insert, lookup, etc. * * @return The int32 value containing the relevant bits from the key. */ public int getLocalHashCode(final byte[] key, final int prefixLength) { - + return BytesUtil.getBits(key, prefixLength, globalDepth); } + /** + * Return the bits from the key which are relevant to the current + * directory page (variant for int32 keys). This depends on the + * <i>prefixLength</i> to be ignored, the <i>globalDepth</i> of this + * directory page, and the key. + * + * @param key + * The key. + * @param prefixLength + * The #of MSB bits in the key which are to be ignored at + * this level of the hash tree. This is computed dynamically + * during recursive traversal of the hash tree. This is ZERO + * (0) for the root directory. It is incremented by + * <i>globalDepth</i> (the #of address bits used by a given + * node) at each level of recursion for insert, lookup, etc. + * + * @return The int32 value containing the relevant bits from the key. + */ + public int getLocalHashCode(final int key, final int prefixLength) { + + return BytesUtil.getBits(key, prefixLength, globalDepth); + + } + /** * Disallowed. */ @@ -517,6 +1107,29 @@ } + /** + * Dump the data onto the {@link PrintStream}. + * + * @param level + * The logging level. + * @param out + * Where to write the dump. + * @param height + * The height of this node in the tree or -1 iff you need to + * invoke this method on a node or leaf whose height in the + * tree is not known. + * @param recursive + * When true, the node will be dumped recursively using a + * pre-order traversal. + * @param materialize + * When <code>true</code>, children will be materialized as + * necessary to dump the tree. + * + * @return <code>true</code> unless an inconsistency was detected. + */ + abstract protected boolean dump(Level level, PrintStream out, + int height, boolean recursive, boolean materialize); + } // class AbstractPage /** @@ -563,10 +1176,20 @@ * reaches this condition of containing only duplicate keys we could of * course compress the keys enormously since they are all the same. * <p> - * While the #of tuples in a buddy is governed by the global depth of the - * bucket, the #of tuples in a bucket page consisting of a single buddy - * bucket might be governed by the target page size. (Perhaps we could even - * split buddy buckets if the page size is exceeded?) + * This works well when we only store the address of the objects in the + * bucket (rather than the objects themselves, e.g., raw records mode) and + * choose the address bits based on the expected size of a tuple record. + * However, we can also accommodate tuples with varying size (think binding + * sets) with in page locality if split the buddy bucket with the most bytes + * when an insert into the page would exceed the target page size. This + * looks pretty much like the case above, except that we split buddy buckets + * based on not only whether their alloted #of slots are filled by tuples + * but also based on the data on the page. + * + * TODO Delete markers will also require some thought. Unless we can purge + * them out at the tx commit point, we can wind up with a full bucket page + * consisting of a single buddy bucket filled with deleted tuples all having + * the same key. */ static class BucketPage extends AbstractPage implements ILeafData { // TODO IBucketData @@ -681,71 +1304,399 @@ super(htree, true/* dirty */, globalDepth); - data = new MutableLeafData(htree.branchingFactor, - htree.versionTimestamps, htree.deleteMarkers, - htree.rawRecords); + // TODO keysRaba must allow nulls! + data = new MutableLeafData(// + (1<<htree.addressBits), // fan-out + htree.versionTimestamps,// + htree.deleteMarkers,// + htree.rawRecords// + ); // new MutableBucketData(data) } - } // class BucketPage + /** + * Return <code>true</code> if there is at lease one tuple in the buddy + * hash bucket for the specified key. + * + * @param key + * The key. + * @param buddyOffset + * The offset within the {@link BucketPage} of the buddy hash + * bucket to be searched. + * + * @return <code>true</code> if a tuple is found in the buddy hash + * bucket for the specified key. + */ + boolean contains(final byte[] key, final int buddyOffset) { - /** - * An {@link HTree} directory page (node). Each directory page will hold one - * or more "buddy" hash tables. The #of buddy hash tables on the page - * depends on the globalDepth of the page and the addressBits as computed by - * {@link DirectoryPage#getNumBuddies()}. - */ - static class DirectoryPage extends AbstractPage implements IDirectoryData { + if (key == null) + throw new IllegalArgumentException(); - /** - * Transient references to the children. - */ - final Reference<AbstractPage>[] refs; - + // #of slots on the page. + final int slotsOnPage = (1 << htree.addressBits); + + // #of address slots in each buddy hash table. + final int slotsPerBuddy = (1 << globalDepth); + +// // #of buddy tables on a page. +// final int nbuddies = (slotsOnPage) / slotsPerBuddy; + + final int lastSlot = buddyOffset + slotsPerBuddy; + + // range check buddyOffset. + if (buddyOffset < 0 || buddyOffset >= slotsOnPage) + throw new IndexOutOfBoundsException(); + + /* + * Locate the first unassigned tuple in the buddy bucket. + * + * TODO Faster comparison with a coded key in the raba by either (a) + * asking the raba to do the equals() test; or (b) copying the key + * from the raba into a buffer which we reuse for each test. This is + * another way in which the hash table keys raba differs from the + * btree keys raba. + */ + final IRaba keys = getKeys(); + for (int i = buddyOffset; i < lastSlot; i++) { + if (!keys.isNull(i)) { + if(BytesUtil.bytesEqual(key,keys.get(i))) { + return true; + } + } + } + return false; + } + /** - * Persistent data. + * Return the first value found in the buddy hash bucket for the + * specified key. + * + * @param key + * The key. + * @param buddyOffset + * The offset within the {@link BucketPage} of the buddy hash + * bucket to be searched. + * + * @return The value associated with the first tuple found in the buddy + * hash bucket for the specified key and <code>null</code> if no + * such tuple was found. Note that the return value is not + * diagnostic if the application allows <code>null</code> values + * into the index. */ - IDirectoryData data; + final byte[] lookupFirst(final byte[] key, final int buddyOffset) { + if (key == null) + throw new IllegalArgumentException(); + + // #of slots on the page. + final int slotsOnPage = (1 << htree.addressBits); + + // #of address slots in each buddy hash table. + final int slotsPerBuddy = (1 << globalDepth); + +// // #of buddy tables on a page. +// final int nbuddies = (slotsOnPage) / slotsPerBuddy; + + final int lastSlot = buddyOffset + slotsPerBuddy; + + // range check buddyOffset. + if (buddyOffset < 0 || buddyOffset >= slotsOnPage) + throw new IndexOutOfBoundsException(); + + /* + * Locate the first unassigned tuple in the buddy bucket. + * + * TODO Faster comparison with a coded key in the raba by either (a) + * asking the raba to do the equals() test; or (b) copying the key + * from the raba into a buffer which we reuse for each test. This is + * another way in which the hash table keys raba differs from the + * btree keys raba. + */ + final IRaba keys = getKeys(); + for (int i = buddyOffset; i < lastSlot; i++) { + if (!keys.isNull(i)) { + if(BytesUtil.bytesEqual(key,keys.get(i))) { + return getValues().get(i); + } + } + } + return null; + } + /** - * Get the child indexed by the key. - * <p> - * Note: The recursive descent pattern requires the caller to separately - * compute the buddyOffset before each descent into a child. + * Return an iterator which will visit each tuple in the buddy hash + * bucket for the specified key. * * @param key * The key. - * @param prefixLength - * The #of MSB bits in the key which are to be ignored at - * this level of the hash tree. This is computed dynamically - * during recursive traversal of the hash tree. This is ZERO - * (0) for the root directory. It is incremented by - * <i>globalDepth</i> at each level of recursion for insert, - * lookup, etc. * @param buddyOffset + * The offset within the {@link BucketPage} of the buddy hash + * bucket to be searched. + * + * @return An iterator which will visit each tuple in the buddy hash + * table for the specified key and never <code>null</code>. + * + * TODO Specify the contract for concurrent modification both + * here and on the {@link HTree#lookupAll(byte[])} methods. + */ + final ITupleIterator lookupAll(final byte[] key, final int buddyOffset) { + + return new BuddyBucketTupleIterator(key, this, buddyOffset); + + } + + /** + * Insert the tuple into the buddy bucket. + * + * @param key + * The key (all bits, all bytes). + * @param val + * The value (optional). + * @param parent + * The parent {@link DirectoryPage} and never + * <code>null</code> (this is required for the copy-on-write + * pattern). + * @param buddyOffset * The offset into the child of the first slot for the buddy * hash table or buddy hash bucket. * - * @return The child indexed by the key. + * @return <code>false</code> iff the buddy bucket must be split. * - * @see HTreeUtil#getBuddyOffset(int, int, int) - * - * TODO This method signature would require us to invoke - * {@link #getLocalHashCode(byte[], int)} twice (once here and once - * in support of obtaining the buddyOffset). That suggests that the - * caller should invoke the method once and then use the - * int32:hashBits variant and we would then drop this version of - * the method. + * @throws IllegalArgumentException + * if <i>key</i> is <code>null</code>. + * @throws IllegalArgumentException + * if <i>parent</i> is <code>null</code>. + * @throws IndexOutOfBoundsException + * if <i>buddyOffset</i> is out of the allowed range. */ - protected AbstractPage getChild(final byte[] key, - final int prefixLength, final int buddyOffset) { + boolean insert(final byte[] key, final byte[] val, + final DirectoryPage parent, + final int buddyOffset) { - return getChild(getLocalHashCode(key, prefixLength), buddyOffset); + if (key == null) + throw new IllegalArgumentException(); + if (parent == null) + throw new IllegalArgumentException(); + + // #of slots on the page. + final int slotsOnPage = (1 << htree.addressBits); + + // #of address slots in each buddy hash table. + final int slotsPerBuddy = (1 << globalDepth); + + // #of buddy tables on a page. + final int nbuddies = (slotsOnPage) / slotsPerBuddy; + + final int lastSlot = buddyOffset + slotsPerBuddy; + + // range check buddyOffset. + if (buddyOffset < 0 || buddyOffset >= slotsOnPage) + throw new IndexOutOfBoundsException(); + + // TODO if(!mutable) copyOnWrite().insert(key,val,parent,buddyOffset); + + /* + * Locate the first unassigned tuple in the buddy bucket. + * + * Note: Given the IRaba data structure, this will require us to + * examine the keys for a null. The "keys" rabas do not allow nulls, + * so we will need to use a "values" raba (nulls allowed) for the + * bucket keys. Unless we keep the entries in a buddy bucket dense + * (maybe making them dense when they are persisted for faster + * scans, but why bother for mutable buckets?) we will have to scan + * the entire buddy bucket to find an open slot (or just to count + * the #of slots which are currently in use). + * + * FIXME We need a modified MutableKeysRaba for this purpose. It + * will have to allow nulls in the middle (unless we compact + * everything). [We will have to compact prior to coding in order + * for the assumptions of the values coder to be maintained.] It + * would be pretty easy to just swap in the first undeleted tuple + * but that will not keep things dense. Likewise, Monet style + * cracking could only be done within a buddy bucket rather than + * across all buddies on a page. + */ + final MutableKeyBuffer keys = (MutableKeyBuffer)getKeys(); + final MutableValueBuffer vals = (MutableValueBuffer)getValues(); + + for (int i = buddyOffset; i < lastSlot; i++) { + if (keys.isNull(i)) { + keys.nkeys++; + keys.keys[i] = key; + vals.nvalues++; + vals.values[i] = val; + // TODO deleteMarker:=false + // TODO versionTimestamp:=... + ((HTree)htree).nentries++; + // insert Ok. + return true; + } + } + + /* + * Any buddy bucket which is full is split unless it is the sole + * buddy in the page since a split doubles the size of the buddy + * bucket (unless it is the only buddy on the page) and the tuple + * can therefore be inserted after a split. [This rule is not + * perfect if we allow splits to be driven by the bytes on a page, + * but it should still be Ok.] + * + * Before we can split the sole buddy bucket in a page, we need to + * know whether or not the keys are identical. If they are then we + * let the page grow rather than splitting it. This can be handled + * insert of bucketPage.insert(). It can have a boolean which is set + * false as soon as it sees a key which is not the equals() to the + * probe key (in all bits). + * + * Note that an allowed split always leaves enough room for another + * tuple (when considering only the #of tuples and not their bytes + * on the page). We can still be "out of space" in terms of bytes on + * the page, even for a single tuple. In this edge case, the tuple + * should really be a raw record. That is easily controlled by + * having a maximum inline value byte[] length for a page - probably + * on the order of pageSize/16 which works out to 256 bytes for a 4k + * page. + */ + if (nbuddies != 1) { + /* + * Force a split since there is more than one buddy on the page. + */ + return false; + } + + /* + * There is only one buddy on the page. Now we have to figure out + * whether or not all keys are duplicates. + */ + boolean identicalKeys = true; + for (int i = buddyOffset; i < buddyOffset + slotsPerBuddy; i++) { + if(!BytesUtil.bytesEqual(key,keys.get(i))) { + identicalKeys = false; + break; + } + } + if(!identicalKeys) { + /* + * Force a split since it is possible to redistribute some + * tuples. + */ + return false; + } + + /* + * Since the page is full, we need to grow the page (or chain an + * overflow page) rather than splitting the page. + * + * TODO Maybe the easiest thing to do is just double the target #of + * slots on the page. We would rely on keys.capacity() in this case + * rather than #slots. In fact, we could just reenter the method + * above after doubling as long as we rely on keys.capacity() in the + * case where nbuddies==1. + */ + throw new UnsupportedOperationException(); + } + + /** + * Human readable representation of the {@link ILeafData} plus transient + * information associated with the {@link BucketPage}. + */ + @Override + public String toString() { + + final StringBuilder sb = new StringBuilder(); + + sb.append(super.toString()); + + sb.append("{ isDirty="+isDirty()); + + sb.append(", isDeleted="+isDeleted()); + + sb.append(", addr=" + identity); + + final DirectoryPage p = (parent == null ? null : parent.get()); + + sb.append(", parent=" + (p == null ? "N/A" : p.toShortString())); + + sb.append(", globalDepth=" + getGlobalDepth()); + + if (data == null) { + + // No data record? (Generally, this means it was stolen by copy on + // write). + sb.append(", data=NA}"); + + return sb.toString(); + + } + + sb.append(", nkeys=" + getKeyCount()); + +// sb.append(", minKeys=" + minKeys()); +// +// sb.append(", maxKeys=" + maxKeys()); + + DefaultLeafCoder.toString(this, sb); + + sb.append("}"); + + return sb.toString(); + + } + + protected boolean dump(final Level level, final PrintStream out, + final int height, final boolean recursive, + final boolean materialize) { + + final boolean debug = level.toInt() <= Level.DEBUG.toInt(); + + // Set to false iff an inconsistency is detected. + boolean ok = true; + + final int globalDepth = getGlobalDepth(); + + if (parent == null || parent.get() == null) { + out.println(indent(height) + "ERROR: parent not set"); + ok = false; + } + + if (globalDepth > parent.get().globalDepth) { + out.println(indent(height) + "ERROR: localDepth exceeds globalDepth of parent"); + ok = false; + } + + if (debug || ! ok ) { + + out.println(indent(height) + toString()); + + } + + return ok; + } + } // class BucketPage + + /** + * An {@link HTree} directory page (node). Each directory page will hold one + * or more "buddy" hash tables. The #of buddy hash tables on the page + * depends on the globalDepth of the page and the addressBits as computed by + * {@link DirectoryPage#getNumBuddies()}. + */ + static class DirectoryPage extends AbstractPage implements IDirectoryData { + + /** + * Transient references to the children. + */ + final Reference<AbstractPage>[] childRefs; + /** + * Persistent data. + */ + IDirectoryData data; + + /** * Get the child indexed by the key. * <p> * Note: The recursive descent pattern requires the caller to separately @@ -780,7 +1731,7 @@ * then we are done and we can return the child reference and the * offset of the buddy table or bucket within the child. */ - final Reference<AbstractPage> ref = refs[index]; + final Reference<AbstractPage> ref = childRefs[index]; AbstractPage child = ref == null ? null : ref.get(); @@ -847,7 +1798,7 @@ if (data.getChildAddr(i) == addr) { - refs[i] = (Reference) child.self; + childRefs[i] = (Reference) child.self; n++; @@ -862,112 +1813,7 @@ return child; } - -// /** -// * Return the address of the child page indexed by the relevant bits of -// * the key. -// * -// * @param hashBits -// * An int32 extract revealing only the relevant bits of the -// * key for the current {@link DirectoryPage}. -// * @param offset -// * Index position of the start of the buddy hash table in the -// * page. This is known to the caller when inspecting the -// * parent directory and is the index of the slot within the -// * buddy hash table. -// * -// * @return The address of the child. -// */ -// public long getChildAddrByHashCode(final int hashBits, final int offset) { -// // width of a buddy hash table in pointer slots. -// final int tableWidth = (1 << globalDepth); -// // index position of the start of the buddy hash table in the page. -// final int tableOffset = (tableWidth * offset); -// // index of the slot in the buddy hash table for the given hash -// // bits. -// final int index = tableOffset + hashBits; -// // the address of the child for that slot. -// final long addr = data.getChildAddr(index); -// return addr; -// } - -// /** -// * The #of bits in the key examined at a given level in the tree. The -// * range is [0:addressBits]. -// * <p> -// * This depends solely on the <i>globalDepth</i> of a directory page and -// * the #of pointers to child (<i>npointers</i>) in that directory page. -// * The local depth is obtained counting the #of slots in the appropriate -// * buddy hash table of the parent which are mapped onto the same child. -// * Given 2^(d-i) such entries in the parent, the local depth of that -// * child is i. Some fancy bit work may be used to solve for i. -// * <p> -// * The local depth of a node may not exceed the global depth of its -// * parent. If splitting a buddy bucket would cause the local depth to -// * exceed the global depth of the parent, then the global depth of the -// * parent is increased, which causes the parent to be split in turn. -// * Since the root directory does not have a parent its local depth is -// * not defined. -// * -// * @param child -// * A reference to a direct child of this -// * {@link DirectoryPage}. -// * -// * @throws IllegalArgumentException -// * if the <i>child</i> is <code>null</code> -// * @throws IllegalArgumentException -// * if the <i>child</i> is not a direct child of this -// * {@link DirectoryPage}. -// */ -// int getLocalDepth(final AbstractPage child) { -// -// final int npointers = co... [truncated message content] |
From: <tho...@us...> - 2011-05-11 14:26:41
|
Revision: 4482 http://bigdata.svn.sourceforge.net/bigdata/?rev=4482&view=rev Author: thompsonbry Date: 2011-05-11 14:26:34 +0000 (Wed, 11 May 2011) Log Message: ----------- Bug fix to the update of the pointers in the parent directory page's buddy hash table when a child is split. Update to the worksheet for this unit test of the htree split rule. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/htree.xls branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/htree.xls =================================================================== (Binary files differ) Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-11 03:20:08 UTC (rev 4481) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-11 14:26:34 UTC (rev 4482) @@ -602,11 +602,15 @@ if (oldBucket.parent != parent.self) // must be same Reference. throw new IllegalStateException(); + if (log.isDebugEnabled()) + log.debug("parent=" + parent.toShortString() + ", buddyOffset=" + + buddyOffset + ", child=" + oldBucket); + final int oldDepth = oldBucket.globalDepth; final int newDepth = oldDepth + 1; // Allocate a new bucket page (globalDepth is increased by one). - final BucketPage newBucket = new BucketPage(this, oldBucket.globalDepth + 1); + final BucketPage newBucket = new BucketPage(this, newDepth); assert newBucket.isDirty(); @@ -614,201 +618,256 @@ newBucket.parent = (Reference) parent.self; // Increase global depth on the old page also. - oldBucket.globalDepth++; + oldBucket.globalDepth = newDepth; nleaves++; // One more bucket page in the hash tree. - // #of slots on the bucket page (invariant given addressBits). - final int slotsOnPage = (1 << addressBits); - /* * Update pointers in buddy hash table in the parent. * - * Note: The upper 1/2 of the pointers in the buddy hash table in the - * parent are rewritten to point to the new child. + * There will be [npointers] slots in the appropriate buddy hash table + * in the parent directory which point to the old bucket page. The upper + * 1/2 of those pointers will be modified to point to the new bucket + * page. The lower 1/2 of the pointers will be unchanged. */ { // #of address slots in the parent buddy hash table. final int slotsPerBuddy = (1 << parent.globalDepth); - // Must be at least two slots per buddy for us to redistribute - // pointers. + // #of pointers in the parent buddy hash table to the old bucket. + final int npointers = 1 << (parent.globalDepth - oldDepth); + + // Must be at least two slots since we will change at least one. assert slotsPerBuddy > 1 : "slotsPerBuddy=" + slotsPerBuddy; + + // Must be at least two pointers since we will change at least one. + assert npointers > 1 : "npointers=" + npointers; - // The first slot in the upper 1/2 of the buddy hash table. - final int firstSlot = buddyOffset + (slotsPerBuddy >> 1); + // The first slot in the buddy hash table in the parent. + final int firstSlot = buddyOffset; - // The last slot in the buddy hash table. + // The last slot in the buddy hash table in the parent. final int lastSlot = buddyOffset + slotsPerBuddy; - + + /* + * Count pointers to the old bucket page. There should be + * [npointers] of them and they should be contiguous. + * + * Note: We can test References here rather than comparing addresses + * because we know that the parent and the old bucket are both + * mutable. This means that their childRef is defined and their + * storage address is NULL. + * + * TODO This logic should be in DirectoryPage#dump() + */ + int firstPointer = -1; + int nfound = 0; + boolean discontiguous = false; for (int i = firstSlot; i < lastSlot; i++) { + if (parent.childRefs[i] == oldBucket.self) { + if (firstPointer == -1) + firstPointer = i; + nfound++; + if (((MutableDirectoryPageData) parent.data).childAddr[i] != IRawStore.NULL) { + throw new RuntimeException( + "Child address should be NULL since child is dirty"); + } + } else { + if (firstPointer != -1 && nfound != npointers) { + discontiguous = true; + } + } + } + if (firstPointer == -1) + throw new RuntimeException("No pointers to child"); + if (nfound != npointers) + throw new RuntimeException("Expected " + npointers + + " pointers to child, but found=" + nfound); + if (discontiguous) + throw new RuntimeException( + "Pointers to child are discontiguous in parent's buddy hash table."); + // Update the upper 1/2 of the pointers to the new bucket. + for (int i = firstPointer + (npointers >> 1); i < npointers; i++) { + + if (parent.childRefs[i] != oldBucket.self) + throw new RuntimeException("Does not point to old child."); + // update the references to the new bucket. parent.childRefs[i] = (Reference) newBucket.self; - // clear address since newBucket is dirty. - ((MutableDirectoryPageData) parent.data).childAddr[i] = 0L; - } } - /* - * Redistribute the buddy buckets. - * - * Note: We are not changing the #of buddy buckets, just their size and - * the page on which they are found. Any tuples in a source bucket will - * wind up in the same bucket afterwards, but the page and offset on the - * page of the buddy bucket may have been changed. - * - * TODO So we could proceed backwards, moving the upper half of the - * buddy buckets to the new bucket page first and then spreading out the - * lower half of the source page among the new bucket boundaries on the - * page. Again, we have to move backwards through the buddy buckets on - * the source page to avoid overwrites of data which has not yet been - * copied. - */ - { + // redistribute buddy buckets between old and new pages. + redistributeBuddyBuckets(oldDepth, newDepth, oldBucket, newBucket); - // #of address slots in each old buddy hash bucket. - final int slotsPerOldBuddy = (1 << oldDepth); + // TODO assert invariants? + + } - // #of address slots in each new buddy hash bucket. - final int slotsPerNewBuddy = (1 << newDepth); + /** + * Redistribute the buddy buckets. + * <p> + * Note: We are not changing the #of buddy buckets, just their size and + * the page on which they are found. Any tuples in a source bucket will + * wind up in the same bucket afterwards, but the page and offset on the + * page of the buddy bucket may have been changed. + * <p> + * We proceed backwards, moving the upper half of the buddy buckets to + * the new bucket page first and then spreading out the lower half of + * the source page among the new bucket boundaries on the page. + */ + private void redistributeBuddyBuckets(final int oldDepth, + final int newDepth, final BucketPage oldBucket, + final BucketPage newBucket) { - // #of buddy tables on the old bucket page. - final int oldBuddyCount = (slotsOnPage) / slotsPerOldBuddy; + // #of slots on the bucket page (invariant given addressBits). + final int slotsOnPage = (1 << addressBits); - // #of buddy tables on the bucket pages after the split. - final int newBuddyCount = (slotsOnPage) / slotsPerNewBuddy; + // #of address slots in each old buddy hash bucket. + final int slotsPerOldBuddy = (1 << oldDepth); - final BucketPage srcPage = oldBucket; - final MutableKeyBuffer srcKeys = (MutableKeyBuffer)oldBucket.getKeys(); - final MutableValueBuffer srcVals = (MutableValueBuffer)oldBucket.getValues(); + // #of address slots in each new buddy hash bucket. + final int slotsPerNewBuddy = (1 << newDepth); - /* - * Move top 1/2 of the buddy buckets from the child to the new page. - */ - { + // #of buddy tables on the old bucket page. + final int oldBuddyCount = (slotsOnPage) / slotsPerOldBuddy; - // target is the new page. - final BucketPage dstPage = newBucket; - final MutableKeyBuffer dstKeys = (MutableKeyBuffer)dstPage.getKeys(); - final MutableValueBuffer dstVals = (MutableValueBuffer)dstPage.getValues(); - - // index (vs offset) of first buddy in upper half of src page. - final int firstSrcBuddyIndex = (oldBuddyCount >> 1); + // #of buddy tables on the bucket pages after the split. + final int newBuddyCount = (slotsOnPage) / slotsPerNewBuddy; - // exclusive upper bound for index (vs offset) of last buddy in - // upper half of src page. - final int lastSrcBuddyIndex = oldBuddyCount; + final BucketPage srcPage = oldBucket; + final MutableKeyBuffer srcKeys = (MutableKeyBuffer) oldBucket.getKeys(); + final MutableValueBuffer srcVals = (MutableValueBuffer) oldBucket + .getValues(); - // exclusive upper bound for index (vs offset) of last buddy in - // upper half of target page. - final int lastDstBuddyIndex = newBuddyCount; + /* + * Move top 1/2 of the buddy buckets from the child to the new page. + */ + { - // work backwards over buddy buckets to avoid stomping data! - for (int srcBuddyIndex = lastSrcBuddyIndex - 1, dstBuddyIndex = lastDstBuddyIndex - 1; // - srcBuddyIndex >= firstSrcBuddyIndex; // - srcBuddyIndex--, dstBuddyIndex--// - ) { + // target is the new page. + final BucketPage dstPage = newBucket; + final MutableKeyBuffer dstKeys = (MutableKeyBuffer) dstPage + .getKeys(); + final MutableValueBuffer dstVals = (MutableValueBuffer) dstPage + .getValues(); - final int firstSrcSlot = srcBuddyIndex * slotsPerOldBuddy; + // index (vs offset) of first buddy in upper half of src page. + final int firstSrcBuddyIndex = (oldBuddyCount >> 1); - final int lastSrcSlot = (srcBuddyIndex + 1) - * slotsPerOldBuddy; + // exclusive upper bound for index (vs offset) of last buddy in + // upper half of src page. + final int lastSrcBuddyIndex = oldBuddyCount; - final int firstDstSlot = dstBuddyIndex * slotsPerNewBuddy; + // exclusive upper bound for index (vs offset) of last buddy in + // upper half of target page. + final int lastDstBuddyIndex = newBuddyCount; - for (int srcSlot = firstSrcSlot, dstSlot = firstDstSlot; srcSlot < lastSrcSlot; srcSlot++, dstSlot++) { + // work backwards over buddy buckets to avoid stomping data! + for (int srcBuddyIndex = lastSrcBuddyIndex - 1, dstBuddyIndex = lastDstBuddyIndex - 1; // + srcBuddyIndex >= firstSrcBuddyIndex; // + srcBuddyIndex--, dstBuddyIndex--// + ) { - if (log.isTraceEnabled()) - log.trace("moving: page(" + srcPage.toShortString() - + "=>" + dstPage.toShortString() + ")" - + ", buddyIndex(" + srcBuddyIndex + "=>" - + dstBuddyIndex + ")" + ", slot(" + srcSlot - + "=>" + dstSlot + ")"); - - // Move the tuple at that slot TODO move metadata also. - dstKeys.keys[dstSlot] = srcKeys.keys[srcSlot]; - dstVals.values[dstSlot] = srcVals.values[srcSlot]; - srcKeys.keys[srcSlot] = null; - srcVals.values[srcSlot] = null; + final int firstSrcSlot = srcBuddyIndex * slotsPerOldBuddy; - } + final int lastSrcSlot = (srcBuddyIndex + 1) * slotsPerOldBuddy; + final int firstDstSlot = dstBuddyIndex * slotsPerNewBuddy; + + for (int srcSlot = firstSrcSlot, dstSlot = firstDstSlot; srcSlot < lastSrcSlot; srcSlot++, dstSlot++) { + + if (log.isTraceEnabled()) + log.trace("moving: page(" + srcPage.toShortString() + + "=>" + dstPage.toShortString() + ")" + + ", buddyIndex(" + srcBuddyIndex + "=>" + + dstBuddyIndex + ")" + ", slot(" + srcSlot + + "=>" + dstSlot + ")"); + + // Move the tuple at that slot TODO move metadata also. + dstKeys.keys[dstSlot] = srcKeys.keys[srcSlot]; + dstVals.values[dstSlot] = srcVals.values[srcSlot]; + srcKeys.keys[srcSlot] = null; + srcVals.values[srcSlot] = null; + } - + } - /* - * Reposition the bottom 1/2 of the buddy buckets on the old page. - */ - { + } - // target is the old page. - final BucketPage dstPage = oldBucket; - final MutableKeyBuffer dstKeys = (MutableKeyBuffer)dstPage.getKeys(); - final MutableValueBuffer dstVals = (MutableValueBuffer)dstPage.getValues(); - - // index (vs offset) of first buddy in lower half of src page. - final int firstSrcBuddyIndex = 0; + /* + * Reposition the bottom 1/2 of the buddy buckets on the old page. + * + * Again, we have to move backwards through the buddy buckets on the + * source page to avoid overwrites of data which has not yet been + * copied. Also, notice that the buddy bucket at index ZERO does not + * move - it is already in place even though it's size has doubled. + */ + { - // exclusive upper bound for index (vs offset) of last buddy in - // lower half of src page. - final int lastSrcBuddyIndex = (oldBuddyCount >> 1); + // target is the old page. + final BucketPage dstPage = oldBucket; + final MutableKeyBuffer dstKeys = (MutableKeyBuffer) dstPage + .getKeys(); + final MutableValueBuffer dstVals = (MutableValueBuffer) dstPage + .getValues(); - // exclusive upper bound for index (vs offset) of last buddy in - // upper half of target page (which is also the source page). - final int lastDstBuddyIndex = newBuddyCount; + // index (vs offset) of first buddy in lower half of src page. + final int firstSrcBuddyIndex = 0; - /* - * Work backwards over buddy buckets to avoid stomping data! - * - * Note: The slots for first buddy in the lower half of the - * source page DO NOT MOVE. The offset of that buddy in the - * first page remains unchanged. Only the size of the buddy is - * changed (it is doubled, just like all the other buddies on - * the page). - */ - for (int srcBuddyIndex = lastSrcBuddyIndex - 1, dstBuddyIndex = lastDstBuddyIndex - 1; // - srcBuddyIndex > firstSrcBuddyIndex; // - srcBuddyIndex--, dstBuddyIndex--// - ) { + // exclusive upper bound for index (vs offset) of last buddy in + // lower half of src page. + final int lastSrcBuddyIndex = (oldBuddyCount >> 1); - final int firstSrcSlot = srcBuddyIndex * slotsPerOldBuddy; + // exclusive upper bound for index (vs offset) of last buddy in + // upper half of target page (which is also the source page). + final int lastDstBuddyIndex = newBuddyCount; - final int lastSrcSlot = (srcBuddyIndex + 1) - * slotsPerOldBuddy; + /* + * Work backwards over buddy buckets to avoid stomping data! + * + * Note: The slots for first buddy in the lower half of the source + * page DO NOT MOVE. The offset of that buddy in the first page + * remains unchanged. Only the size of the buddy is changed (it is + * doubled, just like all the other buddies on the page). + */ + for (int srcBuddyIndex = lastSrcBuddyIndex - 1, dstBuddyIndex = lastDstBuddyIndex - 1; // + srcBuddyIndex > firstSrcBuddyIndex; // DO NOT move 1st buddy bucket! + srcBuddyIndex--, dstBuddyIndex--// + ) { - final int firstDstSlot = dstBuddyIndex * slotsPerNewBuddy; + final int firstSrcSlot = srcBuddyIndex * slotsPerOldBuddy; - for (int srcSlot = firstSrcSlot, dstSlot = firstDstSlot; srcSlot < lastSrcSlot; srcSlot++, dstSlot++) { + final int lastSrcSlot = (srcBuddyIndex + 1) * slotsPerOldBuddy; - if (log.isTraceEnabled()) - log.trace("moving: page(" + srcPage.toShortString() - + "=>" + dstPage.toShortString() + ")" - + ", buddyIndex(" + srcBuddyIndex + "=>" - + dstBuddyIndex + ")" + ", slot(" + srcSlot - + "=>" + dstSlot + ")"); + final int firstDstSlot = dstBuddyIndex * slotsPerNewBuddy; - // Move the tuple at that slot TODO move metadata also. - dstKeys.keys[dstSlot] = srcKeys.keys[srcSlot]; - dstVals.values[dstSlot] = srcVals.values[srcSlot]; - srcKeys.keys[srcSlot] = null; - srcVals.values[srcSlot] = null; + for (int srcSlot = firstSrcSlot, dstSlot = firstDstSlot; srcSlot < lastSrcSlot; srcSlot++, dstSlot++) { - } + if (log.isTraceEnabled()) + log.trace("moving: page(" + srcPage.toShortString() + + "=>" + dstPage.toShortString() + ")" + + ", buddyIndex(" + srcBuddyIndex + "=>" + + dstBuddyIndex + ")" + ", slot(" + srcSlot + + "=>" + dstSlot + ")"); + // Move the tuple at that slot TODO move metadata also. + dstKeys.keys[dstSlot] = srcKeys.keys[srcSlot]; + dstVals.values[dstSlot] = srcVals.values[srcSlot]; + srcKeys.keys[srcSlot] = null; + srcVals.values[srcSlot] = null; + } - + } } - // TODO assert invariants? - } /** @@ -897,7 +956,7 @@ /** * The size of the address space (in bits) for each buddy hash table on - * a directory page. The legal range is <code>[0:addressBits]</code>. + * a directory page. The legal range is <code>[0:addressBits-1]</code>. * <p> * When the global depth is increased, the hash table requires twice as * many slots on the page. This forces the split of the directory page @@ -907,8 +966,9 @@ * minimum global depth is ZERO (0), at which point the buddy hash table * has a single slot. * <p> - * The global depth of a directory page is just the local depth of the - * directory page in its parent. + * The global depth of a child page is just the local depth of the + * directory page in its parent. The global depth of the child page + * is often called its <em>local depth</em>. * * TODO Since the root directory does not have a parent, its global * depth is recorded in the checkpoint record [actually, the global @@ -1959,6 +2019,20 @@ } + /* + * TODO We should dump each bucket page once. This could be done either + * by dumping each buddy bucket on the page separately or by skipping + * through the directory page until we get to the next bucket page and + * then dumping that. + * + * TODO The directory page validation should include checks on the + * bucket references and addresses. For a given buddy hash table, the + * reference and address should pairs should be consistent if either the + * reference or the address appears in another slot of that table. Also, + * there can not be "gaps" between observations of a reference to a + * given bucket - once you see another bucket reference a previously + * observed reference can not then appear. + */ @Override protected boolean dump(Level level, PrintStream out, int height, boolean recursive, boolean materialize) { Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java 2011-05-11 03:20:08 UTC (rev 4481) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java 2011-05-11 14:26:34 UTC (rev 4482) @@ -27,11 +27,13 @@ package com.bigdata.htree; +import junit.framework.TestCase2; + import org.apache.log4j.Level; -import junit.framework.TestCase2; - import com.bigdata.btree.AbstractBTreeTestCase; +import com.bigdata.htree.HTree.BucketPage; +import com.bigdata.htree.HTree.DirectoryPage; import com.bigdata.rawstore.IRawStore; import com.bigdata.rawstore.SimpleMemoryRawStore; @@ -40,6 +42,10 @@ * * @author <a href="mailto:tho...@us...">Bryan Thompson</a> * @version $Id$ + * + * TODO Unit test for the code path where the sole buddy bucket on a + * page consists solely of duplicate keys thus forcing the size of the + * bucket page to double rather than splitting the page. */ public class TestHTree extends TestCase2 { @@ -146,13 +152,14 @@ } /** - * Simple test of basic CRUD operations using an address space with only TWO - * (2) bits (insert, contains, remove, etc). + * Test of basic insert, lookup, and split page operations (including when a + * new directory page must be introduced) using an address space with only + * TWO (2) bits. * - * TODO Verify that we can store keys having more than 2 bits (or 4 bits in - * a 4-bit address space) through a deeper hash tree. + * @see bigdata/src/architecture/htree.xls * - * TODO Do a worksheet example for this test case. + * TODO Verify that we can store keys having more than 2 bits (or 4 + * bits in a 4-bit address space) through a deeper hash tree. */ public void test_example_addressBits2_01() { @@ -168,11 +175,22 @@ assertTrue("store", store == htree.getStore()); assertEquals("addressBits", addressBits, htree.getAddressBits()); + final DirectoryPage root = htree.getRoot(); + assertEquals(4, root.childRefs.length); + final BucketPage a = (BucketPage) root.childRefs[0].get(); + assertTrue(a == (BucketPage) root.childRefs[1].get()); + assertTrue(a == (BucketPage) root.childRefs[2].get()); + assertTrue(a == (BucketPage) root.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth());// starts at max. + assertEquals(0, a.getGlobalDepth());// starts at min. + // verify preconditions. assertEquals("nnodes", 1, htree.getNodeCount()); assertEquals("nleaves", 1, htree.getLeafCount()); assertEquals("nentries", 0, htree.getEntryCount()); htree.dump(Level.ALL, System.err, true/* materialize */); + assertEquals(2, root.getGlobalDepth()); + assertEquals(0, a.getGlobalDepth()); assertFalse(htree.contains(new byte[] { 0x01 })); assertFalse(htree.contains(new byte[] { 0x02 })); assertEquals(null,htree.lookupFirst(new byte[] { 0x01 })); @@ -182,12 +200,17 @@ AbstractBTreeTestCase.assertSameIterator(// new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); - // insert a tuple and verify post-conditions. + /* + * 1. Insert a tuple and verify post-conditions. The tuple goes into + * an empty buddy bucket with a capacity of one. + */ htree.insert(new byte[] { 0x01 }, new byte[] { 0x01 }); assertEquals("nnodes", 1, htree.getNodeCount()); assertEquals("nleaves", 1, htree.getLeafCount()); assertEquals("nentries", 1, htree.getEntryCount()); htree.dump(Level.ALL, System.err, true/* materialize */); + assertEquals(2, root.getGlobalDepth()); + assertEquals(0, a.getGlobalDepth()); assertTrue(htree.contains(new byte[] { 0x01 })); assertFalse(htree.contains(new byte[] { 0x02 })); assertEquals(new byte[] { 0x01 }, htree @@ -200,12 +223,13 @@ new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); /* - * Insert a duplicate key. Since the localDepth of the bucket page - * is zero, each buddy bucket on the page can only accept one entry - * and this will force a split of the buddy bucket. That means that - * a new bucket page will be allocated, the pointers in the parent - * will be updated to link in the new buck page, and the buddy - * buckets will be redistributed among the old and new bucket page. + * 2. Insert a duplicate key. Since the localDepth of the bucket + * page is zero, each buddy bucket on the page can only accept one + * entry and this will force a split of the buddy bucket. That means + * that a new bucket page will be allocated, the pointers in the + * parent will be updated to link in the new buck page, and the + * buddy buckets will be redistributed among the old and new bucket + * page. * * Note: We do not know the order of the tuples in the bucket so * lookupFirst() is difficult to test when there are tuples for the @@ -216,6 +240,16 @@ assertEquals("nleaves", 2, htree.getLeafCount()); assertEquals("nentries", 2, htree.getEntryCount()); htree.dump(Level.ALL, System.err, true/* materialize */); + assertTrue(root == htree.getRoot()); + assertEquals(4, root.childRefs.length); + final BucketPage b = (BucketPage) root.childRefs[2].get(); + assertTrue(a == (BucketPage) root.childRefs[0].get()); + assertTrue(a == (BucketPage) root.childRefs[1].get()); + assertTrue(b == (BucketPage) root.childRefs[2].get()); + assertTrue(b == (BucketPage) root.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth()); + assertEquals(1, a.getGlobalDepth());// localDepth has increased. + assertEquals(1, b.getGlobalDepth());// localDepth is same as [a]. assertTrue(htree.contains(new byte[] { 0x01 })); assertFalse(htree.contains(new byte[] { 0x02 })); assertEquals(new byte[] { 0x01 }, htree @@ -227,8 +261,43 @@ htree.lookupAll(new byte[] { 0x01 })); AbstractBTreeTestCase.assertSameIterator(// new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); + + /* + * 3. Insert another duplicate key. This forces another split. + * + * TODO Could check the #of entries on a bucket page and even in + * each bucket of the bucket pages. + */ + htree.insert(new byte[] { 0x01 }, new byte[] { 0x01 }); + assertEquals("nnodes", 1, htree.getNodeCount()); + assertEquals("nleaves", 3, htree.getLeafCount()); + assertEquals("nentries", 3, htree.getEntryCount()); + htree.dump(Level.ALL, System.err, true/* materialize */); + assertTrue(root == htree.getRoot()); + assertEquals(4, root.childRefs.length); + final BucketPage c = (BucketPage) root.childRefs[1].get(); + assertTrue(a == (BucketPage) root.childRefs[0].get()); + assertTrue(c == (BucketPage) root.childRefs[1].get()); + assertTrue(b == (BucketPage) root.childRefs[2].get()); + assertTrue(b == (BucketPage) root.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth()); + assertEquals(2, a.getGlobalDepth());// localDepth has increased. + assertEquals(1, b.getGlobalDepth());// + assertEquals(2, c.getGlobalDepth());// localDepth is same as [a]. + assertTrue(htree.contains(new byte[] { 0x01 })); + assertFalse(htree.contains(new byte[] { 0x02 })); + assertEquals(new byte[] { 0x01 }, htree + .lookupFirst(new byte[] { 0x01 })); + assertNull(htree.lookupFirst(new byte[] { 0x02 })); + AbstractBTreeTestCase.assertSameIterator( + // + new byte[][] { new byte[] { 0x01 }, new byte[] { 0x01 }, + new byte[] { 0x01 } }, htree + .lookupAll(new byte[] { 0x01 })); + AbstractBTreeTestCase.assertSameIterator(// + new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); - // TODO REMOVE. + // TODO REMOVE (or test suite for remove). // TODO Continue progression here? @@ -245,7 +314,7 @@ * * @see bigdata/src/architecture/htree.xls */ - public void test_example_addressBits4_01() { + public void test_example_addressBits2_02() { fail("write test"); @@ -256,7 +325,7 @@ * or directory page because nothing has been inserted into that part of * the address space. */ - public void test_example_addressBits4_elidedPages() { + public void test_example_addressBits2_elidedPages() { fail("write test"); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-05-11 15:12:27
|
Revision: 4483 http://bigdata.svn.sourceforge.net/bigdata/?rev=4483&view=rev Author: thompsonbry Date: 2011-05-11 15:12:20 +0000 (Wed, 11 May 2011) Log Message: ----------- Refactored slightly and extended the test case and the worksheet to force a split of a buddy bucket where global depth == local depth. This case will force the introduction of a new directory page. I will work on the logic for introducing the new directory page next. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/htree.xls branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/htree.xls =================================================================== (Binary files differ) Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-11 14:26:34 UTC (rev 4482) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-11 15:12:20 UTC (rev 4483) @@ -479,28 +479,43 @@ * method signature to pass an insert enum {ALLDUPS,DUPKEYS,NODUPS}. */ public byte[] insert(final byte[] key, final byte[] value) { + if (key == null) throw new IllegalArgumentException(); + + // the current directory page. DirectoryPage current = getRoot(); // start at the root. + + // #of prefix bits already consumed. int prefixLength = 0;// prefix length of the root is always zero. + + // buddyOffset into [current]. int buddyOffset = 0; // buddyOffset of the root is always zero. + while (true) { + // skip prefixLength bits and then extract globalDepth bits. final int hashBits = current.getLocalHashCode(key, prefixLength); + // find the child directory page or bucket page. final AbstractPage child = current.getChild(hashBits, buddyOffset); + if (child.isLeaf()) { + /* * Found the bucket page, update it. */ + final BucketPage bucketPage = (BucketPage) child; + // attempt to insert the tuple into the bucket. + if(!bucketPage.insert(key, value, current, buddyOffset)) { - + // TODO if(parent.isReadOnly()) parent = copyOnWrite(); - + if (current.globalDepth == child.globalDepth) { - + /* * There is only one buddy hash bucket on the page. To * split the page, we have to introduce a new directory @@ -509,8 +524,12 @@ * TODO Introduce new directory page if sole buddy * bucket is full. */ + addDirectoryPageAndSplitBucketPage(current, + buddyOffset, bucketPage); - throw new UnsupportedOperationException(); + // The children of [current] have changed so we will + // search current again. + continue; } @@ -519,22 +538,36 @@ // Try again. The children have changed. continue; + } + return null; // TODO should be Void return? or depends on enum controlling dups behavior? + } + /* * Recursive descent into a child directory page. We have to update * the prefixLength and compute the offset of the buddy hash table * within the child before descending into the child. */ + + // increase prefix length by the #of address bits consumed by the + // buddy hash table. TODO child.globalDepth might always be + // [addressBits] for a directory page... prefixLength = prefixLength + child.globalDepth; + + // find the offset of the buddy hash table in the child. buddyOffset = HTreeUtil .getBuddyOffset(hashBits, current.globalDepth, child.globalDepth/* localDepthOfChild */); + + // update current so we can search in the child. current = (DirectoryPage) child; + } - } + } // insert() + public byte[] remove(final byte[] key) { // TODO Remove 1st match, returning value. throw new UnsupportedOperationException(); @@ -622,103 +655,135 @@ nleaves++; // One more bucket page in the hash tree. - /* - * Update pointers in buddy hash table in the parent. - * - * There will be [npointers] slots in the appropriate buddy hash table - * in the parent directory which point to the old bucket page. The upper - * 1/2 of those pointers will be modified to point to the new bucket - * page. The lower 1/2 of the pointers will be unchanged. - */ - { + // update the pointers in the parent. + updatePointersInParent(parent, buddyOffset, oldDepth, oldBucket, + newBucket); + + // redistribute buddy buckets between old and new pages. + redistributeBuddyBuckets(oldDepth, newDepth, oldBucket, newBucket); - // #of address slots in the parent buddy hash table. - final int slotsPerBuddy = (1 << parent.globalDepth); + // TODO assert invariants? + + } - // #of pointers in the parent buddy hash table to the old bucket. - final int npointers = 1 << (parent.globalDepth - oldDepth); - - // Must be at least two slots since we will change at least one. - assert slotsPerBuddy > 1 : "slotsPerBuddy=" + slotsPerBuddy; + /** + * Update pointers in buddy hash table in the parent in order to link the + * new {@link BucketPage} into the parent {@link DirectoryPage}. + * <p> + * There will be [npointers] slots in the appropriate buddy hash table in + * the parent {@link DirectoryPage} which point to the old + * {@link BucketPage}. The upper 1/2 of those pointers will be modified to + * point to the new {@link BucketPage}. The lower 1/2 of the pointers will + * be unchanged. + * + * @param parent + * The parent {@link DirectoryPage}. + * @param buddyOffset + * The buddyOffset within the <i>parent</i>. This identifies + * which buddy hash table in the parent must be its pointers + * updated such that it points to both the original child and new + * child. + * @param oldDepth + * The depth of the oldBucket before the split. + * @param oldBucket + * The old {@link BucketPage}. + * @param newBucket + * The new {@link BucketPage}. + */ + private void updatePointersInParent(final DirectoryPage parent, + final int buddyOffset, final int oldDepth, + final BucketPage oldBucket, final BucketPage newBucket) { - // Must be at least two pointers since we will change at least one. - assert npointers > 1 : "npointers=" + npointers; - - // The first slot in the buddy hash table in the parent. - final int firstSlot = buddyOffset; - - // The last slot in the buddy hash table in the parent. - final int lastSlot = buddyOffset + slotsPerBuddy; + // #of address slots in the parent buddy hash table. + final int slotsPerBuddy = (1 << parent.globalDepth); - /* - * Count pointers to the old bucket page. There should be - * [npointers] of them and they should be contiguous. - * - * Note: We can test References here rather than comparing addresses - * because we know that the parent and the old bucket are both - * mutable. This means that their childRef is defined and their - * storage address is NULL. - * - * TODO This logic should be in DirectoryPage#dump() - */ - int firstPointer = -1; - int nfound = 0; - boolean discontiguous = false; - for (int i = firstSlot; i < lastSlot; i++) { - if (parent.childRefs[i] == oldBucket.self) { - if (firstPointer == -1) - firstPointer = i; - nfound++; - if (((MutableDirectoryPageData) parent.data).childAddr[i] != IRawStore.NULL) { - throw new RuntimeException( - "Child address should be NULL since child is dirty"); - } - } else { - if (firstPointer != -1 && nfound != npointers) { - discontiguous = true; - } + // #of pointers in the parent buddy hash table to the old bucket. + final int npointers = 1 << (parent.globalDepth - oldDepth); + + // Must be at least two slots since we will change at least one. + assert slotsPerBuddy > 1 : "slotsPerBuddy=" + slotsPerBuddy; + + // Must be at least two pointers since we will change at least one. + assert npointers > 1 : "npointers=" + npointers; + + // The first slot in the buddy hash table in the parent. + final int firstSlot = buddyOffset; + + // The last slot in the buddy hash table in the parent. + final int lastSlot = buddyOffset + slotsPerBuddy; + + /* + * Count pointers to the old bucket page. There should be + * [npointers] of them and they should be contiguous. + * + * Note: We can test References here rather than comparing addresses + * because we know that the parent and the old bucket are both + * mutable. This means that their childRef is defined and their + * storage address is NULL. + * + * TODO This logic should be in DirectoryPage#dump() + */ + int firstPointer = -1; + int nfound = 0; + boolean discontiguous = false; + for (int i = firstSlot; i < lastSlot; i++) { + if (parent.childRefs[i] == oldBucket.self) { + if (firstPointer == -1) + firstPointer = i; + nfound++; + if (((MutableDirectoryPageData) parent.data).childAddr[i] != IRawStore.NULL) { + throw new RuntimeException( + "Child address should be NULL since child is dirty"); } + } else { + if (firstPointer != -1 && nfound != npointers) { + discontiguous = true; + } } - if (firstPointer == -1) - throw new RuntimeException("No pointers to child"); - if (nfound != npointers) - throw new RuntimeException("Expected " + npointers - + " pointers to child, but found=" + nfound); - if (discontiguous) - throw new RuntimeException( - "Pointers to child are discontiguous in parent's buddy hash table."); + } + if (firstPointer == -1) + throw new RuntimeException("No pointers to child"); + if (nfound != npointers) + throw new RuntimeException("Expected " + npointers + + " pointers to child, but found=" + nfound); + if (discontiguous) + throw new RuntimeException( + "Pointers to child are discontiguous in parent's buddy hash table."); - // Update the upper 1/2 of the pointers to the new bucket. - for (int i = firstPointer + (npointers >> 1); i < npointers; i++) { + // Update the upper 1/2 of the pointers to the new bucket. + for (int i = firstPointer + (npointers >> 1); i < npointers; i++) { - if (parent.childRefs[i] != oldBucket.self) - throw new RuntimeException("Does not point to old child."); - - // update the references to the new bucket. - parent.childRefs[i] = (Reference) newBucket.self; - - } + if (parent.childRefs[i] != oldBucket.self) + throw new RuntimeException("Does not point to old child."); + // update the references to the new bucket. + parent.childRefs[i] = (Reference) newBucket.self; + } - - // redistribute buddy buckets between old and new pages. - redistributeBuddyBuckets(oldDepth, newDepth, oldBucket, newBucket); - - // TODO assert invariants? - + } /** * Redistribute the buddy buckets. * <p> - * Note: We are not changing the #of buddy buckets, just their size and - * the page on which they are found. Any tuples in a source bucket will - * wind up in the same bucket afterwards, but the page and offset on the - * page of the buddy bucket may have been changed. + * Note: We are not changing the #of buddy buckets, just their size and the + * page on which they are found. Any tuples in a source bucket will wind up + * in the same bucket afterwards, but the page and offset on the page of the + * buddy bucket may have been changed. * <p> - * We proceed backwards, moving the upper half of the buddy buckets to - * the new bucket page first and then spreading out the lower half of - * the source page among the new bucket boundaries on the page. + * We proceed backwards, moving the upper half of the buddy buckets to the + * new bucket page first and then spreading out the lower half of the source + * page among the new bucket boundaries on the page. + * + * @param oldDepth + * The depth of the old {@link BucketPage} before the split. + * @param newDepth + * The depth of the old and new {@link BucketPage} after the + * split (this is just oldDepth+1). + * @param oldBucket + * The old {@link BucketPage}. + * @param newBucket + * The new {@link BucketPage}. */ private void redistributeBuddyBuckets(final int oldDepth, final int newDepth, final BucketPage oldBucket, @@ -869,6 +934,24 @@ } } + + /** + * Split when <code>globalDepth == localDepth</code>. This case requires the + * introduction of a new {@link DirectoryPage}. + * + * @param parent + * The parent. + * @param buddyOffset + * The offset of the buddy hash table within the parent. + * @param oldBucket + * The {@link BucketPage} to be split. + */ + private void addDirectoryPageAndSplitBucketPage(final DirectoryPage parent, + final int buddyOffset, final BucketPage oldBucket) { + + throw new UnsupportedOperationException(); + + } /** * Persistence capable abstract base class for HTree pages. Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java 2011-05-11 14:26:34 UTC (rev 4482) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java 2011-05-11 15:12:20 UTC (rev 4483) @@ -296,7 +296,109 @@ .lookupAll(new byte[] { 0x01 })); AbstractBTreeTestCase.assertSameIterator(// new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); - + + /* + * 4. Insert another duplicate key. The buddy bucket is now full + * again. It is only the only buddy bucket on the page. + * + * Note: At this point if we insert another duplicate key the page + * size will be doubled because all keys on the page are duplicates + * and there is only one buddy bucket on the page. + * + * However, rather than test the doubling of the page size, this + * example is written to test a split where global depth == local + * depth, which will cause the introduction of a new directory page + * in the htree. + */ + htree.insert(new byte[] { 0x01 }, new byte[] { 0x01 }); + assertEquals("nnodes", 1, htree.getNodeCount()); + assertEquals("nleaves", 3, htree.getLeafCount()); + assertEquals("nentries", 4, htree.getEntryCount()); + htree.dump(Level.ALL, System.err, true/* materialize */); + assertTrue(root == htree.getRoot()); + assertEquals(4, root.childRefs.length); + assertTrue(a == (BucketPage) root.childRefs[0].get()); + assertTrue(c == (BucketPage) root.childRefs[1].get()); + assertTrue(b == (BucketPage) root.childRefs[2].get()); + assertTrue(b == (BucketPage) root.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth()); + assertEquals(2, a.getGlobalDepth());// localDepth has increased. + assertEquals(1, b.getGlobalDepth());// + assertEquals(2, c.getGlobalDepth());// localDepth is same as [a]. + assertTrue(htree.contains(new byte[] { 0x01 })); + assertFalse(htree.contains(new byte[] { 0x02 })); + assertEquals(new byte[] { 0x01 }, htree + .lookupFirst(new byte[] { 0x01 })); + assertNull(htree.lookupFirst(new byte[] { 0x02 })); + AbstractBTreeTestCase.assertSameIterator( + // + new byte[][] { new byte[] { 0x01 }, new byte[] { 0x01 }, + new byte[] { 0x01 }, new byte[] { 0x01 } }, htree + .lookupAll(new byte[] { 0x01 })); + AbstractBTreeTestCase.assertSameIterator(// + new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); + + /* + * 4. Insert 0x20. This key is directed into the same buddy bucket + * as the 0x01 keys that we have been inserting. That buddy bucket + * is already full and, further, it is the only buddy bucket on the + * page. Since we are not inserting a duplicate key we can split the + * buddy bucket. However, since global depth == local depth (i.e., + * only one buddy bucket on the page), this split will introduce a + * new directory page. The new directory page basically codes for + * the additional prefix bits required to differentiate the two + * distinct keys such that they are directed into the appropriate + * buckets. + * + * Note: The #of new directory levels which have to be introduced + * here is a function of the #of prefix bits which have to be + * consumed before a distinction can be made between the existing + * key (0x01) and the new key (0x20). With an address space of 2 + * bits, each directory level examines the next 2-bits of the key. + * The key (x20) was chosen since the distinction can be made by + * adding only one directory level (the keys differ in the first 4 + * bits). + * + * TODO At this point we should also prune the buckets [b] and [c] + * since they are empty and replace them with null references. + */ + htree.insert(new byte[] { 0x20 }, new byte[] { 0x20 }); + assertEquals("nnodes", 2, htree.getNodeCount()); + assertEquals("nleaves", 4, htree.getLeafCount()); + assertEquals("nentries", 5, htree.getEntryCount()); + htree.dump(Level.ALL, System.err, true/* materialize */); + assertTrue(root == htree.getRoot()); + assertEquals(4, root.childRefs.length); + final DirectoryPage d = (DirectoryPage)root.childRefs[0].get(); + assertTrue(d == (DirectoryPage) root.childRefs[0].get()); + assertTrue(d == (DirectoryPage) root.childRefs[1].get()); + assertTrue(b == (BucketPage) root.childRefs[2].get()); + assertTrue(b == (BucketPage) root.childRefs[3].get()); + assertEquals(4, d.childRefs.length); + final BucketPage e = (BucketPage)d.childRefs[1].get(); + assertTrue(a == (BucketPage) d.childRefs[0].get()); + assertTrue(e == (BucketPage) d.childRefs[1].get()); + assertTrue(c == (BucketPage) d.childRefs[2].get()); + assertTrue(c == (BucketPage) d.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth()); + assertEquals(2, d.getGlobalDepth()); + assertEquals(2, a.getGlobalDepth());// unchanged + assertEquals(2, e.getGlobalDepth());// same as [a]. + assertEquals(1, b.getGlobalDepth());// unchanged. + assertEquals(2, c.getGlobalDepth());// unchanged. + assertTrue(htree.contains(new byte[] { 0x01 })); + assertFalse(htree.contains(new byte[] { 0x02 })); + assertEquals(new byte[] { 0x01 }, htree + .lookupFirst(new byte[] { 0x01 })); + assertNull(htree.lookupFirst(new byte[] { 0x02 })); + AbstractBTreeTestCase.assertSameIterator( + // + new byte[][] { new byte[] { 0x01 }, new byte[] { 0x01 }, + new byte[] { 0x01 }, new byte[] { 0x01 } }, htree + .lookupAll(new byte[] { 0x01 })); + AbstractBTreeTestCase.assertSameIterator(// + new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); + // TODO REMOVE (or test suite for remove). // TODO Continue progression here? This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-05-15 18:46:24
|
Revision: 4503 http://bigdata.svn.sourceforge.net/bigdata/?rev=4503&view=rev Author: thompsonbry Date: 2011-05-15 18:46:17 +0000 (Sun, 15 May 2011) Log Message: ----------- A bit more work on the HTree. The test case that I have been developing turns out to be a bit of a complex edge case because it involves the split of a full bucket having only duplicate keys. I am going to rework the test case to be somewhat simpler in order to work through the core of the logic for introducing new directory pages with 2 and then with 3 levels, cache invalidation of global depth, etc. I can then work on the edge cases revolving around hash buckets which can not be split because they contain duplicate keys. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/htree.xls branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTreeUtil.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/architecture/htree.xls =================================================================== (Binary files differ) Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-15 17:46:39 UTC (rev 4502) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-15 18:46:17 UTC (rev 4503) @@ -685,7 +685,7 @@ * The parent {@link DirectoryPage}. * @param buddyOffset * The buddyOffset within the <i>parent</i>. This identifies - * which buddy hash table in the parent must be its pointers + * which buddy hash table in the parent must have its pointers * updated such that it points to both the original child and new * child. * @param oldDepth @@ -766,7 +766,7 @@ } - } + } // updatePointersInParent /** * Redistribute the buddy buckets. @@ -957,6 +957,138 @@ throw new UnsupportedOperationException(); } + + /** + * Validate pointers in buddy hash table in the parent against the global + * depth as self-reported by the child. By definition, the global depth of + * the child is based on the global depth of the parent and the #of pointers + * to the child in the parent. The {@link AbstractPage#globalDepth} value is + * the cached result of that computation. It can be cross checked by + * counting the actual number of pointers in the parent and comparing that + * with this formula: + * + * <pre> + * npointers := 1 << (parent.globalDepth - child.globalDepth) + * </pre> + * + * This method validates that the cached value of global depth on the child + * is consistent with the #of pointers in the specified buddy hash table of + * the parent. However, this validate depends on the global depth of the + * parent itself being correct. + * <p> + * Note: While other buddy hash tables in the parent can point into the same + * child page. However, due to the buddy offset computation they will not + * point into the same buddy on the same child. + * + * @param parent + * The parent {@link DirectoryPage}. + * @param buddyOffset + * The buddyOffset within the <i>parent</i>. This identifies + * which buddy hash table in the parent should have references to + * the child. + * @param child + * The child {@link AbstractPage}. + */ + private void validatePointersInParent(final DirectoryPage parent, + final int buddyOffset, final AbstractPage child) { + + // #of address slots in the parent buddy hash table. + final int slotsPerBuddy = (1 << parent.globalDepth); + + // #of pointers expected in the parent buddy hash table. + final int npointers = 1 << (parent.globalDepth - child.globalDepth); + + // The first slot in the buddy hash table in the parent. + final int firstSlot = buddyOffset; + + // The last slot in the buddy hash table in the parent. + final int lastSlot = buddyOffset + slotsPerBuddy; + + if (parent.isDirty() && parent.getIdentity() != IRawStore.NULL) + throw new RuntimeException( + "Parent address should be NULL since parent is dirty"); + + if (child.isDirty() && child.getIdentity() != IRawStore.NULL) + throw new RuntimeException( + "Child address should be NULL since child is dirty"); + + /* + * Count pointers to the child page. There should be [npointers] of them + * and they should be contiguous. Since the child is materialized (we + * have its reference) we can test pointers. If the child is dirty, then + * all slots in the parent having pointers to the child should be + * associated with NULL addresses for the child. If the child is clean, + * then all slots in the parent having references for the child should + * have the same non-zero address for the child. Also, any slot in the + * parent having the address of a persistent child should have the same + * Reference object, which should be child.self. + */ + int firstPointer = -1; + int nfound = 0; + boolean discontiguous = false; + for (int i = firstSlot; i < lastSlot; i++) { + final boolean slotRefIsChild = parent.childRefs[i] == child.self; + final boolean slotAddrIsChild = parent.getChildAddr(i) == child + .getIdentity(); + if (slotAddrIsChild && !slotRefIsChild) + throw new RuntimeException( + "Child reference in parent should be child since addr in parent is child addr"); + final boolean slotIsChild = slotRefIsChild || slotAddrIsChild; + if (slotIsChild) { + if (child.isDirty()) { + // A dirty child must have a NULL address in the parent. + if (parent.data.getChildAddr(i) != IRawStore.NULL) + throw new RuntimeException( + "Child address in parent should be NULL since child is dirty"); + } else { + // A clean child must have a non-NULL address in the parent.1 + if (parent.data.getChildAddr(i) == IRawStore.NULL) + throw new RuntimeException( + "Child address in parent must not be NULL since child is clean"); + } + if (firstPointer == -1) + firstPointer = i; + nfound++; + } else { + if (firstPointer != -1 && nfound != npointers) { + discontiguous = true; + } + } + } + if (firstPointer == -1) + throw new RuntimeException("No pointers to child"); + if (nfound != npointers) { + /* + * This indicates either a problem in maintaining the pointers + * (References and/or addresses) in the parent for the child, a + * problem where child.self is not a unique Reference for the child, + * and/or a problem with the cached [globalDepth] for the child. + */ + throw new RuntimeException("Expected " + npointers + + " pointers to child, but found=" + nfound); + } + if (discontiguous) + throw new RuntimeException( + "Pointers to child are discontiguous in parent's buddy hash table."); + } // validatePointersInParent + +// /** +// * TODO Scan the entire parent for addresses and References to each child. Any +// * time we see the address of a child, then the Reference must be +// * [child.self]. Any time we see a reference which is not child.self, then +// * the address for that slot must not be the same as the address of the +// * child. +// * <p> +// * Note: Since the child is materialized, any Reference in the parent to +// * that child should be [child.self]. Due to the buddy page system, when we +// * load a child we need to scan the parent across all buddy hash tables and +// * set the Reference on any slot having the address of that child. Likewise, +// * when the child becomes dirty, we need to clear the address of the child +// * in any slot of any buddy hash table in the parent. +// */ +// private void validateChildren(final DirectoryPage parent) { +// +// } /** * Persistence capable abstract base class for HTree pages. @@ -1741,7 +1873,7 @@ * slots on the page. We would rely on keys.capacity() in this case * rather than #slots. In fact, we could just reenter the method * above after doubling as long as we rely on keys.capacity() in the - * case where nbuddies==1. + * case where nbuddies==1. [Unit test for this case.] */ throw new UnsupportedOperationException(); } @@ -1802,8 +1934,6 @@ // Set to false iff an inconsistency is detected. boolean ok = true; - final int globalDepth = getGlobalDepth(); - if (parent == null || parent.get() == null) { out.println(indent(height) + "ERROR: parent not set"); ok = false; @@ -1813,7 +1943,18 @@ out.println(indent(height) + "ERROR: localDepth exceeds globalDepth of parent"); ok = false; } - + + /* + * FIXME Count the #of pointers in each buddy hash table of the + * parent to each buddy bucket in this bucket page and verify that + * the globalDepth on the child is consistent with the pointers in + * the parent. + * + * FIXME The same check must be performed for the directory page to + * cross validate the parent child linking pattern with the + * transient cached globalDepth fields. + */ + if (debug || ! ok ) { out.println(indent(height) + toString()); @@ -2107,7 +2248,7 @@ } - /* + /** * TODO We should dump each bucket page once. This could be done either * by dumping each buddy bucket on the page separately or by skipping * through the directory page until we get to the next bucket page and @@ -2120,6 +2261,8 @@ * there can not be "gaps" between observations of a reference to a * given bucket - once you see another bucket reference a previously * observed reference can not then appear. + * + * @see HTree#validatePointersInParent(DirectoryPage, int, AbstractPage) */ @Override protected boolean dump(Level level, PrintStream out, @@ -2163,14 +2306,6 @@ } } -// // verify keys are monotonically increasing. -// try { -// assertKeysMonotonic(); -// } catch (AssertionError ex) { -// out.println(indent(height) + " ERROR: " + ex); -// ok = false; -// } - if (debug) { out.println(indent(height) + toString()); } @@ -2213,19 +2348,6 @@ out.println(indent(height) + " ERROR child[" + i + "] has wrong parent."); ok = false; -// // some extra stuff used to track down a bug. -// if (!ok) { -// if (level == Level.DEBUG) { -// // dump the child also and exit. -// System.err.println("child"); -// child.dump(Level.DEBUG, System.err); -// throw new AssertionError(); -// } else { -// // recursive call to get debug level dump. -// System.err.println("this"); -// this.dump(Level.DEBUG, System.err); -// } -// } } if (child.isDirty()) { @@ -2250,15 +2372,6 @@ + " since the child is dirty"); ok = false; } - // if (!dirtyChildren.contains(child)) { - // out - // .println(indent(height + 1) - // + " ERROR child at index=" - // + i - // + " is dirty, but not on the dirty list: child=" - // + child); - // ok = false; - // } } else { /* * Clean child (ie, persistent). The parent of a clean @@ -2270,15 +2383,6 @@ + ", but child is not dirty"); ok = false; } - // if (dirtyChildren.contains(child)) { - // out - // .println(indent(height) - // + " ERROR child at index=" - // + i - // + " is not dirty, but is on the dirty list: child=" - // + child); - // ok = false; - // } } } @@ -2354,126 +2458,12 @@ } - // if (child.isDirty() && !dirtyChildren.contains(child)) { - // - // out - // .println(indent(height + 1) - // + "ERROR dirty child not in node's dirty list at index=" - // + i); - // - // ok = false; - // - // } - // - // if (!child.isDirty() && dirtyChildren.contains(child)) { - // - // out - // .println(indent(height + 1) - // + - // "ERROR clear child found in node's dirty list at index=" - // + i); - // - // ok = false; - // - // } - if (child.isDirty()) { dirty.add(child); } -// if (i == 0) { -// -// if (nkeys == 0) { -// -// /* -// * Note: a node with zero keys is valid. It MUST -// * have a single child. Such nodes arise when -// * splitting a node in a btree of order m := 3 when -// * the splitIndex is computed as m/2-1 = 0. This is -// * perfectly normal. -// */ -// -// } else { -// /* -// * Note: All keys on the first child MUST be LT the -// * first key on this node. -// */ -// final byte[] k0 = getKeys().get(0); -// final byte[] ck0 = child.getKeys().get(0); -// if (BytesUtil.compareBytes(ck0, k0) >= 0) { -// // if( child.compare(0,keys,0) >= 0 ) { -// -// out -// .println(indent(height + 1) -// + "ERROR first key on first child must be LT " -// + keyAsString(k0) -// + ", but found " -// + keyAsString(ck0)); -// -// ok = false; -// -// } -// -// if (child.getKeyCount() >= 1) { -// -// final byte[] ckn = child.getKeys().get( -// child.getKeyCount() - 1); -// if (BytesUtil.compareBytes(ckn, k0) >= 0) { -// // if (child.compare(child.nkeys-1, keys, 0) -// // >= 0) { -// -// out -// .println(indent(height + 1) -// + "ERROR last key on first child must be LT " -// + keyAsString(k0) -// + ", but found " -// + keyAsString(ckn)); -// -// ok = false; -// -// } -// -// } -// -// } -// -// } else if (i < nkeys) { -// -// // Note: The delete rule does not preserve this -// // characteristic since we do not -// // update the separatorKey for a leaf when removing its -// // left most key. -// // -// // if (child.isLeaf() && keys[i - 1] != child.keys[0]) { -// // -// // /* -// // * While each key in a node always is the first key of -// // * some leaf, we are only testing the direct children -// // * here. Therefore if the children are not leaves then -// // * we can not cross check their first key with the -// // keys -// // * on this node. -// // */ -// // out.println(indent(height + 1) -// // + "ERROR first key on child leaf must be " -// // + keys[i - 1] + ", not " + child.keys[0] -// // + " at index=" + i); -// // -// // ok = false; -// // -// // } -// -// } else { -// -// /* -// * While there is a child for the last index of a node, -// * there is no key for that index. -// */ -// -// } - if (!child.dump(level, out, height + 1, true, materialize)) { @@ -2485,16 +2475,6 @@ } - // if (dirty.size() != dirtyChildren.size()) { - // - // out.println(indent(height + 1) + "ERROR found " + dirty.size() - // + " dirty children, but " + dirtyChildren.size() - // + " in node's dirty list"); - // - // ok = false; - // - // } - } return ok; Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java 2011-05-15 17:46:39 UTC (rev 4502) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java 2011-05-15 18:46:17 UTC (rev 4503) @@ -339,65 +339,132 @@ new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); /* - * 4. Insert 0x20. This key is directed into the same buddy bucket + * FIXME We MUST NOT decrease the localDepth of (a) since that would + * place duplicate keys into different buckets (lost retrival). + * Since (a) can not have its localDepth decreased, we need to have + * localDepth(d=2) with one pointer to (a) to get localDepth(a:=2). + * That makes this a very complicated example. It would be a lot + * easier if we started with distinct keys such that the keys in (a) + * could be redistributed. This case should be reserved for later + * once the structural mutations are better understood. + * + * 5. Insert 0x20. This key is directed into the same buddy bucket * as the 0x01 keys that we have been inserting. That buddy bucket * is already full and, further, it is the only buddy bucket on the * page. Since we are not inserting a duplicate key we can split the - * buddy bucket. However, since global depth == local depth (i.e., - * only one buddy bucket on the page), this split will introduce a - * new directory page. The new directory page basically codes for - * the additional prefix bits required to differentiate the two - * distinct keys such that they are directed into the appropriate - * buckets. + * buddy bucket (rather than doubling the size of the bucket). + * However, since global depth == local depth (i.e., only one buddy + * bucket on the page), this split will introduce a new directory + * page. The new directory page basically codes for the additional + * prefix bits required to differentiate the two distinct keys such + * that they are directed into the appropriate buckets. * - * Note: The #of new directory levels which have to be introduced + * The new directory page (d) is inserted one level below the root + * on the path leading to (a). The new directory must have a local + * depth of ONE (1), since it will add a one bit distinction. Since + * we know the global depth of the root and the local depth of the + * new directory page, we solve for npointers := 1 << (globalDepth - + * localDepth). This tells us that we will have 2 pointers in the + * root to the new directory page. + * + * The precondition state of root is {a,c,b,b}. Since we know that + * we need npointers:=2 pointers to (d), this means that we will + * copy the {a,c} references into (d) and replace those references + * in the root with references to (d). The root is now {d,d,b,b}. d + * is now {a,a;c,c}. Since d has a local depth of 1 and address bits + * of 2, it is comprised of two buddy hash tables {a,a} and {c,c}. + * + * Linking in (d) has also changes the local depths of (a) and (c). + * Since they each now have npointers:=2, their localDepth is has + * been reduced from TWO (2) to ONE (1) (and their transient cached + * depth values must be either invalidated or recomputed). Note that + * the local depth of (d) and its children (a,c)) are ONE after this + * operation so if we force another split in (a) that will force a + * split in (d). + * + * Having introduced a new directory page into the hash tree, we now + * retry the insert. Once again, the insert is directed into (a). + * Since (a) is still full it is split. (d) is now the parent of + * (a). Once again, we have globalDepth(d=1)==localDepth(a=1) so we + * need to split (d). However, since localDepth(d=1) is less than + * globalDepth(root=2) we can split the buddy hash tables in (d). + * This will require us to allocate a new page (f) which will be the + * right sibling of (d). There are TWO (2) buddy hash tables in (d). + * They are now redistributed between (d) and (f). We also have to + * update the pointers to (d) in the parent such that 1/2 of them + * point to the new right sibling of (d). Since we have changed the + * #of pointers to (d) (from 2 to 1) the local depth of (d) (and of + * f) is now TWO (2). Since globalDepth(d=2) is greater than + * localDepth(a=1) we can now split (a) into (a,e), redistribute the + * tuples in the sole buddy page (a) between (a,e) and update the + * pointers in (d) to (a,a,e,e). + * + * Having split (d) into (d,f), we now retry the insert. This time + * the insert is directed into (e). There is room in (e) (it is + * empty) and the tuple is inserted without further mutation to the + * structure of the hash tree. + * + * TODO At this point we should also prune the buckets [b] and [c] + * since they are empty and replace them with null references. + * + * TODO The #of new directory levels which have to be introduced * here is a function of the #of prefix bits which have to be * consumed before a distinction can be made between the existing * key (0x01) and the new key (0x20). With an address space of 2 * bits, each directory level examines the next 2-bits of the key. * The key (x20) was chosen since the distinction can be made by * adding only one directory level (the keys differ in the first 4 - * bits). + * bits). [Do an alternative example which requires recursive splits + * in order to verify that we reenter the logic correctly each time. + * E.g., by inserting 0x02 rather than 0x20.] * - * TODO At this point we should also prune the buckets [b] and [c] - * since they are empty and replace them with null references. + * TODO Do an example in which we explore an insert which introduces + * a new directory level in a 3-level tree. This should be a driven + * by a single insert so we can examine in depth how the new + * directory is introduced and verify whether it is introduced below + * the root or above the bucket. [I believe that it is introduced + * immediately below below the root. Note that a balanced tree, + * e.g., a B-Tree, introduces the new level above the root. However, + * the HTree is intended to be unbalanced in order to optimize + * storage and access times to the parts of the index which + * correspond to unequal distributions in the hash codes.] */ - htree.insert(new byte[] { 0x20 }, new byte[] { 0x20 }); - assertEquals("nnodes", 2, htree.getNodeCount()); - assertEquals("nleaves", 4, htree.getLeafCount()); - assertEquals("nentries", 5, htree.getEntryCount()); - htree.dump(Level.ALL, System.err, true/* materialize */); - assertTrue(root == htree.getRoot()); - assertEquals(4, root.childRefs.length); - final DirectoryPage d = (DirectoryPage)root.childRefs[0].get(); - assertTrue(d == (DirectoryPage) root.childRefs[0].get()); - assertTrue(d == (DirectoryPage) root.childRefs[1].get()); - assertTrue(b == (BucketPage) root.childRefs[2].get()); - assertTrue(b == (BucketPage) root.childRefs[3].get()); - assertEquals(4, d.childRefs.length); - final BucketPage e = (BucketPage)d.childRefs[1].get(); - assertTrue(a == (BucketPage) d.childRefs[0].get()); - assertTrue(e == (BucketPage) d.childRefs[1].get()); - assertTrue(c == (BucketPage) d.childRefs[2].get()); - assertTrue(c == (BucketPage) d.childRefs[3].get()); - assertEquals(2, root.getGlobalDepth()); - assertEquals(2, d.getGlobalDepth()); - assertEquals(2, a.getGlobalDepth());// unchanged - assertEquals(2, e.getGlobalDepth());// same as [a]. - assertEquals(1, b.getGlobalDepth());// unchanged. - assertEquals(2, c.getGlobalDepth());// unchanged. - assertTrue(htree.contains(new byte[] { 0x01 })); - assertFalse(htree.contains(new byte[] { 0x02 })); - assertEquals(new byte[] { 0x01 }, htree - .lookupFirst(new byte[] { 0x01 })); - assertNull(htree.lookupFirst(new byte[] { 0x02 })); - AbstractBTreeTestCase.assertSameIterator( - // - new byte[][] { new byte[] { 0x01 }, new byte[] { 0x01 }, - new byte[] { 0x01 }, new byte[] { 0x01 } }, htree - .lookupAll(new byte[] { 0x01 })); - AbstractBTreeTestCase.assertSameIterator(// - new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); +// htree.insert(new byte[] { 0x20 }, new byte[] { 0x20 }); +// assertEquals("nnodes", 2, htree.getNodeCount()); +// assertEquals("nleaves", 4, htree.getLeafCount()); +// assertEquals("nentries", 5, htree.getEntryCount()); +// htree.dump(Level.ALL, System.err, true/* materialize */); +// assertTrue(root == htree.getRoot()); +// assertEquals(4, root.childRefs.length); +// final DirectoryPage d = (DirectoryPage)root.childRefs[0].get(); +// assertTrue(d == (DirectoryPage) root.childRefs[0].get()); +// assertTrue(d == (DirectoryPage) root.childRefs[1].get()); +// assertTrue(b == (BucketPage) root.childRefs[2].get()); +// assertTrue(b == (BucketPage) root.childRefs[3].get()); +// assertEquals(4, d.childRefs.length); +// final BucketPage e = (BucketPage)d.childRefs[1].get(); +// assertTrue(a == (BucketPage) d.childRefs[0].get()); +// assertTrue(e == (BucketPage) d.childRefs[1].get()); +// assertTrue(c == (BucketPage) d.childRefs[2].get()); +// assertTrue(c == (BucketPage) d.childRefs[3].get()); +// assertEquals(2, root.getGlobalDepth()); +// assertEquals(2, d.getGlobalDepth()); +// assertEquals(2, a.getGlobalDepth());// unchanged +// assertEquals(2, e.getGlobalDepth());// same as [a]. +// assertEquals(1, b.getGlobalDepth());// unchanged. +// assertEquals(2, c.getGlobalDepth());// unchanged. +// assertTrue(htree.contains(new byte[] { 0x01 })); +// assertFalse(htree.contains(new byte[] { 0x02 })); +// assertEquals(new byte[] { 0x01 }, htree +// .lookupFirst(new byte[] { 0x01 })); +// assertNull(htree.lookupFirst(new byte[] { 0x02 })); +// AbstractBTreeTestCase.assertSameIterator( +// // +// new byte[][] { new byte[] { 0x01 }, new byte[] { 0x01 }, +// new byte[] { 0x01 }, new byte[] { 0x01 } }, htree +// .lookupAll(new byte[] { 0x01 })); +// AbstractBTreeTestCase.assertSameIterator(// +// new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); // TODO REMOVE (or test suite for remove). Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTreeUtil.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTreeUtil.java 2011-05-15 17:46:39 UTC (rev 4502) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTreeUtil.java 2011-05-15 18:46:17 UTC (rev 4503) @@ -98,6 +98,12 @@ * range is [0:addressBits]. This depends solely on the <i>globalDepth</i> * of a directory page and the #of pointers to child (<i>npointers</i>) in * that directory page.</dd> + * <dt>nbits</dt> + * <dd>This is <code>(globalDepth-localDepth)</code>. It is the #of bits of + * the hash code which will be used to compute the <i>buddyOffset</i>.</dd> + * <dt>hashBits</dt> + * <dd>The LSB <code>globalDepth-localDepth</code> bits of the hash code. + * This is used to compute the <i>buddyOffset</i>.</dd> * <dt>buddyOffset</dt> * <dd>The offset of the buddy hash table or buddy bucket within the child.</dd> * </dl> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <tho...@us...> - 2011-05-16 20:29:02
|
Revision: 4512 http://bigdata.svn.sourceforge.net/bigdata/?rev=4512&view=rev Author: thompsonbry Date: 2011-05-16 20:28:56 +0000 (Mon, 16 May 2011) Log Message: ----------- Fixed some asserts in the HTree constructor. Introduced a simpler example based on inserting 1,2,3,4,.... into the HTree. This example will make it possible to explore introducing a new directory page into the hash tree without having to deal with all the edge cases at once. The old example is still present in the test suite (and the worksheet) and I will revisit it after the basics of directory splitting are in place. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-16 19:55:58 UTC (rev 4511) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/java/com/bigdata/htree/HTree.java 2011-05-16 20:28:56 UTC (rev 4512) @@ -188,28 +188,28 @@ this.deleteMarkers = false; this.rawRecords = true; - /* - * The initial setup of the hash tree is a root directory page whose - * global depth is always the #of address bits (which is the maximum - * value that global depth can take on for a given hash tree). There is - * a single bucket page and all entries in the root directory page point - * to that bucket. This means that the local depth of the initial bucket - * page will be zero (you can prove this for yourself by consulting the - * tables generated by TestHTree). With a depth of zero, the initial - * bucket page will have buddy hash buckets which can hold only a single - * distinct hash key (buckets always have to handle duplicates of a hash - * key). - * - * From this initial configuration, inserts of 2 distinct keys which - * fall into the same buddy hash tables will cause that buddy hash - * buckets in the initial bucket page to split, increasing the depth of - * the resulting bucket page by one. Additional splits driven by inserts - * of distinct keys will eventually cause the local depth of some bucket - * page to exceed the global depth of the root and a new level will be - * introduced below the root. The hash tree can continue to grow in this - * manner, gradually adding depth where there are bit prefixes for which - * there exists a lot of variety in the observed keys. - */ + /* + * The initial setup of the hash tree is a root directory page whose + * global depth is the #of address bits (which is the maximum value that + * global depth can take on for a given hash tree). There is a single + * bucket page and all entries in the root directory page point to that + * bucket. This means that the local depth of the initial bucket page + * will be zero (you can prove this for yourself by consulting the + * tables generated by TestHTree). With a depth of zero, the initial + * bucket page will have buddy hash buckets which can hold only a single + * distinct hash key (buckets always have to handle duplicates of a hash + * key). + * + * From this initial configuration, inserts of 2 distinct keys which + * fall into the same buddy hash tables will cause that buddy hash + * buckets in the initial bucket page to split, increasing the depth of + * the resulting bucket page by one. Additional splits driven by inserts + * of distinct keys will eventually cause the local depth of some bucket + * page to exceed the global depth of the root and a new level will be + * introduced below the root. The hash tree can continue to grow in this + * manner, gradually adding depth where there are bit prefixes for which + * there exists a lot of variety in the observed keys. + */ // Initial root. final DirectoryPage r = new DirectoryPage(// @@ -217,8 +217,9 @@ addressBits // the global depth of the root. ); nnodes++; - assert r.getSlotsPerBuddy() == 1; - assert r.getNumBuddies() == (1 << addressBits); + assert r.getSlotsPerBuddy() == (1 << addressBits) : "slotsPerBuddy=" + + r.getSlotsPerBuddy(); + assert r.getNumBuddies() == 1 : "numBuddies=" + r.getNumBuddies(); // Data for the root. final MutableDirectoryPageData rdata = (MutableDirectoryPageData) r.data; Modified: branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java 2011-05-16 19:55:58 UTC (rev 4511) +++ branches/QUADS_QUERY_BRANCH/bigdata/src/test/com/bigdata/htree/TestHTree.java 2011-05-16 20:28:56 UTC (rev 4512) @@ -151,7 +151,389 @@ } - /** + /** + * Test of basic insert, lookup, and split page operations (including when a + * new directory page must be introduced) using an address space with only + * TWO (2) bits. + * + * @see bigdata/src/architecture/htree.xls#example1 + * + * TODO Verify that we can store keys having more than 2 bits (or 4 + * bits in a 4-bit address space) through a deeper hash tree. + */ + public void test_example_addressBits2_01() { + + final int addressBits = 2; + + final IRawStore store = new SimpleMemoryRawStore(); + + try { + + final byte[] k1 = new byte[]{0x01}; + final byte[] k2 = new byte[]{0x02}; + final byte[] k3 = new byte[]{0x03}; + final byte[] k4 = new byte[]{0x04}; + final byte[] k5 = new byte[]{0x05}; + final byte[] k6 = new byte[]{0x06}; + final byte[] k7 = new byte[]{0x07}; + final byte[] k8 = new byte[]{0x08}; + final byte[] k9 = new byte[]{0x09}; + final byte[] k10 = new byte[]{0x0a}; + + final byte[] v1 = new byte[]{0x01}; + final byte[] v2 = new byte[]{0x02}; + final byte[] v3 = new byte[]{0x03}; + final byte[] v4 = new byte[]{0x04}; + final byte[] v5 = new byte[]{0x05}; + final byte[] v6 = new byte[]{0x06}; + final byte[] v7 = new byte[]{0x07}; + final byte[] v8 = new byte[]{0x08}; + final byte[] v9 = new byte[]{0x09}; + final byte[] v10 = new byte[]{0x0a}; + + // a key which we never insert and which should never be found by lookup. + final byte[] unused = new byte[]{-127}; + + final HTree htree = new HTree(store, addressBits); + + // Verify initial conditions. + assertTrue("store", store == htree.getStore()); + assertEquals("addressBits", addressBits, htree.getAddressBits()); + + final DirectoryPage root = htree.getRoot(); + assertEquals(4, root.childRefs.length); + final BucketPage a = (BucketPage) root.childRefs[0].get(); + assertTrue(a == (BucketPage) root.childRefs[1].get()); + assertTrue(a == (BucketPage) root.childRefs[2].get()); + assertTrue(a == (BucketPage) root.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth());// starts at max. + assertEquals(0, a.getGlobalDepth());// starts at min. + + // verify preconditions. + assertEquals("nnodes", 1, htree.getNodeCount()); + assertEquals("nleaves", 1, htree.getLeafCount()); + assertEquals("nentries", 0, htree.getEntryCount()); + htree.dump(Level.ALL, System.err, true/* materialize */); + assertEquals(2, root.getGlobalDepth()); + assertEquals(0, a.getGlobalDepth()); + assertEquals(0, a.getKeyCount()); + assertEquals(0, a.getValueCount()); + assertFalse(htree.contains(k1)); + assertFalse(htree.contains(unused)); + assertEquals(null, htree.lookupFirst(k1)); + assertEquals(null, htree.lookupFirst(unused)); + AbstractBTreeTestCase.assertSameIterator(// + new byte[][] {}, htree.lookupAll(k1)); + AbstractBTreeTestCase.assertSameIterator(// + new byte[][] {}, htree.lookupAll(unused)); + + /* + * 1. Insert a tuple (0x01) and verify post-conditions. The tuple + * goes into an empty buddy bucket with a capacity of one. + */ + htree.insert(k1, v1); + assertEquals("nnodes", 1, htree.getNodeCount()); + assertEquals("nleaves", 1, htree.getLeafCount()); + assertEquals("nentries", 1, htree.getEntryCount()); + htree.dump(Level.ALL, System.err, true/* materialize */); + assertEquals(2, root.getGlobalDepth()); + assertEquals(0, a.getGlobalDepth()); + assertEquals(1, a.getKeyCount()); + assertEquals(1, a.getValueCount()); + assertTrue(htree.contains(k1)); + assertFalse(htree.contains(unused)); + assertEquals(v1, htree.lookupFirst(k1)); + assertEquals(null,htree.lookupFirst(unused)); + AbstractBTreeTestCase.assertSameIterator(// + new byte[][] { v1 }, htree.lookupAll(k1)); + AbstractBTreeTestCase.assertSameIterator(// + new byte[][] {}, htree.lookupAll(unused)); + + /* + * 2. Insert a tuple (0x02). Since the root directory is only paying + * attention to the 2 MSB bits, this will be hash into the same + * buddy hash bucket as the first key. Since the localDepth of the + * bucket page is zero, each buddy bucket on the page can only + * accept one entry and this will force a split of the buddy bucket. + * That means that a new bucket page will be allocated, the pointers + * in the parent will be updated to link in the new buck page, and + * the buddy buckets will be redistributed among the old and new + * bucket page. + */ + htree.insert(k2, v2); + assertEquals("nnodes", 1, htree.getNodeCount()); + assertEquals("nleaves", 2, htree.getLeafCount()); + assertEquals("nentries", 2, htree.getEntryCount()); + htree.dump(Level.ALL, System.err, true/* materialize */); + assertTrue(root == htree.getRoot()); + assertEquals(4, root.childRefs.length); + final BucketPage b = (BucketPage) root.childRefs[2].get(); + assertTrue(a == (BucketPage) root.childRefs[0].get()); + assertTrue(a == (BucketPage) root.childRefs[1].get()); + assertTrue(b == (BucketPage) root.childRefs[2].get()); + assertTrue(b == (BucketPage) root.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth()); + assertEquals(1, a.getGlobalDepth());// localDepth has increased. + assertEquals(1, b.getGlobalDepth());// localDepth is same as [a]. + assertEquals(2, a.getKeyCount()); + assertEquals(2, a.getValueCount()); + assertEquals(0, b.getKeyCount()); + assertEquals(0, b.getValueCount()); + assertTrue(htree.contains(k1)); + assertTrue(htree.contains(k2)); + assertFalse(htree.contains(unused)); + assertEquals(v1, htree.lookupFirst(k1)); + assertNull(htree.lookupFirst(unused)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v1 }, htree + .lookupAll(k1)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v2 }, htree + .lookupAll(k2)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] {}, htree + .lookupAll(unused)); + + /* + * 3. Insert 0x03. This forces another split. + */ + htree.insert(k3, v3); + assertEquals("nnodes", 1, htree.getNodeCount()); + assertEquals("nleaves", 3, htree.getLeafCount()); + assertEquals("nentries", 3, htree.getEntryCount()); + htree.dump(Level.ALL, System.err, true/* materialize */); + assertTrue(root == htree.getRoot()); + assertEquals(4, root.childRefs.length); + final BucketPage c = (BucketPage) root.childRefs[1].get(); + assertTrue(a == (BucketPage) root.childRefs[0].get()); + assertTrue(c == (BucketPage) root.childRefs[1].get()); + assertTrue(b == (BucketPage) root.childRefs[2].get()); + assertTrue(b == (BucketPage) root.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth()); + assertEquals(2, a.getGlobalDepth());// localDepth has increased. + assertEquals(1, b.getGlobalDepth());// + assertEquals(2, c.getGlobalDepth());// localDepth is same as [a]. + assertEquals(3, a.getKeyCount()); + assertEquals(3, a.getValueCount()); + assertEquals(0, b.getKeyCount()); + assertEquals(0, b.getValueCount()); + assertEquals(0, c.getKeyCount()); + assertEquals(0, c.getValueCount()); + assertTrue(htree.contains(k1)); + assertTrue(htree.contains(k2)); + assertTrue(htree.contains(k3)); + assertFalse(htree.contains(unused)); + assertEquals(v1, htree.lookupFirst(k1)); + assertEquals(v2, htree.lookupFirst(k2)); + assertEquals(v3, htree.lookupFirst(k3)); + assertNull(htree.lookupFirst(unused)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v1 }, htree + .lookupAll(k1)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v2 }, htree + .lookupAll(k2)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v3 }, htree + .lookupAll(k3)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] {}, htree + .lookupAll(unused)); + + /* + * 4. Insert 0x04. This goes into the same buddy bucket. The buddy + * bucket is now full again. It is only the only buddy bucket on the + * page, e.g., global depth == local depth. + * + * Note: Inserting another tuple into this buddy bucket will not + * only cause it to split but it will also introduce a new directory + * page into the hash tree. + */ + htree.insert(k4, v4); + assertEquals("nnodes", 1, htree.getNodeCount()); + assertEquals("nleaves", 3, htree.getLeafCount()); + assertEquals("nentries", 4, htree.getEntryCount()); + htree.dump(Level.ALL, System.err, true/* materialize */); + assertTrue(root == htree.getRoot()); + assertEquals(4, root.childRefs.length); + assertTrue(a == (BucketPage) root.childRefs[0].get()); + assertTrue(c == (BucketPage) root.childRefs[1].get()); + assertTrue(b == (BucketPage) root.childRefs[2].get()); + assertTrue(b == (BucketPage) root.childRefs[3].get()); + assertEquals(2, root.getGlobalDepth()); + assertEquals(2, a.getGlobalDepth());// localDepth has increased. + assertEquals(1, b.getGlobalDepth());// + assertEquals(2, c.getGlobalDepth());// localDepth is same as [a]. + assertEquals(4, a.getKeyCount()); + assertEquals(4, a.getValueCount()); + assertEquals(0, b.getKeyCount()); + assertEquals(0, b.getValueCount()); + assertEquals(0, c.getKeyCount()); + assertEquals(0, c.getValueCount()); + assertTrue(htree.contains(k1)); + assertTrue(htree.contains(k2)); + assertTrue(htree.contains(k3)); + assertTrue(htree.contains(k4)); + assertFalse(htree.contains(unused)); + assertEquals(v1, htree.lookupFirst(k1)); + assertEquals(v2, htree.lookupFirst(k2)); + assertEquals(v3, htree.lookupFirst(k3)); + assertEquals(v4, htree.lookupFirst(k4)); + assertNull(htree.lookupFirst(unused)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v1 }, htree + .lookupAll(k1)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v2 }, htree + .lookupAll(k2)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v3 }, htree + .lookupAll(k3)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] { v4 }, htree + .lookupAll(k4)); + AbstractBTreeTestCase.assertSameIterator(new byte[][] {}, htree + .lookupAll(unused)); + + /* + * 5. Insert 0x05. This goes into the same buddy bucket. The buddy + * bucket is full again. It is only the only buddy bucket on the + * page, e.g., global depth == local depth. Therefore, before we can + * split the buddy bucket we have first introduce a new directory + * page into the hash tree. + * + * FIXME Update comments to reflect the example (and update the + * worksheet as well). + * + * FIXME We MUST NOT decrease the localDepth of (a) since that would + * place duplicate keys into different buckets (lost retrival). + * Since (a) can not have its localDepth decreased, we need to have + * localDepth(d=2) with one pointer to (a) to get localDepth(a:=2). + * That makes this a very complicated example. It would be a lot + * easier if we started with distinct keys such that the keys in (a) + * could be redistributed. This case should be reserved for later + * once the structural mutations are better understood. + * + * 5. Insert 0x20. This key is directed into the same buddy bucket + * as the 0x01 keys that we have been inserting. That buddy bucket + * is already full and, further, it is the only buddy bucket on the + * page. Since we are not inserting a duplicate key we can split the + * buddy bucket (rather than doubling the size of the bucket). + * However, since global depth == local depth (i.e., only one buddy + * bucket on the page), this split will introduce a new directory + * page. The new directory page basically codes for the additional + * prefix bits required to differentiate the two distinct keys such + * that they are directed into the appropriate buckets. + * + * The new directory page (d) is inserted one level below the root + * on the path leading to (a). The new directory must have a local + * depth of ONE (1), since it will add a one bit distinction. Since + * we know the global depth of the root and the local depth of the + * new directory page, we solve for npointers := 1 << (globalDepth - + * localDepth). This tells us that we will have 2 pointers in the + * root to the new directory page. + * + * The precondition state of root is {a,c,b,b}. Since we know that + * we need npointers:=2 pointers to (d), this means that we will + * copy the {a,c} references into (d) and replace those references + * in the root with references to (d). The root is now {d,d,b,b}. d + * is now {a,a;c,c}. Since d has a local depth of 1 and address bits + * of 2, it is comprised of two buddy hash tables {a,a} and {c,c}. + * + * Linking in (d) has also changes the local depths of (a) and (c). + * Since they each now have npointers:=2, their localDepth is has + * been reduced from TWO (2) to ONE (1) (and their transient cached + * depth values must be either invalidated or recomputed). Note that + * the local depth of (d) and its children (a,c)) are ONE after this + * operation so if we force another split in (a) that will force a + * split in (d). + * + * Having introduced a new directory page into the hash tree, we now + * retry the insert. Once again, the insert is directed into (a). + * Since (a) is still full it is split. (d) is now the parent of + * (a). Once again, we have globalDepth(d=1)==localDepth(a=1) so we + * need to split (d). However, since localDepth(d=1) is less than + * globalDepth(root=2) we can split the buddy hash tables in (d). + * This will require us to allocate a new page (f) which will be the + * right sibling of (d). There are TWO (2) buddy hash tables in (d). + * They are now redistributed between (d) and (f). We also have to + * update the pointers to (d) in the parent such that 1/2 of them + * point to the new right sibling of (d). Since we have changed the + * #of pointers to (d) (from 2 to 1) the local depth of (d) (and of + * f) is now TWO (2). Since globalDepth(d=2) is greater than + * localDepth(a=1) we can now split (a) into (a,e), redistribute the + * tuples in the sole buddy page (a) between (a,e) and update the + * pointers in (d) to (a,a,e,e). + * + * Having split (d) into (d,f), we now retry the insert. This time + * the insert is directed into (e). There is room in (e) (it is + * empty) and the tuple is inserted without further mutation to the + * structure of the hash tree. + * + * TODO At this point we should also prune the buckets [b] and [c] + * since they are empty and replace them with null references. + * + * TODO The #of new directory levels which have to be introduced + * here is a function of the #of prefix bits which have to be + * consumed before a distinction can be made between the existing + * key (0x01) and the new key (0x20). With an address space of 2 + * bits, each directory level examines the next 2-bits of the key. + * The key (x20) was chosen since the distinction can be made by + * adding only one directory level (the keys differ in the first 4 + * bits). [Do an alternative example which requires recursive splits + * in order to verify that we reenter the logic correctly each time. + * E.g., by inserting 0x02 rather than 0x20.] + * + * TODO Do an example in which we explore an insert which introduces + * a new directory level in a 3-level tree. This should be a driven + * by a single insert so we can examine in depth how the new + * directory is introduced and verify whether it is introduced below + * the root or above the bucket. [I believe that it is introduced + * immediately below below the root. Note that a balanced tree, + * e.g., a B-Tree, introduces the new level above the root. However, + * the HTree is intended to be unbalanced in order to optimize + * storage and access times to the parts of the index which + * correspond to unequal distributions in the hash codes.] + */ +// htree.insert(new byte[] { 0x20 }, new byte[] { 0x20 }); +// assertEquals("nnodes", 2, htree.getNodeCount()); +// assertEquals("nleaves", 4, htree.getLeafCount()); +// assertEquals("nentries", 5, htree.getEntryCount()); +// htree.dump(Level.ALL, System.err, true/* materialize */); +// assertTrue(root == htree.getRoot()); +// assertEquals(4, root.childRefs.length); +// final DirectoryPage d = (DirectoryPage)root.childRefs[0].get(); +// assertTrue(d == (DirectoryPage) root.childRefs[0].get()); +// assertTrue(d == (DirectoryPage) root.childRefs[1].get()); +// assertTrue(b == (BucketPage) root.childRefs[2].get()); +// assertTrue(b == (BucketPage) root.childRefs[3].get()); +// assertEquals(4, d.childRefs.length); +// final BucketPage e = (BucketPage)d.childRefs[1].get(); +// assertTrue(a == (BucketPage) d.childRefs[0].get()); +// assertTrue(e == (BucketPage) d.childRefs[1].get()); +// assertTrue(c == (BucketPage) d.childRefs[2].get()); +// assertTrue(c == (BucketPage) d.childRefs[3].get()); +// assertEquals(2, root.getGlobalDepth()); +// assertEquals(2, d.getGlobalDepth()); +// assertEquals(2, a.getGlobalDepth());// unchanged +// assertEquals(2, e.getGlobalDepth());// same as [a]. +// assertEquals(1, b.getGlobalDepth());// unchanged. +// assertEquals(2, c.getGlobalDepth());// unchanged. +// assertTrue(htree.contains(new byte[] { 0x01 })); +// assertFalse(htree.contains(new byte[] { 0x02 })); +// assertEquals(new byte[] { 0x01 }, htree +// .lookupFirst(new byte[] { 0x01 })); +// assertNull(htree.lookupFirst(new byte[] { 0x02 })); +// AbstractBTreeTestCase.assertSameIterator( +// // +// new byte[][] { new byte[] { 0x01 }, new byte[] { 0x01 }, +// new byte[] { 0x01 }, new byte[] { 0x01 } }, htree +// .lookupAll(new byte[] { 0x01 })); +// AbstractBTreeTestCase.assertSameIterator(// +// new byte[][] {}, htree.lookupAll(new byte[] { 0x02 })); + + // TODO REMOVE (or test suite for remove). + + // TODO Continue progression here? + + } finally { + + store.destroy(); + + } + + } + + /** * Test of basic insert, lookup, and split page operations (including when a * new directory page must be introduced) using an address space with only * TWO (2) bits. @@ -161,7 +543,7 @@ * TODO Verify that we can store keys having more than 2 bits (or 4 * bits in a 4-bit address space) through a deeper hash tree. */ - public void test_example_addressBits2_01() { + public void test_example_addressBits2_01x() { final int addressBits = 2; This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |