/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com 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 */ package com.bigdata.rwstore.sector; import java.io.File; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.LinkedHashSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Logger; import com.bigdata.cache.ConcurrentWeakValueCache; import com.bigdata.counters.CounterSet; import com.bigdata.counters.OneShotInstrument; import com.bigdata.journal.AbstractJournal; import com.bigdata.journal.ICommitter; import com.bigdata.rawstore.IAllocationContext; import com.bigdata.rawstore.IPSOutputStream; import com.bigdata.rwstore.IRawTx; import com.bigdata.rwstore.PSOutputStream; /** * 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 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 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 IAllocationContext, IMemoryManager {//, IStore { private static final transient Logger log = Logger .getLogger(AllocationContext.class); /** * The top-level {@link MemoryManager}. */ private final MemoryManager m_root; /** * The parent {@link IMemoryManager}. */ private final IMemoryManager m_parent; /** * The lock used to serialize all all allocation/deallocation requests. This * is shared by the top-level {@link MemoryManager} to avoid lock ordering * problems. */ private final Lock writeLock; private final Lock readLock; /** * All addresses allocated either directly by this {@link AllocationContext} * or recursively by any {@link AllocationContext} created within this * {@link AllocationContext}. */ private final LinkedHashSet<Long> m_addresses = new LinkedHashSet<Long>(); private final AtomicLong m_allocCount = new AtomicLong(); private final AtomicLong m_userBytes = new AtomicLong(); private final AtomicLong m_slotBytes = new AtomicLong(); /** * Note: Must be either atomic or volatile since accessed without a lock! */ private final AtomicBoolean m_active = new AtomicBoolean(true); final boolean m_isolated; @Override final public void checkActive() { if (!m_active.get()) { throw new IllegalStateException(); } } public AllocationContext(final MemoryManager root, boolean isolated) { if(root == null) throw new IllegalArgumentException(); m_isolated = isolated; m_root = root; m_parent = root; writeLock = root.m_allocationLock.writeLock(); readLock = m_root.m_allocationLock.readLock(); } public AllocationContext(final AllocationContext parent) { if(parent == null) throw new IllegalArgumentException(); m_root = parent.m_root; m_parent = parent; writeLock = m_root.m_allocationLock.writeLock(); readLock = m_root.m_allocationLock.readLock(); m_isolated = parent.m_isolated; } @Override public boolean isIsolated() { return m_isolated; } @Override public long allocate(final ByteBuffer data) { return allocate(data, m_root.isBlocking()/* blocks */); } @Override public long allocate(final ByteBuffer data, final boolean blocks) { if (data == null) throw new IllegalArgumentException(); final long addr = allocate(data.remaining(), blocks); final ByteBuffer[] bufs = get(addr); MemoryManager.copyData(data, bufs); return addr; } @Override public long allocate(final int nbytes) { return allocate(nbytes, true/*blocks*/); } /* * Core impl. */ @Override public long allocate(final int nbytes, final boolean blocks) { writeLock.lock(); try { final long addr = m_parent.allocate(nbytes, blocks); final int rwaddr = MemoryManager.getAllocationAddress(addr); final SectorAllocator sector = m_root.getSector(rwaddr); m_addresses.add(Long.valueOf(addr)); m_allocCount.incrementAndGet(); m_userBytes.addAndGet(nbytes); m_slotBytes.addAndGet(sector.getPhysicalSize(SectorAllocator .getSectorOffset(rwaddr))); return addr; } finally { writeLock.unlock(); } } @Override public void clear() { writeLock.lock(); try { if(log.isDebugEnabled()) log.debug(""); for (Long addr : m_addresses) { m_parent.free(addr); } m_addresses.clear(); m_allocCount.set(0); m_userBytes.set(0); m_slotBytes.set(0); } finally { writeLock.unlock(); } } @Override public void free(final long addr) { final int rwaddr = MemoryManager.getAllocationAddress(addr); final int size = MemoryManager.getAllocationSize(addr); final int offset = SectorAllocator.getSectorOffset(rwaddr); writeLock.lock(); try { final SectorAllocator sector = m_root.getSector(rwaddr); m_parent.free(addr); m_addresses.remove(Long.valueOf(addr)); m_allocCount.decrementAndGet(); m_userBytes.addAndGet(-size); m_slotBytes.addAndGet(-sector.getPhysicalSize(offset)); } finally { writeLock.unlock(); } } @Override public ByteBuffer[] get(final long addr) { return m_root.get(addr); } @Override public byte[] read(final long addr) { return MemoryManager.read(this, addr); } @Override public IMemoryManager createAllocationContext() { return new AllocationContext(this); } @Override public int allocationSize(final long addr) { return m_root.allocationSize(addr); } @Override public long getAllocationCount() { return m_allocCount.get(); } @Override public long getSlotBytes() { return m_slotBytes.get(); } @Override public long getUserBytes() { return m_userBytes.get(); } @Override public CounterSet getCounters() { final CounterSet root = new CounterSet(); // #of allocation slot bytes. root.addCounter("slotBytes", new OneShotInstrument<Long>( getUserBytes())); // #of application data bytes. root.addCounter("userBytes", new OneShotInstrument<Long>( getUserBytes())); // #of allocations spanned by this context. root.addCounter("allocationCount", new OneShotInstrument<Long>( getAllocationCount())); return root; } @Override public IPSOutputStream getOutputStream() { return PSOutputStream.getNew(this, SectorAllocator.BLOB_SIZE+4 /*no checksum*/, null); } @Override public IPSOutputStream getOutputStream(IAllocationContext context) { return PSOutputStream.getNew(this, SectorAllocator.BLOB_SIZE+4 /*no checksum*/, context); } @Override public InputStream getInputStream(long addr) { return new PSInputStream(this, addr); } @Override public long alloc(byte[] buf, int size, IAllocationContext context) { if (context != null) throw new IllegalArgumentException("Nested AllocationContexts are not supported"); return MemoryManager.getAllocationAddress(allocate(ByteBuffer.wrap(buf, 0, size))); // return the rwaddr! } @Override public void close() { clear(); } @Override public void free(long addr, int size) { free((addr << 32) + size); } @Override public int getAssociatedSlotSize(int addr) { return m_root.allocationSize(addr); } @Override public void getData(long l, byte[] buf) { /** * TBD: this could probably be more efficient! */ readLock.lock(); try { final ByteBuffer rbuf = m_root.getBuffer((int) l, buf.length); rbuf.get(buf); } finally { readLock.unlock(); } } @Override public File getStoreFile() { throw new UnsupportedOperationException("The MemoryManager does not provdie a StoreFile"); } @Override public int getSectorSize() { return m_root.getSectorSize(); } @Override public int getMaxSectors() { return m_root.getMaxSectors(); } @Override public int getSectorCount() { return m_root.getSectorCount(); } @Override public void commit() { m_root.commit(); } @Override public Lock getCommitLock() { return m_root.getCommitLock(); } @Override public void postCommit() { m_root.postCommit(); } @Override public void registerExternalCache( ConcurrentWeakValueCache<Long, ICommitter> historicalIndexCache, int byteCount) { m_root.registerExternalCache(historicalIndexCache, byteCount); } @Override public int checkDeferredFrees(AbstractJournal abstractJournal) { return m_root.checkDeferredFrees(abstractJournal); } @Override public IRawTx newTx() { return m_root.newTx(); } @Override public long saveDeferrals() { return m_root.saveDeferrals(); } @Override public long getLastReleaseTime() { return m_root.getLastReleaseTime(); } @Override public void abortContext(final IAllocationContext context) { throw new UnsupportedOperationException(); } @Override public void detachContext(final IAllocationContext context) { throw new UnsupportedOperationException(); } // @Override // public void registerContext(final IAllocationContext context) { // throw new UnsupportedOperationException(); // } // @Override // public void setRetention(final long parseLong) { // throw new UnsupportedOperationException(); // } @Override public boolean isCommitted(final long addr) { return m_root.isCommitted(addr); } @Override public long getPhysicalAddress(final long addr) { return m_root.getPhysicalAddress(addr); } @Override public long allocate(ByteBuffer data, IAllocationContext context) { throw new UnsupportedOperationException(); } @Override public long write(ByteBuffer data, IAllocationContext context) { return allocate(data,context); } @Override public void free(long addr, IAllocationContext context) { throw new UnsupportedOperationException(); } @Override public void delete(long addr, IAllocationContext context) { free(addr,context); } @Override public IAllocationContext newAllocationContext(final boolean isolated) { return this; } @Override public void release() { checkActive(); m_active.set(false); } // 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)); // } // } }