| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.mavibot.btree; |
| |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.FileChannel; |
| import java.util.ArrayList; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Queue; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentLinkedDeque; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import java.util.concurrent.locks.ReentrantLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException; |
| import org.apache.directory.mavibot.btree.exception.CursorException; |
| import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException; |
| import org.apache.directory.mavibot.btree.exception.FileException; |
| import org.apache.directory.mavibot.btree.exception.InvalidOffsetException; |
| import org.apache.directory.mavibot.btree.exception.KeyNotFoundException; |
| import org.apache.directory.mavibot.btree.exception.RecordManagerException; |
| import org.apache.directory.mavibot.btree.serializer.ElementSerializer; |
| import org.apache.directory.mavibot.btree.serializer.IntSerializer; |
| import org.apache.directory.mavibot.btree.serializer.LongArraySerializer; |
| import org.apache.directory.mavibot.btree.serializer.LongSerializer; |
| import org.apache.directory.mavibot.btree.util.Strings; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.github.benmanes.caffeine.cache.Cache; |
| import com.github.benmanes.caffeine.cache.Caffeine; |
| |
| |
| /** |
| * The RecordManager is used to manage the file in which we will store the b-trees. |
| * A RecordManager will manage more than one B-tree.<br/> |
| * |
| * It stores data in fixed size pages (default size is 512 bytes), which may be linked one to |
| * the other if the data we want to store is too big for a page. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class RecordManager implements TransactionManager |
| { |
| /** The LoggerFactory used by this class */ |
| protected static final Logger LOG = LoggerFactory.getLogger( RecordManager.class ); |
| |
| /** The LoggerFactory used to trace TXN operations */ |
| protected static final Logger TXN_LOG = LoggerFactory.getLogger( "TXN_LOG" ); |
| |
| /** The LoggerFactory used for Pages operations logging */ |
| protected static final Logger LOG_PAGES = LoggerFactory.getLogger( "org.apache.directory.mavibot.LOG_PAGES" ); |
| |
| /** A dedicated logger for the check */ |
| protected static final Logger LOG_CHECK = LoggerFactory.getLogger( "org.apache.directory.mavibot.LOG_CHECK" ); |
| |
| /** The associated file */ |
| private File file; |
| |
| /** The channel used to read and write data */ |
| /* no qualifier */FileChannel fileChannel; |
| |
| /** The RecordManager header */ |
| private AtomicReference<RecordManagerHeader> recordManagerHeaderReference = new AtomicReference<>(); |
| |
| /** Some counters to track the number of free pages */ |
| private AtomicLong nbFreedPages = new AtomicLong( 0 ); |
| private AtomicLong nbCreatedPages = new AtomicLong( 0 ); |
| private AtomicLong nbReusedPages = new AtomicLong( 0 ); |
| private AtomicLong nbUpdateRMHeader = new AtomicLong( 0 ); |
| private AtomicLong nbUpdateBtreeHeader = new AtomicLong( 0 ); |
| private AtomicLong nbUpdatePageIOs = new AtomicLong( 0 ); |
| public AtomicLong nbCacheHits = new AtomicLong( 0 ); |
| public AtomicLong nbCacheMisses = new AtomicLong( 0 ); |
| |
| /** |
| * A B-tree used to manage the page that has been copied in a new version. |
| * Those pages can be reclaimed when the associated version is dead. |
| **/ |
| /* no qualifier */BTree<RevisionName, long[]> copiedPageBtree; |
| |
| /** The RecordManager header size */ |
| /* no qualifier */static int recordManagerHeaderSize = BTreeConstants.DEFAULT_PAGE_SIZE; |
| |
| /** A global buffer used to store the RecordManager header */ |
| private ByteBuffer recordManagerHeaderBuffer; |
| |
| /** A static buffer used to store the RecordManager header */ |
| private byte[] recordManagerHeaderBytes; |
| |
| /** The length of an Offset, as a negative value */ |
| //private byte[] LONG_LENGTH = new byte[] |
| // { ( byte ) 0xFF, ( byte ) 0xFF, ( byte ) 0xFF, ( byte ) 0xF8 }; |
| |
| /** The set of managed B-trees */ |
| private Map<String, BTree<Object, Object>> managedBtrees; |
| |
| /** The queue of recently closed transactions */ |
| private Queue<RevisionName> closedTransactionsQueue = new LinkedBlockingQueue<>(); |
| |
| /** A flag set to true if we want to keep old revisions */ |
| private boolean keepRevisions; |
| |
| /** The List of B-trees */ |
| /* no qualifier */List<BTree> listOfBtrees; |
| |
| /** A lock to protect the transaction handling */ |
| private ReentrantLock transactionLock = new ReentrantLock(); |
| |
| /** The list of PageIO that can be freed after a commit */ |
| List<PageIO> freedPages = new ArrayList<>(); |
| |
| /** The list of PageIO that can be freed after a roolback */ |
| private List<PageIO> allocatedPages = new ArrayList<>(); |
| |
| /** A Map keeping the latest revisions for each managed BTree */ |
| private Map<String, BTreeHeader<?, ?>> currentBTreeHeaders = new HashMap<>(); |
| |
| /** A Map storing the new revisions when some change have been made in some BTrees */ |
| private Map<String, BTreeHeader<?, ?>> newBTreeHeaders = new HashMap<>(); |
| |
| /** A lock to protect the BtreeHeader maps */ |
| private ReadWriteLock btreeHeadersLock = new ReentrantReadWriteLock(); |
| |
| /** A lock to protect the freepage pointers */ |
| private ReentrantLock freePageLock = new ReentrantLock(); |
| |
| /** the space reclaimer */ |
| private PageReclaimer reclaimer; |
| |
| /* a flag used to disable the free page reclaimer (used for internal testing only) */ |
| private boolean disableReclaimer = false; |
| |
| public Map<Long, Integer> writeCounter = new HashMap<>(); |
| |
| /** The page cache */ |
| private Cache<Long, Page> pageCache; |
| |
| /** The active transactions list */ |
| Deque<RecordManagerHeader> activeTransactionsList = new ConcurrentLinkedDeque<>(); |
| |
| /** The dead transactions list */ |
| Deque<RecordManagerHeader> deadTransactionsList = new ConcurrentLinkedDeque<>(); |
| |
| /** |
| * Create a Record manager which will either create the underlying file |
| * or load an existing one. If a folder is provided, then we will create |
| * a file with a default name : mavibot.db |
| * |
| * @param name The file name, or a folder name |
| */ |
| public RecordManager( String fileName ) |
| { |
| this( fileName, BTreeConstants.DEFAULT_PAGE_SIZE, BTreeConstants.DEFAULT_CACHE_SIZE ); |
| } |
| |
| |
| /** |
| * Create a Record manager which will either create the underlying file |
| * or load an existing one. If a folder is provider, then we will create |
| * a file with a default name : mavibot.db |
| * |
| * @param name The file name, or a folder name |
| * @param pageSize the size of a page on disk, in bytes |
| */ |
| public RecordManager( String fileName, int pageSize, int cacheSize ) |
| { |
| // Create the RMH |
| RecordManagerHeader recordManagerHeader = new RecordManagerHeader(); |
| |
| // Create the map of managed b-trees |
| managedBtrees = new LinkedHashMap<>(); |
| |
| // The page size can't be lower than 512 |
| if ( pageSize < BTreeConstants.MIN_PAGE_SIZE ) |
| { |
| recordManagerHeader.pageSize = BTreeConstants.MIN_PAGE_SIZE; |
| } |
| else |
| { |
| recordManagerHeader.pageSize = pageSize; |
| } |
| |
| recordManagerHeaderReference.set( recordManagerHeader ); |
| recordManagerHeaderBuffer = ByteBuffer.allocate( recordManagerHeader.pageSize ); |
| recordManagerHeaderBytes = new byte[recordManagerHeader.pageSize]; |
| recordManagerHeaderSize = recordManagerHeader.pageSize; |
| |
| // Open the file or create it |
| File tmpFile = new File( fileName ); |
| |
| if ( tmpFile.isDirectory() ) |
| { |
| // It's a directory. Check that we don't have an existing mavibot file |
| tmpFile = new File( tmpFile, BTreeConstants.DEFAULT_FILE_NAME ); |
| } |
| |
| // Create the cache |
| pageCache = Caffeine.newBuilder().maximumSize( cacheSize ).build(); |
| |
| // We have to create a new file, if it does not already exist |
| boolean isNewFile = createFile( tmpFile ); |
| |
| // Create the file, or load it if it already exists |
| try |
| { |
| RandomAccessFile randomFile = new RandomAccessFile( file, "rw" ); |
| |
| fileChannel = randomFile.getChannel(); |
| |
| if ( isNewFile ) |
| { |
| initRecordManager(); |
| } |
| else |
| { |
| loadRecordManager(); |
| } |
| |
| // Create the unused pages reclaimer |
| reclaimer = new PageReclaimer( this ); |
| //runReclaimer(); |
| } |
| catch ( Exception e ) |
| { |
| LOG.error( "Error while initializing the RecordManager : {}", e.getMessage() ); |
| LOG.error( "", e ); |
| throw new RecordManagerException( e ); |
| } |
| } |
| |
| |
| /** |
| * runs the PageReclaimer to free the copied pages |
| */ |
| private void runReclaimer() |
| { |
| if ( disableReclaimer ) |
| { |
| LOG.warn( "Free page reclaimer is disabled, this should not be disabled on production systems." ); |
| return; |
| } |
| |
| try |
| { |
| reclaimer.reclaim(); |
| // must update the headers after reclaim operation |
| RecordManagerHeader recordManagerHeader = recordManagerHeaderReference.get(); |
| writeRecordManagerHeader( recordManagerHeader ); |
| } |
| catch ( Exception e ) |
| { |
| LOG.warn( "PageReclaimer failed to free the pages", e ); |
| } |
| } |
| |
| |
| /** |
| * Create the mavibot file if it does not exist |
| */ |
| private boolean createFile( File mavibotFile ) |
| { |
| try |
| { |
| boolean creation = mavibotFile.createNewFile(); |
| |
| file = mavibotFile; |
| |
| if ( mavibotFile.length() == 0 ) |
| { |
| return true; |
| } |
| else |
| { |
| return creation; |
| } |
| } |
| catch ( IOException ioe ) |
| { |
| LOG.error( "Cannot create the file {}", mavibotFile.getName() ); |
| return false; |
| } |
| } |
| |
| |
| /** |
| * We will create a brand new RecordManager file, containing nothing, but the RecordManager header, |
| * a B-tree to manage the old revisions we want to keep and |
| * a B-tree used to manage pages associated with old versions. |
| * <br/> |
| * The RecordManager header contains the following details : |
| * <pre> |
| * +--------------------------+ |
| * | PageSize | 4 bytes : The size of a physical page (default to 4096) |
| * +--------------------------+ |
| * | Nb B-Trees | 4 bytes : The number of managed B-trees (zero or more) |
| * +--------------------------+ |
| * | Revision | 8 bytes : The current revision |
| * +--------------------------+ |
| * | FirstFree offset | 8 bytes : The offset of the first free page |
| * +--------------------------+ |
| * | list of B-trees offset | 8 bytes : The offset of the current list of B-trees |
| * +--------------------------+ |
| * | current CP btree offset | 8 bytes : The offset of the current BoB |
| * +--------------------------+ |
| * | idCounter | 8 bytes : The page ID counter (an incremental counter) |
| * +--------------------------+ |
| * </pre> |
| * |
| * We then store the B-tree managing the pages that have been copied when we have added |
| * or deleted an element in the B-tree. They are associated with a version. |
| * |
| * Last, we add the bTree that keep a track on each revision we can have access to. |
| */ |
| private void initRecordManager() throws IOException |
| { |
| // Create a new Header |
| RecordManagerHeader recordManagerHeader = recordManagerHeaderReference.get(); |
| recordManagerHeader.nbBtree = 0; |
| recordManagerHeader.firstFreePage = BTreeConstants.NO_PAGE; |
| recordManagerHeader.currentListOfBtreesOffset = BTreeConstants.NO_PAGE; |
| recordManagerHeader.idCounter = 0L; |
| |
| writeRecordManagerHeader( recordManagerHeader ); |
| |
| // First, create the list of btrees <RevisionName, Long> |
| listOfBtrees = new ArrayList<>(); |
| |
| WriteTransaction transaction = null; |
| |
| try |
| { |
| transaction = beginWriteTransaction(); |
| // Now, create the Copied Page B-tree |
| BTree<RevisionName, long[]> copiedPagesBtree = createCopiedPagesBtree( transaction ); |
| |
| // The Copied Pages B-tree |
| writeManagementTree( transaction, copiedPagesBtree ); |
| transaction.recordManagerHeader.copiedPagesBtree = copiedPagesBtree; |
| transaction.recordManagerHeader.nbBtree++; |
| recordManagerHeader.copiedPagesBtree = copiedPagesBtree; |
| |
| transaction.commit(); |
| } |
| catch ( IOException ioe ) |
| { |
| if ( transaction != null ) |
| { |
| transaction.abort(); |
| } |
| } |
| } |
| |
| |
| /** |
| * Create the CopiedPagesBtree |
| */ |
| private BTree<RevisionName, long[]> createCopiedPagesBtree( Transaction transaction ) |
| { |
| BTreeConfiguration<RevisionName, long[]> configuration = new BTreeConfiguration<>(); |
| configuration.setKeySerializer( RevisionNameSerializer.INSTANCE ); |
| configuration.setName( BTreeConstants.COPIED_PAGE_BTREE_NAME ); |
| configuration.setValueSerializer( LongArraySerializer.INSTANCE ); |
| configuration.setBtreeType( BTreeTypeEnum.COPIED_PAGES_BTREE ); |
| |
| return BTreeFactory.createBTree( transaction, configuration ); |
| } |
| |
| |
| private void loadListOfBtrees( RecordManagerHeader recordManagerHeader, Map<String, Long> loadedBtrees ) |
| throws EndOfFileExceededException, IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, IllegalArgumentException, SecurityException, NoSuchFieldException |
| { |
| PageIO[] lobHeaderPageIos = readPageIOs( recordManagerHeader.pageSize, |
| recordManagerHeader.currentListOfBtreesOffset, Long.MAX_VALUE ); |
| |
| // The B-tree header page ID |
| long pos = 0L; |
| long id = readLong( recordManagerHeader.pageSize, lobHeaderPageIos, pos ); |
| pos += BTreeConstants.LONG_SIZE; |
| |
| int nbLists = readInt( recordManagerHeader.pageSize, lobHeaderPageIos, pos ); |
| pos += BTreeConstants.INT_SIZE; |
| |
| List<Long> deadBtrees = new ArrayList<>(); |
| |
| for ( int i = 0; i < nbLists; i++ ) |
| { |
| int nbBtrees = readInt( recordManagerHeader.pageSize, lobHeaderPageIos, pos ); |
| pos += BTreeConstants.INT_SIZE; |
| |
| for ( int j = 0; j < nbBtrees; j++ ) |
| { |
| long revision = readLong( recordManagerHeader.pageSize, lobHeaderPageIos, pos ); |
| pos += BTreeConstants.LONG_SIZE; |
| |
| long btreeOffset = readLong( recordManagerHeader.pageSize, lobHeaderPageIos, pos ); |
| pos += BTreeConstants.LONG_SIZE; |
| |
| if ( i > 0 ) |
| { |
| deadBtrees.add( btreeOffset ); |
| } |
| else |
| { |
| // We need to read the Btree info |
| PageIO[] btreePageIos = readPageIOs( recordManagerHeader.pageSize, btreeOffset, Long.MAX_VALUE ); |
| |
| BTree<?, ?> btree = new BTree<>(); |
| Transaction readTxn = null; |
| |
| try |
| { |
| readTxn = new ReadTransaction( this ); |
| loadBtree( readTxn, btreePageIos, btree ); |
| readTxn.commit(); |
| } |
| catch ( IOException ioe ) |
| { |
| if ( readTxn != null ) |
| { |
| readTxn.abort(); |
| } |
| } |
| |
| // Add the btree into the map of managed B-trees |
| recordManagerHeader.btreeMap.put( btree.getName(), ( BTree<Object, Object> ) btree ); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Load the BTrees from the disk. |
| * |
| * @throws InstantiationException |
| * @throws IllegalAccessException |
| * @throws ClassNotFoundException |
| * @throws NoSuchFieldException |
| * @throws SecurityException |
| * @throws IllegalArgumentException |
| * @throws CursorException |
| */ |
| private void loadRecordManager() throws IOException, ClassNotFoundException, IllegalAccessException, |
| InstantiationException, IllegalArgumentException, SecurityException, NoSuchFieldException, KeyNotFoundException, CursorException |
| { |
| RecordManagerHeader recordManagerHeader = recordManagerHeaderReference.get(); |
| |
| if ( fileChannel.size() != 0 ) |
| { |
| ByteBuffer rmhBytes = ByteBuffer.allocate( recordManagerHeaderSize ); |
| |
| // The file exists, we have to load the data now |
| fileChannel.read( rmhBytes ); |
| |
| rmhBytes.rewind(); |
| |
| // read the RecordManager Header : |
| // +---------------------+ |
| // | PageSize | 4 bytes : The size of a physical page (default to 4096) |
| // +---------------------+ |
| // | NbTree | 4 bytes : The number of managed B-trees (at least 1) |
| // +---------------------+ |
| // | Revision | 8 bytes : The current revision |
| // +---------------------+ |
| // | FirstFree | 8 bytes : The offset of the first free page |
| // +---------------------+ |
| // | List of B-trees | 8 bytes : The offset of the list of B-trees |
| // +---------------------+ |
| // | CPB offset | 8 bytes : The offset of the current Copied Pages B-tree |
| // +---------------------+ |
| // | ID | 8 bytes : The ID counter |
| // +---------------------+ |
| recordManagerHeader.pageSize = rmhBytes.getInt(); |
| |
| // The number of managed B-trees |
| recordManagerHeader.nbBtree = rmhBytes.getInt(); |
| |
| // The current revision |
| recordManagerHeader.revision = rmhBytes.getLong(); |
| |
| // The first free page |
| recordManagerHeader.firstFreePage = rmhBytes.getLong(); |
| |
| // Read all the free pages |
| checkFreePages(); |
| |
| // The LOB offset |
| recordManagerHeader.currentListOfBtreesOffset = rmhBytes.getLong(); |
| |
| // The current Copied Pages B-tree offset |
| recordManagerHeader.currentCopiedPagesBtreeOffset = rmhBytes.getLong(); |
| |
| // The current Copied Pages B-tree offset |
| recordManagerHeader.idCounter = rmhBytes.getLong(); |
| |
| // Set the last offset, which is the file's size |
| recordManagerHeader.lastOffset = fileChannel.size(); |
| |
| // read the list of B-trees |
| Transaction transaction = null; |
| |
| try |
| { |
| transaction = beginReadTransaction(); |
| |
| // read the copied page B-tree |
| PageIO[] copiedPagesPageIos = readPageIOs( recordManagerHeader.pageSize, recordManagerHeader.currentCopiedPagesBtreeOffset, Long.MAX_VALUE ); |
| |
| recordManagerHeader.copiedPagesBtree = BTreeFactory.<RevisionName, long[]> createBTree( BTreeTypeEnum.COPIED_PAGES_BTREE ); |
| //( ( BTree<RevisionName, long[]> ) recordManagerHeader.copiedPagesBtree ).setRecordManagerHeader( transaction.getRecordManagerHeader() ); |
| |
| loadBtree( transaction, copiedPagesPageIos, recordManagerHeader.copiedPagesBtree ); |
| |
| transaction.commit(); |
| } |
| catch ( IOException ioe ) |
| { |
| if ( transaction != null ) |
| { |
| transaction.abort(); |
| } |
| } |
| |
| Map<String, Long> loadedBtrees = new HashMap<>(); |
| |
| // Now, read all the B-trees from the btree of btrees |
| loadListOfBtrees( recordManagerHeader, loadedBtrees ); |
| |
| /* |
| Transaction readTxn = null; |
| |
| try |
| { |
| readTxn = beginReadTransaction(); |
| TupleCursor<NameRevision, Long> btreeCursor = null; //readTxn.getRecordManagerHeader().listOfBtrees.browse( transaction ); |
| |
| // loop on all the btrees we have, and keep only the latest revision |
| long currentRevision = -1L; |
| |
| while ( btreeCursor.hasNext() ) |
| { |
| Tuple<NameRevision, Long> btreeTuple = btreeCursor.next(); |
| NameRevision nameRevision = btreeTuple.getKey(); |
| long btreeOffset = btreeTuple.getValue(); |
| long revision = nameRevision.getValue(); |
| |
| // Check if we already have processed this B-tree |
| Long loadedBtreeRevision = loadedBtrees.get( nameRevision.getName() ); |
| |
| if ( loadedBtreeRevision != null ) |
| { |
| // The btree has already been loaded. The revision is necessarily higher |
| if ( revision > currentRevision ) |
| { |
| // We have a newer revision : switch to the new revision (we keep the offset atm) |
| loadedBtrees.put( nameRevision.getName(), btreeOffset ); |
| currentRevision = revision; |
| } |
| } |
| else |
| { |
| // This is a new B-tree |
| loadedBtrees.put( nameRevision.getName(), btreeOffset ); |
| currentRevision = nameRevision.getRevision(); |
| } |
| } |
| |
| // TODO : clean up the old revisions... |
| |
| // Now, we can load the real btrees using the offsets |
| for ( Map.Entry<String, Long> loadedBtree : loadedBtrees.entrySet() ) |
| { |
| long btreeOffset = loadedBtree.getValue(); |
| |
| PageIO[] btreePageIos = readPageIOs( recordManagerHeader.pageSize, btreeOffset, Long.MAX_VALUE ); |
| |
| BTree<?, ?> btree = new BTree<>(); |
| loadBtree( readTxn, btreePageIos, btree ); |
| |
| // Add the btree into the map of managed B-trees |
| recordManagerHeader.btreeMap.put( loadedBtree.getKey(), ( BTree<Object, Object> ) btree ); |
| } |
| |
| readTxn.commit(); |
| } |
| catch ( IOException ioe ) |
| { |
| if ( readTxn != null ) |
| { |
| readTxn.abort(); |
| } |
| } |
| */ |
| } |
| } |
| |
| |
| private <K, V> BTree<K, V> readBTree( Transaction transaction, long btreeOffset ) throws NoSuchFieldException, InstantiationException, EndOfFileExceededException, |
| IOException, IllegalAccessException, ClassNotFoundException |
| { |
| PageIO[] btreePageIos = readPageIOs( transaction.getRecordManagerHeader().pageSize, btreeOffset, Long.MAX_VALUE ); |
| |
| BTree<K, V> btree = BTreeFactory.<K, V> createBTree(); |
| loadBtree( transaction, btreePageIos, btree ); |
| |
| return btree; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Transaction beginReadTransaction() |
| { |
| return beginReadTransaction( Transaction.DEFAULT_TIMEOUT ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Transaction beginReadTransaction( long timeout ) |
| { |
| if ( TXN_LOG.isDebugEnabled() ) |
| { |
| TXN_LOG.debug( "Begining a new Read Transaction on thread {}", |
| Thread.currentThread().getName() ); |
| } |
| |
| Transaction transaction = new ReadTransaction( this, timeout ); |
| |
| transaction.getRecordManagerHeader().txnCounter.getAndIncrement(); |
| |
| return transaction; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public WriteTransaction beginWriteTransaction() |
| { |
| if ( TXN_LOG.isDebugEnabled() ) |
| { |
| TXN_LOG.debug( "Begining a new write transaction on thread {}", |
| Thread.currentThread().getName() ); |
| } |
| |
| // First, take the lock if it's not already taken |
| if ( !transactionLock.isHeldByCurrentThread() ) |
| { |
| TXN_LOG.debug( "--> Lock taken" ); |
| transactionLock.lock(); |
| } |
| else |
| { |
| TXN_LOG.debug( "..o The current thread already holds the lock" ); |
| } |
| |
| return new WriteTransaction( this ); |
| } |
| |
| |
| /** |
| * Reads all the PageIOs that are linked to the page at the given position, including |
| * the first page. |
| * |
| * @param position The position of the first page |
| * @param limit The maximum bytes to read. Set this value to -1 when the size is unknown. |
| * @return An array of pages |
| */ |
| /*no qualifier*/PageIO[] readPageIOs( int pageSize, long position, long limit ) |
| throws IOException, EndOfFileExceededException |
| { |
| LOG.debug( "Read PageIOs at position {}", position ); |
| |
| if ( limit <= 0 ) |
| { |
| limit = Long.MAX_VALUE; |
| } |
| |
| PageIO firstPage = fetchPageIO( pageSize, position ); |
| firstPage.setSize(); |
| List<PageIO> listPages = new ArrayList<>(); |
| listPages.add( firstPage ); |
| long dataRead = pageSize - BTreeConstants.LONG_SIZE - BTreeConstants.INT_SIZE; |
| |
| // Iterate on the pages, if needed |
| long nextPage = firstPage.getNextPage(); |
| |
| if ( ( dataRead < limit ) && ( nextPage != BTreeConstants.NO_PAGE ) ) |
| { |
| while ( dataRead < limit ) |
| { |
| PageIO page = fetchPageIO( pageSize, nextPage ); |
| listPages.add( page ); |
| nextPage = page.getNextPage(); |
| dataRead += pageSize - BTreeConstants.LONG_SIZE; |
| |
| if ( nextPage == BTreeConstants.NO_PAGE ) |
| { |
| page.setNextPage( BTreeConstants.NO_PAGE ); |
| break; |
| } |
| } |
| } |
| |
| LOG.debug( "Nb of PageIOs read : {}", listPages.size() ); |
| |
| // Return |
| return listPages.toArray( new PageIO[] |
| {} ); |
| } |
| |
| |
| /** |
| * Reads all the PageIOs that are linked to the page at the given position, including |
| * the first page. |
| * |
| * @param position The position of the first page |
| * @param limit The maximum bytes to read. Set this value to -1 when the size is unknown. |
| * @return An array of pages |
| */ |
| /*no qualifier*/static PageIO[] readPageIOs( RecordManagerHeader recordManagerHeader, FileChannel fileChannel, int pageSize, long position, long limit ) |
| throws IOException, EndOfFileExceededException |
| { |
| LOG.debug( "Read PageIOs at position {}", position ); |
| |
| if ( limit <= 0 ) |
| { |
| limit = Long.MAX_VALUE; |
| } |
| |
| PageIO firstPage = fetchPageIO( recordManagerHeader, fileChannel, pageSize, position ); |
| firstPage.setSize(); |
| List<PageIO> listPages = new ArrayList<>(); |
| listPages.add( firstPage ); |
| long dataRead = pageSize - BTreeConstants.LONG_SIZE - BTreeConstants.INT_SIZE; |
| |
| // Iterate on the pages, if needed |
| long nextPage = firstPage.getNextPage(); |
| |
| if ( ( dataRead < limit ) && ( nextPage != BTreeConstants.NO_PAGE ) ) |
| { |
| while ( dataRead < limit ) |
| { |
| PageIO page = fetchPageIO( recordManagerHeader, fileChannel, pageSize, nextPage ); |
| listPages.add( page ); |
| nextPage = page.getNextPage(); |
| dataRead += pageSize - BTreeConstants.LONG_SIZE; |
| |
| if ( nextPage == BTreeConstants.NO_PAGE ) |
| { |
| page.setNextPage( BTreeConstants.NO_PAGE ); |
| break; |
| } |
| } |
| } |
| |
| LOG.debug( "Nb of PageIOs read : {}", listPages.size() ); |
| |
| // Return |
| return listPages.toArray( new PageIO[] |
| {} ); |
| } |
| |
| |
| /** |
| * Check the offset to be sure it's a valid one : |
| * <ul> |
| * <li>It's >= 0</li> |
| * <li>It's below the end of the file</li> |
| * <li>It's a multiple of the pageSize |
| * </ul> |
| * @param offset The offset to check |
| * @throws InvalidOffsetException If the offset is not valid |
| */ |
| /* no qualifier */static void checkOffset( RecordManagerHeader recordManagerHeader, long offset ) |
| { |
| if ( ( offset < 0 ) || ( offset > recordManagerHeader.lastOffset ) || ( ( offset % recordManagerHeader.pageSize ) != 0 ) ) |
| { |
| throw new InvalidOffsetException( "Bad Offset : " + Long.toHexString( offset ) ); |
| } |
| } |
| |
| |
| /** |
| * Check the offset to be sure it's a valid one : |
| * <ul> |
| * <li>It's >= 0</li> |
| * <li>It's below the end of the file</li> |
| * <li>It's a multiple of the pageSize |
| * </ul> |
| * @param offset The offset to check |
| * @throws InvalidOffsetException If the offset is not valid |
| */ |
| /* no qualifier */void checkOffset( long offset ) |
| { |
| RecordManagerHeader recordManagerHeader = recordManagerHeaderReference.get(); |
| |
| if ( ( offset < 0 ) || ( offset > recordManagerHeader.lastOffset ) || ( ( offset % recordManagerHeader.pageSize ) != 0 ) ) |
| { |
| throw new InvalidOffsetException( "Bad Offset : " + offset ); |
| } |
| } |
| |
| |
| /** |
| * Read a B-tree from the disk. The meta-data are at the given position in the list of pages. |
| * We load a B-tree in two steps : first, we load the B-tree header, then the common informations |
| * |
| * @param pageIos The list of pages containing the meta-data |
| * @param btree The B-tree we have to initialize |
| * @throws InstantiationException |
| * @throws IllegalAccessException |
| * @throws ClassNotFoundException |
| * @throws NoSuchFieldException |
| * @throws SecurityException |
| * @throws IllegalArgumentException |
| */ |
| private <K, V> void loadBtree( Transaction transaction, PageIO[] pageIos, BTree<K, V> btree ) throws EndOfFileExceededException, |
| IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, IllegalArgumentException, |
| SecurityException, NoSuchFieldException |
| { |
| loadBtree( transaction, pageIos, btree, null ); |
| } |
| |
| |
| /** |
| * Read a B-tree from the disk. The meta-data are at the given position in the list of pages. |
| * We load a B-tree in two steps : first, we load the B-tree header, then the common informations |
| * |
| * @param pageIos The list of pages containing the meta-data |
| * @param btree The b-tree we have to initialize |
| * @throws IOException |
| * @throws ClassNotFoundException |
| * @throws IllegalAccessException |
| * @throws InstantiationException |
| * @throws NoSuchFieldException |
| */ |
| /* no qualifier */<K, V> void loadBtree( Transaction transaction, PageIO[] pageIos, BTree<K, V> btree, BTree<K, V> parentBTree ) |
| throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException |
| { |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| int pageSize = recordManagerHeader.pageSize; |
| BTreeInfo<K, V> btreeInfo = new BTreeInfo<>(); |
| long dataPos = 0L; |
| |
| // Process the B-tree header |
| BTreeHeader<K, V> btreeHeader = new BTreeHeader<>( btreeInfo ); |
| |
| // The BtreeHeader offset |
| btreeHeader.setOffset( pageIos[0].getOffset() ); |
| |
| // The B-tree header page ID |
| long id = readLong( pageSize, pageIos, dataPos ); |
| btreeHeader.setId( id ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The B-tree current revision |
| long revision = readLong( pageSize, pageIos, dataPos ); |
| btreeHeader.setRevision( revision ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The nb elems in the tree |
| long nbElems = readLong( pageSize, pageIos, dataPos ); |
| btreeHeader.setNbElems( nbElems ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The b-tree rootPage offset |
| long rootPageOffset = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The B-tree information offset |
| long btreeInfoOffset = readLong( pageSize, pageIos, dataPos ); |
| btree.setBtreeInfo( btreeInfo ); |
| |
| // Now, process the common informations |
| PageIO[] infoPageIos = readPageIOs( recordManagerHeader.pageSize, btreeInfoOffset, Long.MAX_VALUE ); |
| dataPos = 0L; |
| |
| // The B-tree page numbers of elements |
| int btreePageNbElem = readInt( pageSize, infoPageIos, dataPos ); |
| BTreeFactory.setPageNbElem( btree, btreePageNbElem ); |
| dataPos += BTreeConstants.INT_SIZE; |
| |
| // The tree name |
| ByteBuffer btreeNameBytes = readBytes( pageSize, infoPageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE + btreeNameBytes.limit(); |
| String btreeName = Strings.utf8ToString( btreeNameBytes ); |
| BTreeFactory.setName( btree, btreeName ); |
| |
| // The keySerializer FQCN |
| ByteBuffer keySerializerBytes = readBytes( pageSize, infoPageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE + keySerializerBytes.limit(); |
| |
| String keySerializerFqcn = Strings.utf8ToString( keySerializerBytes ); |
| |
| BTreeFactory.setKeySerializer( btree, keySerializerFqcn ); |
| |
| // The valueSerialier FQCN |
| ByteBuffer valueSerializerBytes = readBytes( pageSize, infoPageIos, dataPos ); |
| |
| String valueSerializerFqcn = Strings.utf8ToString( valueSerializerBytes ); |
| |
| BTreeFactory.setValueSerializer( btree, valueSerializerFqcn ); |
| |
| // Update the BtreeHeader reference |
| btree.setBtreeHeader( btreeHeader ); |
| |
| // Read the rootPage pages on disk |
| PageIO[] rootPageIos = readPageIOs( recordManagerHeader.pageSize, rootPageOffset, Long.MAX_VALUE ); |
| BTreeFactory.setRecordManager( btree, this ); |
| |
| Page<K, V> btreeRoot = readPage( recordManagerHeader.pageSize, btreeInfo, rootPageIos ); |
| |
| BTreeFactory.setRootPage( btree, btreeRoot ); |
| } |
| |
| |
| /** |
| * Read a page from some PageIO for a given B-tree |
| * @param btree The B-tree we want to read a page for |
| * @param pageIos The PageIO containing the raw data |
| * @return The read Page if successful |
| * @throws IOException If the deserialization failed |
| */ |
| /* No qualifier*/ <K, V> Page<K, V> readPage( int pageSize, BTreeInfo<K, V> btreeInfo, PageIO[] pageIos ) throws IOException |
| { |
| long position = 0L; |
| |
| // The id |
| long id = readLong( pageSize, pageIos, position ); |
| position += BTreeConstants.LONG_SIZE; |
| |
| // The revision |
| long revision = readLong( pageSize, pageIos, position ); |
| position += BTreeConstants.LONG_SIZE; |
| |
| // The number of elements in the page |
| int nbElems = readInt( pageSize, pageIos, position ); |
| position += BTreeConstants.INT_SIZE; |
| |
| // The size of the data containing the keys and values |
| Page<K, V> page = null; |
| |
| // Reads the bytes containing all the keys and values, if we have some |
| // We read big blog of data into ByteBuffer, then we will process |
| // this ByteBuffer |
| ByteBuffer byteBuffer = readBytes( pageSize, pageIos, position ); |
| |
| // Now, deserialize the data block. If the number of elements |
| // is positive, it's a Leaf, otherwise it's a Node |
| // Note that only a leaf can have 0 elements, and it's the root page then. |
| if ( nbElems >= 0 ) |
| { |
| // It's a leaf |
| page = readLeafKeysAndValues( btreeInfo, nbElems, revision, byteBuffer, pageIos ); |
| } |
| else |
| { |
| // It's a node |
| page = readNodeKeysAndValues( btreeInfo, -nbElems, revision, byteBuffer, pageIos ); |
| } |
| |
| ( ( AbstractPage<K, V> ) page ).setOffset( pageIos[0].getOffset() ); |
| ( ( AbstractWALObject<K, V> ) page ).setId( id ); |
| |
| return page; |
| } |
| |
| |
| /** |
| * Read a page from some PageIO for a given B-tree |
| * @param btree The B-tree we want to read a page for |
| * @param pageIos The PageIO containing the raw data |
| * @return The read Page if successful |
| * @throws IOException If the deserialization failed |
| */ |
| /* No qualifier*/ <K, V> Page<K, V> dumpPage( StringBuilder sb, BTreeInfo<K, V> btreeInfo, int pageSize, PageIO[] pageIos ) throws IOException |
| { |
| long position = 0L; |
| |
| // The id |
| long id = readLong( pageSize, pageIos, position ); |
| position += BTreeConstants.LONG_SIZE; |
| |
| // The revision |
| long revision = readLong( pageSize, pageIos, position ); |
| position += BTreeConstants.LONG_SIZE; |
| |
| // The number of elements in the page |
| int nbElems = readInt( pageSize, pageIos, position ); |
| position += BTreeConstants.INT_SIZE; |
| |
| // The size of the data containing the keys and values |
| Page<K, V> page = null; |
| |
| // Reads the bytes containing all the keys and values, if we have some |
| // We read big blog of data into ByteBuffer, then we will process |
| // this ByteBuffer |
| ByteBuffer byteBuffer = readBytes( pageSize, pageIos, position ); |
| |
| sb.append( String.format( "| ID = %8d |\n", id ) ); |
| sb.append( String.format( "| Revision = %8d |\n", revision ) ); |
| |
| // Now, deserialize the data block. If the number of elements |
| // is positive, it's a Leaf, otherwise it's a Node |
| // Note that only a leaf can have 0 elements, and it's the root page then. |
| if ( nbElems >= 0 ) |
| { |
| // It's a leaf |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( "| Leaf |\n" ); |
| sb.append( String.format( "| nbElems = %8d |\n", nbElems ) ); |
| |
| if ( nbElems > 0 ) |
| { |
| dumpLeaf( btreeInfo, nbElems, byteBuffer, sb ); |
| } |
| } |
| else |
| { |
| // It's a node |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( "| Node |\n" ); |
| sb.append( String.format( "| nbElems = %8d |\n", ( 1 - nbElems ) ) ); |
| |
| if ( nbElems > 0 ) |
| { |
| page = readNodeKeysAndValues( btreeInfo, -nbElems, revision, byteBuffer, pageIos ); |
| } |
| } |
| |
| //( ( AbstractPage<K, V> ) page ).setOffset( pageIos[0].getOffset() ); |
| //( ( AbstractWALObject<K, V> ) page ).setId( id ); |
| return page; |
| } |
| |
| |
| /** |
| * Read a page from some PageIO for a given B-tree |
| * @param btree The B-tree we want to read a page for |
| * @param pageIos The PageIO containing the raw data |
| * @return The read Page if successful |
| * @throws IOException If the deserialization failed |
| */ |
| /* No qualifier*/ static <K, V> Page<K, V> dumpPage( String tabs, RecordManagerHeader recordManagerHeader, |
| FileChannel fileChannel, int pageSize, PageIO[] pageIos, BTreeInfo btreeInfo ) throws IOException |
| { |
| long position = 0L; |
| |
| // The id |
| long id = readLong( pageSize, pageIos, position ); |
| position += BTreeConstants.LONG_SIZE; |
| |
| System.out.println( tabs + " ID :" + id ); |
| |
| // The revision |
| long revision = readLong( pageSize, pageIos, position ); |
| position += BTreeConstants.LONG_SIZE; |
| |
| System.out.println( tabs + " revision :" + revision ); |
| |
| // The number of elements in the page |
| int nbElems = readInt( pageSize, pageIos, position ); |
| position += BTreeConstants.INT_SIZE; |
| |
| |
| // The size of the data containing the keys and values |
| Page<K, V> page = null; |
| |
| // Reads the bytes containing all the keys and values, if we have some |
| // We read big blog of data into ByteBuffer, then we will process |
| // this ByteBuffer |
| ByteBuffer byteBuffer = readBytes( pageSize, pageIos, position ); |
| |
| // Now, deserialize the data block. If the number of elements |
| // is positive, it's a Leaf, otherwise it's a Node |
| // Note that only a leaf can have 0 elements, and it's the root page then. |
| if ( nbElems >= 0 ) |
| { |
| // It's a leaf |
| System.out.println( tabs + " NbElems :" + nbElems ); |
| page = readLeafKeysAndValues( btreeInfo, nbElems, revision, byteBuffer, pageIos ); |
| } |
| else |
| { |
| // It's a node |
| System.out.println( tabs + " NbElems :" + ( 1 - nbElems ) ); |
| page = readNodeKeysAndValues( btreeInfo, -nbElems, revision, byteBuffer, pageIos ); |
| |
| for ( long child : ( ( Node<K, V> ) page ).children ) |
| { |
| pageIos = readPageIOs( recordManagerHeader, fileChannel, pageSize, child, Long.MAX_VALUE ); |
| dumpPage( tabs + " ", recordManagerHeader, fileChannel, pageSize, pageIos, btreeInfo ); |
| } |
| } |
| |
| System.out.println( tabs + " " + page ); |
| |
| //( ( AbstractPage<K, V> ) page ).setOffset( pageIos[0].getOffset() ); |
| //( ( AbstractWALObject<K, V> ) page ).setId( id ); |
| |
| return page; |
| } |
| |
| |
| private static String format80( String text ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| int start = 0; |
| |
| while ( true ) |
| { |
| |
| if ( start + 78 > text.length() ) |
| { |
| int delta = text.length() - start; |
| sb.append( "| " ).append( text.substring( start, text.length() ) ); |
| |
| for ( int i = delta; i < 78; i++ ) |
| { |
| sb.append( ' ' ); |
| } |
| |
| sb.append( " |\n" ); |
| return sb.toString(); |
| } |
| |
| sb.append( "| " ).append( text.substring( start, start + 78 ) ).append( " |\n" ); |
| start += 78; |
| } |
| } |
| |
| |
| /* No qualifier */ static <K, V> void dumpLeaf( BTreeInfo<K, V> btreeInfo, int pageNbElems, |
| ByteBuffer byteBuffer, StringBuilder sb ) throws IOException |
| { |
| // Iterate on the keys and values. We first serialise the value, then the key |
| // until we are done with all of them. If we are serialising a page, we have |
| // to serialise one more value |
| StringBuilder kv = new StringBuilder(); |
| |
| boolean isFirst = true; |
| |
| for ( int pos = 0; pos < pageNbElems; pos++ ) |
| { |
| // Start with the value |
| V value = btreeInfo.getValueSerializer().deserialize( byteBuffer ); |
| |
| // Then the key |
| K key = btreeInfo.getKeySerializer().deserialize( byteBuffer ); |
| |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| kv.append( ", " ); |
| } |
| |
| kv.append( '<' ).append( key ).append(',' ).append( btreeInfo.getValueSerializer().toString( value ) ).append( '>' ); |
| } |
| |
| sb.append( format80( kv.toString() ) ); |
| } |
| |
| |
| /** |
| * Deserialize a Leaf from some PageIOs |
| */ |
| private static <K, V> Leaf<K, V> readLeafKeysAndValues( BTreeInfo<K, V> btreeInfo, int nbElems, long revision, |
| ByteBuffer byteBuffer, PageIO[] pageIos ) throws IOException |
| { |
| // Its a leaf, create it |
| Leaf<K, V> leaf = new Leaf<>( btreeInfo, revision, nbElems ); |
| |
| // Store the page offset on disk |
| leaf.setOffset( pageIos[0].getOffset() ); |
| |
| return leaf.deserialize( byteBuffer ); |
| } |
| |
| |
| /** |
| * Deserialize a Node from some PageIos |
| */ |
| private static <K, V> Node<K, V> readNodeKeysAndValues( BTreeInfo<K, V> btreeInfo, int nbElems, long revision, |
| ByteBuffer byteBuffer, PageIO[] pageIos ) throws IOException |
| { |
| Node<K, V> node = new Node<>( btreeInfo, revision, nbElems ); |
| |
| // Store the page offset on disk |
| node.setOffset( pageIos[0].getOffset() ); |
| |
| return node.deserialize( byteBuffer ); |
| } |
| |
| |
| /** |
| * Read a byte[] from pages. |
| * |
| * @param pageIos The pages we want to read the byte[] from |
| * @param position The position in the data stored in those pages |
| * @return The byte[] we have read |
| */ |
| /* no qualifier */static ByteBuffer readBytes( int pageSize, PageIO[] pageIos, long position ) |
| { |
| // Read the byte[] length first |
| int length = readInt( pageSize, pageIos, position ); |
| position += BTreeConstants.INT_SIZE; |
| |
| // Compute the page in which we will store the data given the |
| // current position |
| int pageNb = computePageNb( pageSize, position ); |
| |
| // Compute the position in the current page |
| int pagePos = ( int ) ( position + ( pageNb + 1 ) * BTreeConstants.LONG_SIZE + BTreeConstants.INT_SIZE ) - pageNb * pageSize; |
| |
| // Check that the length is correct : it should fit in the provided pageIos |
| int pageEnd = computePageNb( pageSize, position + length ); |
| |
| if ( pageEnd > pageIos.length ) |
| { |
| // This is wrong... |
| LOG.error( "Wrong size : {}, it's larger than the number of provided pages {}", length, pageIos.length ); |
| |
| for ( PageIO page : pageIos ) |
| { |
| System.out.println( page ); |
| } |
| |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| |
| ByteBuffer pageData = pageIos[pageNb].getData(); |
| int remaining = pageData.capacity() - pagePos; |
| |
| if ( length == 0 ) |
| { |
| // No bytes to read : return null; |
| return null; |
| } |
| else |
| { |
| ByteBuffer bytes = ByteBuffer.allocate( length ); |
| |
| while ( length > 0 ) |
| { |
| if ( length <= remaining ) |
| { |
| pageData.mark(); |
| pageData.position( pagePos ); |
| int oldLimit = pageData.limit(); |
| pageData.limit( pagePos + length ); |
| bytes.put( pageData ); |
| pageData.limit( oldLimit ); |
| pageData.reset(); |
| bytes.rewind(); |
| |
| return bytes; |
| } |
| |
| pageData.mark(); |
| pageData.position( pagePos ); |
| int oldLimit = pageData.limit(); |
| pageData.limit( pagePos + remaining ); |
| bytes.put( pageData ); |
| pageData.limit( oldLimit ); |
| pageData.reset(); |
| pageNb++; |
| pagePos = BTreeConstants.LINK_SIZE; |
| pageData = pageIos[pageNb].getData(); |
| length -= remaining; |
| remaining = pageData.capacity() - pagePos; |
| } |
| |
| bytes.rewind(); |
| |
| return bytes; |
| } |
| } |
| |
| |
| /** |
| * Read an int from pages |
| * @param pageIos The pages we want to read the int from |
| * @param position The position in the data stored in those pages |
| * @return The int we have read |
| */ |
| /* no qualifier */static int readInt( int pageSize, PageIO[] pageIos, long position ) |
| { |
| // Compute the page in which we will store the data given the |
| // current position |
| int pageNb = computePageNb( pageSize, position ); |
| |
| // Compute the position in the current page |
| int pagePos = ( int ) ( position + ( pageNb + 1 ) * BTreeConstants.LONG_SIZE + BTreeConstants.INT_SIZE ) - pageNb * pageSize; |
| |
| ByteBuffer pageData = pageIos[pageNb].getData(); |
| int remaining = pageData.capacity() - pagePos; |
| int value; |
| |
| if ( remaining >= BTreeConstants.INT_SIZE ) |
| { |
| value = pageData.getInt( pagePos ); |
| } |
| else |
| { |
| value = 0; |
| |
| switch ( remaining ) |
| { |
| case 3: |
| value += ( ( pageData.get( pagePos + 2 ) & 0x00FF ) << 8 ); |
| // Fallthrough !!! |
| |
| case 2: |
| value += ( ( pageData.get( pagePos + 1 ) & 0x00FF ) << 16 ); |
| // Fallthrough !!! |
| |
| case 1: |
| value += ( pageData.get( pagePos ) << 24 ); |
| break; |
| } |
| |
| // Now deal with the next page |
| pageData = pageIos[pageNb + 1].getData(); |
| pagePos = BTreeConstants.LINK_SIZE; |
| |
| switch ( remaining ) |
| { |
| case 1: |
| value += ( pageData.get( pagePos ) & 0x00FF ) << 16; |
| // fallthrough !!! |
| |
| case 2: |
| value += ( pageData.get( pagePos + 2 - remaining ) & 0x00FF ) << 8; |
| // fallthrough !!! |
| |
| case 3: |
| value += ( pageData.get( pagePos + 3 - remaining ) & 0x00FF ); |
| break; |
| } |
| } |
| |
| return value; |
| } |
| |
| |
| /** |
| * Read a byte from pages |
| * @param pageIos The pages we want to read the byte from |
| * @param position The position in the data stored in those pages |
| * @return The byte we have read |
| */ |
| private byte readByte( int pageSize, PageIO[] pageIos, long position ) |
| { |
| // Compute the page in which we will store the data given the |
| // current position |
| int pageNb = computePageNb( pageSize, position ); |
| |
| // Compute the position in the current page |
| int pagePos = ( int ) ( position + ( pageNb + 1 ) * BTreeConstants.LONG_SIZE + BTreeConstants.INT_SIZE ) - pageNb * pageSize; |
| |
| ByteBuffer pageData = pageIos[pageNb].getData(); |
| |
| return pageData.get( pagePos ); |
| } |
| |
| |
| /** |
| * Read a long from pages |
| * @param pageIos The pages we want to read the long from |
| * @param position The position in the data stored in those pages |
| * @return The long we have read |
| */ |
| /* no qualifier */static long readLong( int pageSize, PageIO[] pageIos, long position ) |
| { |
| // Compute the page in which we will store the data given the |
| // current position |
| int pageNb = computePageNb( pageSize, position ); |
| |
| // Compute the position in the current page |
| int pagePos = ( int ) ( position + ( pageNb + 1 ) * BTreeConstants.LONG_SIZE + BTreeConstants.INT_SIZE ) - pageNb * pageSize; |
| |
| ByteBuffer pageData = pageIos[pageNb].getData(); |
| int remaining = pageData.capacity() - pagePos; |
| long value = 0L; |
| |
| if ( remaining >= BTreeConstants.LONG_SIZE ) |
| { |
| value = pageData.getLong( pagePos ); |
| } |
| else |
| { |
| switch ( remaining ) |
| { |
| case 7: |
| value += ( ( ( long ) pageData.get( pagePos + 6 ) & 0x00FF ) << 8 ); |
| // Fallthrough !!! |
| |
| case 6: |
| value += ( ( ( long ) pageData.get( pagePos + 5 ) & 0x00FF ) << 16 ); |
| // Fallthrough !!! |
| |
| case 5: |
| value += ( ( ( long ) pageData.get( pagePos + 4 ) & 0x00FF ) << 24 ); |
| // Fallthrough !!! |
| |
| case 4: |
| value += ( ( ( long ) pageData.get( pagePos + 3 ) & 0x00FF ) << 32 ); |
| // Fallthrough !!! |
| |
| case 3: |
| value += ( ( ( long ) pageData.get( pagePos + 2 ) & 0x00FF ) << 40 ); |
| // Fallthrough !!! |
| |
| case 2: |
| value += ( ( ( long ) pageData.get( pagePos + 1 ) & 0x00FF ) << 48 ); |
| // Fallthrough !!! |
| |
| case 1: |
| value += ( ( long ) pageData.get( pagePos ) << 56 ); |
| break; |
| } |
| |
| // Now deal with the next page |
| pageData = pageIos[pageNb + 1].getData(); |
| pagePos = BTreeConstants.LINK_SIZE; |
| |
| switch ( remaining ) |
| { |
| case 1: |
| value += ( ( long ) pageData.get( pagePos ) & 0x00FF ) << 48; |
| // fallthrough !!! |
| |
| case 2: |
| value += ( ( long ) pageData.get( pagePos + 2 - remaining ) & 0x00FF ) << 40; |
| // fallthrough !!! |
| |
| case 3: |
| value += ( ( long ) pageData.get( pagePos + 3 - remaining ) & 0x00FF ) << 32; |
| // fallthrough !!! |
| |
| case 4: |
| value += ( ( long ) pageData.get( pagePos + 4 - remaining ) & 0x00FF ) << 24; |
| // fallthrough !!! |
| |
| case 5: |
| value += ( ( long ) pageData.get( pagePos + 5 - remaining ) & 0x00FF ) << 16; |
| // fallthrough !!! |
| |
| case 6: |
| value += ( ( long ) pageData.get( pagePos + 6 - remaining ) & 0x00FF ) << 8; |
| // fallthrough !!! |
| |
| case 7: |
| value += ( ( long ) pageData.get( pagePos + 7 - remaining ) & 0x00FF ); |
| break; |
| } |
| } |
| |
| return value; |
| } |
| |
| |
| /** |
| * Manage a B-tree. The btree will be added and managed by this RecordManager. We will create a |
| * new RootPage for this added B-tree, which will contain no data.<br/> |
| * This method is threadsafe. |
| * Managing a btree is a matter of storing an reference to the managed B-tree in the list Of B-trees. |
| * At the same time, we keep a track of the managed B-trees in a Map. |
| * |
| * @param btree The new B-tree to manage. |
| * @param treeType flag indicating if this is an internal tree |
| * |
| * @throws BTreeAlreadyManagedException If the B-tree is already managed |
| * @throws IOException if there was a problem while accessing the file |
| */ |
| public synchronized <K, V> void manage( WriteTransaction transaction, BTree<K, V> btree ) throws BTreeAlreadyManagedException, IOException |
| { |
| long revision = transaction.getRevision(); |
| RecordManagerHeader recordManagerHeader = transaction.recordManagerHeader; |
| |
| try |
| { |
| LOG.debug( "Managing the btree {}", btree.getName() ); |
| BTreeFactory.setRecordManager( btree, this ); |
| |
| String name = btree.getName(); |
| |
| if ( recordManagerHeader.containsBTree( name ) ) |
| { |
| // There is already a B-tree with this name in the recordManager... |
| LOG.error( "There is already a B-tree named '{}' managed by this recordManager", name ); |
| transaction.abort(); |
| throw new BTreeAlreadyManagedException( name ); |
| } |
| |
| // Create the B-tree info |
| BTreeInfo<K, V> btreeInfo = btree.getBtreeInfo(); |
| btreeInfo.initId( recordManagerHeader ); |
| transaction.addWALObject( btreeInfo ); |
| btree.setBtreeInfo( btreeInfo ); |
| |
| // Create the first root page, with the context revision. It will be empty |
| Leaf<K, V> rootPage = transaction.newLeaf( btreeInfo, 0 ); |
| transaction.addWALObject( rootPage ); |
| |
| // Create a B-tree header, and initialize it |
| BTreeHeader<K, V> btreeHeader = new BTreeHeader<>( btreeInfo ); |
| btreeHeader.setRootPage( rootPage ); |
| btreeHeader.setRevision( revision ); |
| btree.setBtreeHeader( btreeHeader ); |
| btreeHeader.initId( recordManagerHeader ); |
| |
| transaction.addWALObject( btreeHeader ); |
| |
| // We can safely increment the number of managed B-trees |
| recordManagerHeader.nbBtree++; |
| |
| // Finally add the new B-tree in the map of managed B-trees. |
| recordManagerHeader.addBTree( btree ); |
| } |
| catch ( IOException ioe ) |
| { |
| transaction.abort(); |
| throw ioe; |
| } |
| } |
| |
| |
| /** |
| * Insert an entry in the BTree. |
| * <p> |
| * We will replace the value if the provided key already exists in the |
| * btree. |
| * <p> |
| * The revision number is the revision to use to insert the data. |
| * |
| * @param key Inserted key |
| * @param value Inserted value |
| * @param revision The revision to use |
| * @return an instance of the InsertResult. |
| */ |
| public <K, V> InsertResult<K, V> insert( WriteTransaction transaction, String btreeName, K key, V value ) throws KeyNotFoundException, IOException |
| { |
| // Fetch the B-tree from the RMH |
| BTree<K, V> btree = transaction.getBTree( btreeName ); |
| |
| return btree.insert( transaction, key, value ); |
| } |
| |
| |
| /** |
| * Create the serialized list of B-trees. Each stored B-tree is referenced by |
| * its offset. The saved structure is : |
| * |
| * <pre> |
| * +--+--+--+--+ |
| * | | | | | pageID |
| * +--+--+--+--+ |
| * | | | | | number of transaction lists |
| * +--+--+--+--+ |
| * The current RMH : |
| * +--+--+--+--+ |
| * | | | | | number of managed BTrees (N) |
| * +--+--+--+--+--+--+--+--+ |
| * | | | | | | | | | revision |
| * +--+--+--+--+--+--+--+--+ |
| * ... |
| * +--+--+--+--+--+--+--+--+ |
| * | | | | | | | | | BtreeHeader offset x N, For each managed B-tree |
| * +--+--+--+--+--+--+--+--+ |
| * ... |
| * |
| * The transactions list : |
| * +--+--+--+--+ |
| * | | | | | number of managed BTrees for RMH[i] (M) |
| * +--+--+--+--+--+--+--+--+ |
| * | | | | | | | | | revision RMH[i] |
| * +--+--+--+--+--+--+--+--+ |
| * ... |
| * +--+--+--+--+--+--+--+--+ |
| * | | | | | | | | | BtreeHeader offset x M, For each managed B-tree of RMH[i] |
| * +--+--+--+--+--+--+--+--+ |
| * ... |
| * </pre> |
| * @param recordManagerHeader The RecordManagerHeader instance |
| * @param transaction The associated transaction |
| * @throws IOException |
| */ |
| /* No qualifier */ void serializeListOfBtrees( RecordManagerHeader recordManagerHeader, Transaction transaction ) throws IOException |
| { |
| int nbLists = activeTransactionsList.size() + 1; |
| |
| // Compute the buffer size |
| int bufferSize = BTreeConstants.INT_SIZE; // The number of pending transactions |
| |
| // Add the page ID |
| bufferSize += BTreeConstants.LONG_SIZE; |
| |
| // Iterate on transactions |
| for ( RecordManagerHeader rmh : activeTransactionsList ) |
| { |
| bufferSize += BTreeConstants.INT_SIZE // The number of B-trees |
| + BTreeConstants.LONG_SIZE // The current revision |
| + ( BTreeConstants.LONG_SIZE * rmh.btreeMap.size() ); // A place holder for each B-tree offset |
| } |
| |
| // Add the last transaction |
| bufferSize += BTreeConstants.INT_SIZE // The number of B-trees |
| + BTreeConstants.LONG_SIZE // The current revision |
| + ( BTreeConstants.LONG_SIZE * recordManagerHeader.btreeMap.size() ); // A place holder for each B-tree offset |
| |
| // Create the requested PageIO to store the full list |
| PageIO[] pageIOs = getFreePageIOs( recordManagerHeader, bufferSize ); |
| long id = recordManagerHeader.idCounter++; |
| |
| long position = 0L; |
| |
| // Store the PageID |
| position = store( recordManagerHeader, position, id, pageIOs ); |
| |
| // Store the number of lists |
| position = store( recordManagerHeader, position, nbLists, pageIOs ); |
| |
| // Seralize the new RMH |
| position = store( recordManagerHeader, position, recordManagerHeader.btreeMap.size(), pageIOs ); |
| position = store( recordManagerHeader, position, transaction.getRevision(), pageIOs ); |
| |
| // Now store each B-tree offset, starting with the current one |
| for ( BTree<?,?> btree : recordManagerHeader.btreeMap.values() ) |
| { |
| position = store( recordManagerHeader, position, btree.getBtreeHeader().getOffset(), pageIOs ); |
| } |
| |
| // Now process the pending transactions |
| for ( RecordManagerHeader rmh : activeTransactionsList ) |
| { |
| position = store( recordManagerHeader, position, rmh.btreeMap.size(), pageIOs ); |
| position = store( recordManagerHeader, position, rmh.getRevision(), pageIOs ); |
| |
| for ( BTree<?,?> btree : rmh.btreeMap.values() ) |
| { |
| position = store( rmh, position, btree.getBtreeHeader().getOffset(), pageIOs ); |
| } |
| } |
| |
| flushPages( recordManagerHeader, pageIOs ); |
| recordManagerHeader.currentListOfBtreesOffset = pageIOs[0].getOffset(); |
| } |
| |
| |
| /** |
| * Inject a newly created BTree in the BtreeOfBtrees |
| */ |
| /* No qualifier */ <K, V> void insertInListOfBtrees( RecordManagerHeader recordManagerHeader, WriteTransaction transaction, BTree<K, V> btree ) throws IOException |
| { |
| // Inject it into the list of B-trees (or replace it) |
| recordManagerHeader.btreeMap.put( btree.getName(), btree ); |
| } |
| |
| |
| /** |
| * Inject the copied pages in the copiedPagesBtree. We may have many pages from various B-trees |
| * to inject. As the pages are mixed, we first have to gather them by B-tree. |
| */ |
| /* No qualifier */ void insertInCopiedPagesBtree( WriteTransaction transaction ) throws IOException |
| { |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| |
| Map<String, List<Long>> pagesList = new HashMap<>(); |
| |
| // Order the WALObject by B-tree name |
| for ( WALObject<?, ?> walObject : transaction.getCopiedPageMap().values() ) |
| { |
| if ( walObject.getBtreeInfo().getType() != BTreeTypeEnum.COPIED_PAGES_BTREE ) |
| { |
| String name = walObject.getName(); |
| |
| List<Long> offsets = pagesList.get( name ); |
| |
| if ( offsets == null ) |
| { |
| offsets = new ArrayList<>(); |
| } |
| |
| offsets.add( walObject.getOffset() ); |
| pagesList.put( name, offsets ); |
| } |
| } |
| |
| // Inject it into the copied pages B-tree |
| BTree<RevisionName, long[]> copiedPagesBTree = recordManagerHeader.copiedPagesBtree; |
| |
| for ( Map.Entry<String, List<Long>> entry : pagesList.entrySet() ) |
| { |
| // Create the new RevisionName |
| RevisionName revisionName = new RevisionName( recordManagerHeader.getRevision(), entry.getKey() ); |
| List<Long> offsetList = entry.getValue(); |
| |
| // Convert the Long array to a long array (we can't use list.toArray, as it generates a Long[] |
| // not a long[]. |
| long[] offsetArray = new long[offsetList.size()]; |
| |
| for ( int i = 0; i < offsetList.size(); i++ ) |
| { |
| offsetArray[i] = offsetList.get( i ); |
| } |
| |
| copiedPagesBTree.insert( transaction, revisionName, offsetArray ); |
| } |
| } |
| |
| |
| /** |
| * Write the management BTrees (LOB and CPB). There are special BTrees, we can write them on disk immediately |
| * (this is done only once anyway : when we create the RecordManager). |
| * |
| * @param btree The new B-tree to manage. |
| * @param treeType flag indicating if this is an internal tree |
| * |
| * @throws BTreeAlreadyManagedException If the B-tree is already managed |
| * @throws IOException |
| */ |
| private synchronized <K, V> void writeManagementTree( WriteTransaction transaction, BTree<K, V> btree ) |
| { |
| LOG.debug( "Managing the sub-btree {}", btree.getName() ); |
| BTreeInfo<K, V> btreeInfo = btree.getBtreeInfo(); |
| |
| // Create the first root page, with version 0L. It will be empty |
| // and increment the revision at the same time |
| Leaf<K, V> rootPage = transaction.newLeaf( btreeInfo, 0 ); |
| |
| LOG.debug( "Flushing the newly managed '{}' btree rootpage", btree.getName() ); |
| |
| // Store the B-tree root Page in the WAL |
| transaction.addWALObject( rootPage ); |
| |
| // Now, create the b-tree info |
| btreeInfo.initId( transaction.getRecordManagerHeader() ); |
| |
| // Store the B-tree info in the WAL |
| transaction.addWALObject( btreeInfo ); |
| |
| // Last, not least, Create a B-tree header, and initialize it |
| BTreeHeader<K, V> btreeHeader = new BTreeHeader<>( btreeInfo ); |
| btreeHeader.setRootPage( rootPage ); |
| btreeHeader.setRevision( transaction.getRevision() ); |
| btreeHeader.initId( transaction.getRecordManagerHeader() ); |
| |
| // Store the BtreeHeader in the BTree |
| btree.setBtreeHeader( btreeHeader ); |
| |
| // Store the B-tree header in the WAL |
| transaction.addWALObject( btreeHeader ); |
| } |
| |
| |
| /** |
| * Write the RecordmanagerHeader in disk |
| */ |
| /* No Qualifier */ void writeRecordManagerHeader( RecordManagerHeader recordManagerHeader ) |
| { |
| recordManagerHeader.serialize( this ); |
| |
| try |
| { |
| fileChannel.write( recordManagerHeaderBuffer, 0 ); |
| fileChannel.force( true ); |
| recordManagerHeader.lastOffset = fileChannel.size(); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new FileException( ioe.getMessage(), ioe ); |
| } |
| |
| // Clean the buffer and switch the versions |
| recordManagerHeaderBuffer.clear(); |
| recordManagerHeaderReference.set( recordManagerHeader ); |
| |
| nbUpdateRMHeader.incrementAndGet(); |
| } |
| |
| |
| /** |
| * Update the RecordManager header, injecting the following data : |
| * |
| * <pre> |
| * +---------------------+ |
| * | current LoB offset | 8 bytes : The offset of the current B-tree of B-trees |
| * +---------------------+ |
| * | current CP offset | 8 bytes : The offset of the current CopiedPages B-tree |
| * +---------------------+ |
| * </pre> |
| */ |
| /* No qualifier */ void updateRecordManagerHeader( RecordManagerHeader recordManagerHeader, long newListOfBtreesOffset, long newCopiedPageBtreeOffset ) |
| { |
| if ( newListOfBtreesOffset != -1L ) |
| { |
| recordManagerHeader.currentListOfBtreesOffset = newListOfBtreesOffset; |
| } |
| |
| if ( newCopiedPageBtreeOffset != -1L ) |
| { |
| recordManagerHeader.currentCopiedPagesBtreeOffset = newCopiedPageBtreeOffset; |
| } |
| } |
| |
| |
| /** |
| * Inject an int into a byte[] at a given position. |
| */ |
| /* No qualifier*/ int writeData( byte[] buffer, int position, int value ) |
| { |
| buffer[position] = ( byte ) ( value >>> 24 ); |
| buffer[position + 1] = ( byte ) ( value >>> 16 ); |
| buffer[position + 2] = ( byte ) ( value >>> 8 ); |
| buffer[position + 3] = ( byte ) ( value ); |
| |
| return position + 4; |
| } |
| |
| |
| /** |
| * Inject a long into a byte[] at a given position. |
| */ |
| /* No qualifier*/ int writeData( byte[] buffer, int position, long value ) |
| { |
| buffer[position] = ( byte ) ( value >>> 56 ); |
| buffer[position + 1] = ( byte ) ( value >>> 48 ); |
| buffer[position + 2] = ( byte ) ( value >>> 40 ); |
| buffer[position + 3] = ( byte ) ( value >>> 32 ); |
| buffer[position + 4] = ( byte ) ( value >>> 24 ); |
| buffer[position + 5] = ( byte ) ( value >>> 16 ); |
| buffer[position + 6] = ( byte ) ( value >>> 8 ); |
| buffer[position + 7] = ( byte ) ( value ); |
| |
| return position + 8; |
| } |
| |
| |
| /** |
| * Add a new <btree, revision> tuple into the B-tree of B-trees. |
| * |
| * @param name The B-tree name |
| * @param revision The B-tree revision |
| * @param btreeHeaderOffset The B-tree offset |
| * @throws IOException If the update failed |
| */ |
| /* no qualifier */void addInBtreeOfBtrees( WriteTransaction transaction, String name, long revision, |
| long btreeHeaderOffset ) throws IOException |
| { |
| |
| checkOffset( btreeHeaderOffset ); |
| NameRevision nameRevision = new NameRevision( name, revision ); |
| |
| //listOfBtrees.insert( transaction, nameRevision, btreeHeaderOffset ); |
| |
| // Update the B-tree of B-trees offset |
| //transaction.recordManagerHeader.currentBtreeOfBtreesOffset = getNewBTreeHeader( BTreeConstants.BTREE_OF_BTREES_NAME ).getOffset(); |
| } |
| |
| |
| /** |
| * Add a new <btree, revision> tuple into the CopiedPages B-tree. |
| * |
| * @param name The B-tree name |
| * @param revision The B-tree revision |
| * @param btreeHeaderOffset The B-tree offset |
| * @throws IOException If the update failed |
| */ |
| /* no qualifier */<K, V> void addInCopiedPagesBtree( WriteTransaction transaction, String name, long revision, List<Page<K, V>> pages ) |
| throws IOException |
| { |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| RevisionName revisionName = new RevisionName( revision, name ); |
| |
| long[] pageOffsets = new long[pages.size()]; |
| int pos = 0; |
| |
| for ( Page<K, V> page : pages ) |
| { |
| pageOffsets[pos++] = ( ( AbstractPage<K, V> ) page ).getOffset(); |
| } |
| |
| copiedPageBtree.insert( transaction, revisionName, pageOffsets ); |
| |
| // Update the CopiedPageBtree offset |
| recordManagerHeader.currentCopiedPagesBtreeOffset = |
| ( ( BTree<RevisionName, long[]> ) copiedPageBtree ).getBtreeHeader().getOffset(); |
| } |
| |
| |
| /** |
| * Write the B-tree header on disk. We will write the following informations : |
| * <pre> |
| * +------------+ |
| * | revision | The B-tree revision |
| * +------------+ |
| * | nbElems | The B-tree number of elements |
| * +------------+ |
| * | rootPage | The root page offset |
| * +------------+ |
| * | BtreeInfo | The B-tree info offset |
| * +------------+ |
| * </pre> |
| * @param btree The B-tree which header has to be written |
| * @param btreeInfoOffset The offset of the B-tree informations |
| * @param now a flag set to <tt>true</tt> if the header has to be written on disk |
| * @return The B-tree header offset |
| * @throws IOException If we weren't able to write the B-tree header |
| */ |
| /* no qualifier *<K, V> PageIO[] writeBtreeHeader( WriteTransaction transaction, BTree<K, V> btree, BTreeHeader<K, V> btreeHeader, boolean now ) |
| throws IOException |
| { |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| |
| int bufferSize = |
| BTreeConstants.LONG_SIZE + // The revision |
| BTreeConstants.LONG_SIZE + // the number of element |
| BTreeConstants.LONG_SIZE + // The root page offset |
| BTreeConstants.LONG_SIZE; // The B-tree info page offset |
| |
| // Get the pageIOs we need to store the data. We may need more than one. |
| PageIO[] pageIOs = getFreePageIOs( recordManagerHeader, bufferSize ); |
| |
| // Store the B-tree header Offset into the B-tree |
| long btreeHeaderOffset = btreeHeaderPageIos[0].getOffset(); |
| |
| // Now store the B-tree data in the pages : |
| // - the B-tree revision |
| // - the B-tree number of elements |
| // - the B-tree root page offset |
| // - the B-tree info page offset |
| // Starts at 0 |
| long position = 0L; |
| |
| // The B-tree current revision |
| position = store( recordManagerHeader, position, btreeHeader.getRevision(), btreeHeaderPageIos ); |
| |
| // The nb elems in the tree |
| position = store( recordManagerHeader, position, btreeHeader.getNbElems(), btreeHeaderPageIos ); |
| |
| // Now, we can inject the B-tree rootPage offset into the B-tree header |
| position = store( recordManagerHeader, position, btreeHeader.getRootPageOffset(), btreeHeaderPageIos ); |
| |
| // The B-tree info page offset |
| position = store( recordManagerHeader, position, btree.getBtreeInfoOffset(), btreeHeaderPageIos ); |
| |
| // And flush the pages to disk now |
| LOG.debug( "Flushing the newly managed '{}' btree header", btree.getName() ); |
| |
| if ( LOG_PAGES.isDebugEnabled() ) |
| { |
| LOG_PAGES.debug( "Writing BTreeHeader revision {} for {}", btreeHeader.getRevision(), btree.getName() ); |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "Offset : " ).append( Long.toHexString( btreeHeaderOffset ) ).append( "\n" ); |
| sb.append( " Revision : " ).append( btreeHeader.getRevision() ).append( "\n" ); |
| sb.append( " NbElems : " ).append( btreeHeader.getNbElems() ).append( "\n" ); |
| sb.append( " RootPage : 0x" ).append( Long.toHexString( btreeHeader.getRootPageOffset() ) ) |
| .append( "\n" ); |
| sb.append( " Info : 0x" ) |
| .append( Long.toHexString( btree.getBtreeInfoOffset() ) ).append( "\n" ); |
| |
| LOG_PAGES.debug( "Btree Header[{}]\n{}", btreeHeader.getRevision(), sb.toString() ); |
| } |
| |
| btreeHeader.setPageIOs( btreeHeaderPageIos ); |
| btreeHeader.setOffset( btreeHeaderOffset ); |
| |
| return btreeHeaderPageIos; |
| } |
| |
| |
| /** |
| * Update the B-tree header after a B-tree modification. This will make the latest modification |
| * visible.<br/> |
| * We update the following fields : |
| * <ul> |
| * <li>the revision</li> |
| * <li>the number of elements</li> |
| * <li>the B-tree root page offset</li> |
| * </ul> |
| * <br/> |
| * As a result, a new version of the BtreHeader will be created, which will replace the previous |
| * B-tree header |
| * @param btree TheB-tree to update |
| * @param btreeHeaderOffset The offset of the modified btree header |
| * @return The offset of the new B-tree Header |
| * @throws IOException If we weren't able to write the file on disk |
| * @throws EndOfFileExceededException If we tried to write after the end of the file |
| */ |
| /* no qualifier */<K, V> long updateBtreeHeader( BTree<K, V> btree, long btreeHeaderOffset ) |
| throws EndOfFileExceededException, IOException |
| { |
| return updateBtreeHeader( btree, btreeHeaderOffset ); |
| } |
| |
| |
| /** |
| * Update the B-tree header after a B-tree modification. This will make the latest modification |
| * visible.<br/> |
| * We update the following fields : |
| * <ul> |
| * <li>the revision</li> |
| * <li>the number of elements</li> |
| * <li>the reference to the current B-tree revisions</li> |
| * <li>the reference to the old B-tree revisions</li> |
| * </ul> |
| * <br/> |
| * As a result, we new version of the BtreHeader will be created |
| * @param btree The B-tree to update |
| * @param btreeHeaderOffset The offset of the modified btree header |
| * @return The offset of the new B-tree Header if it has changed (ie, when the onPlace flag is set to true) |
| * @throws IOException |
| * @throws EndOfFileExceededException |
| */ |
| /* no qualifier */<K, V> void updateBtreeHeaderOnPlace( WriteTransaction transaction, BTree<K, V> btree, long btreeHeaderOffset ) |
| throws EndOfFileExceededException, |
| IOException |
| { |
| updateBtreeHeader( transaction, btree, btreeHeaderOffset, true ); |
| } |
| |
| |
| /** |
| * Update the B-tree header after a B-tree modification. This will make the latest modification |
| * visible.<br/> |
| * We update the following fields : |
| * <ul> |
| * <li>the revision</li> |
| * <li>the number of elements</li> |
| * <li>the reference to the current B-tree revisions</li> |
| * <li>the reference to the old B-tree revisions</li> |
| * </ul> |
| * <br/> |
| * As a result, a new version of the BtreHeader will be created, which may replace the previous |
| * B-tree header (if the onPlace flag is set to true) or a new set of pageIos will contain the new |
| * version. |
| * |
| * @param btree The B-tree to update |
| * @param rootPageOffset The offset of the modified rootPage |
| * @param onPlace Tells if we modify the B-tree on place, or if we create a copy |
| * @return The offset of the new B-tree Header if it has changed (ie, when the onPlace flag is set to true) |
| * @throws EndOfFileExceededException If we tried to write after the end of the file |
| * @throws IOException If tehre were some error while writing the data on disk |
| */ |
| private <K, V> long updateBtreeHeader( WriteTransaction transaction, BTree<K, V> btree, long btreeHeaderOffset, boolean onPlace ) |
| throws EndOfFileExceededException, IOException |
| { |
| // Read the pageIOs associated with this B-tree |
| PageIO[] pageIos; |
| long newBtreeHeaderOffset = BTreeConstants.NO_PAGE; |
| long offset = btree.getBtreeOffset(); |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| |
| if ( onPlace ) |
| { |
| // We just have to update the existing BTreeHeader |
| long headerSize = BTreeConstants.LONG_SIZE + BTreeConstants.LONG_SIZE + BTreeConstants.LONG_SIZE; |
| |
| pageIos = readPageIOs( recordManagerHeader.pageSize, offset, headerSize ); |
| |
| // Now, update the revision |
| long position = 0; |
| |
| position = store( recordManagerHeader, position, transaction.getRevision(), pageIos ); |
| position = store( recordManagerHeader, position, btree.getNbElems(), pageIos ); |
| store( recordManagerHeader, position, btreeHeaderOffset, pageIos ); |
| |
| // Write the pages on disk |
| if ( LOG.isDebugEnabled() ) |
| { |
| LOG.debug( "-----> Flushing the '{}' B-treeHeader", btree.getName() ); |
| LOG.debug( " revision : " + transaction.getRevision() + ", NbElems : " |
| + btree.getNbElems() |
| + ", btreeHeader offset : 0x" |
| + Long.toHexString( btreeHeaderOffset ) ); |
| } |
| |
| // Get new place on disk to store the modified BTreeHeader if it's not onPlace |
| // Rewrite the pages at the same place |
| LOG.debug( "Rewriting the B-treeHeader on place for B-tree " + btree.getName() ); |
| flushPages( recordManagerHeader, pageIos ); |
| } |
| else |
| { |
| // We have to read and copy the existing BTreeHeader and to create a new one |
| pageIos = readPageIOs( recordManagerHeader.pageSize, offset, Long.MAX_VALUE ); |
| |
| // Now, copy every read page |
| PageIO[] newPageIOs = new PageIO[pageIos.length]; |
| int pos = 0; |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| // Fetch a free page |
| newPageIOs[pos] = fetchNewPage( recordManagerHeader ); |
| |
| // keep a track of the allocated and copied pages so that we can |
| // free them when we do a commit or rollback, if the btree is an management one |
| if ( ( btree.getType() == BTreeTypeEnum.BTREE_OF_BTREES ) |
| || ( btree.getType() == BTreeTypeEnum.COPIED_PAGES_BTREE ) ) |
| { |
| freedPages.add( pageIo ); |
| allocatedPages.add( newPageIOs[pos] ); |
| } |
| |
| pageIo.copy( newPageIOs[pos] ); |
| |
| if ( pos > 0 ) |
| { |
| newPageIOs[pos - 1].setNextPage( newPageIOs[pos].getOffset() ); |
| } |
| |
| pos++; |
| } |
| |
| // store the new btree header offset |
| // and update the revision |
| long position = 0; |
| |
| position = store( recordManagerHeader, position, transaction.getRevision(), newPageIOs ); |
| position = store( recordManagerHeader, position, btree.getNbElems(), newPageIOs ); |
| store( recordManagerHeader, position, btreeHeaderOffset, newPageIOs ); |
| |
| // Get new place on disk to store the modified BTreeHeader if it's not onPlace |
| // Flush the new B-treeHeader on disk |
| LOG.debug( "Rewriting the B-treeHeader on place for B-tree " + btree.getName() ); |
| flushPages( recordManagerHeader, newPageIOs ); |
| |
| newBtreeHeaderOffset = newPageIOs[0].getOffset(); |
| } |
| |
| nbUpdateBtreeHeader.incrementAndGet(); |
| |
| if ( LOG_CHECK.isDebugEnabled() ) |
| { |
| MavibotInspector.check( this ); |
| } |
| |
| return newBtreeHeaderOffset; |
| } |
| |
| |
| /** |
| * Write the pages on disk, either at the end of the file, or at |
| * the position they were taken from. |
| * |
| * @param pageIos The list of pages to write |
| * @throws IOException If the write failed |
| */ |
| /* No qualifier */ void flushPages( RecordManagerHeader recordManagerHeader, PageIO... pageIos ) throws IOException |
| { |
| if ( pageIos == null ) |
| { |
| LOG.debug( "No PageIO to flush" ); |
| return; |
| } |
| |
| if ( LOG.isDebugEnabled() ) |
| { |
| for ( PageIO pageIo : pageIos ) |
| { |
| dump( pageIo ); |
| } |
| } |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| pageIo.getData().rewind(); |
| long pos = pageIo.getOffset(); |
| |
| if ( fileChannel.size() < ( pageIo.getOffset() + recordManagerHeader.pageSize ) ) |
| { |
| LOG.debug( "Adding a page at the end of the file" ); |
| // This is a page we have to add to the file |
| pos = fileChannel.size(); |
| fileChannel.write( pageIo.getData(), pageIo.getOffset() ); |
| long endFile = fileChannel.size(); |
| |
| if ( endFile == pos ) |
| { |
| System.out.println( "----------------------------------------------------FORCING FLUSH " ); |
| fileChannel.force( true ); |
| } |
| |
| pageIo.getData().rewind(); |
| } |
| else |
| { |
| LOG.debug( "Writing a page at position {}", pageIo.getOffset() ); |
| fileChannel.write( pageIo.getData(), pageIo.getOffset() ); |
| //fileChannel.force( false ); |
| } |
| |
| writeCounter.put( pos, writeCounter.containsKey( pos ) ? writeCounter.get( pos ) + 1 : 1 ); |
| |
| nbUpdatePageIOs.incrementAndGet(); |
| |
| pageIo.getData().rewind(); |
| } |
| } |
| |
| |
| /** |
| * Store the pages in the context. If the page has no Offset, we will |
| * use a virtual offset (ie, a negative one) |
| * |
| * @param pageIos The list of pages to write |
| * @throws IOException If the store failed |
| */ |
| /* No qualifier */void storePages( PageIO... pageIos ) throws IOException |
| { |
| if ( LOG.isDebugEnabled() ) |
| { |
| for ( PageIO pageIo : pageIos ) |
| { |
| dump( pageIo ); |
| } |
| } |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| pageIo.getData().rewind(); |
| long offset = pageIo.getOffset(); |
| |
| LOG.debug( "Writing a page at position {}", offset ); |
| |
| writeCounter.put( offset, writeCounter.containsKey( offset ) ? writeCounter.get( offset ) + 1 : 1 ); |
| |
| nbUpdatePageIOs.incrementAndGet(); |
| } |
| } |
| |
| |
| /** |
| * Compute the page in which we will store data given an offset, when |
| * we have a list of pages. |
| * |
| * @param offset The position in the data |
| * @return The page number in which the offset will start |
| */ |
| private static int computePageNb( int pageSize, long offset ) |
| { |
| long pageNb = 0; |
| |
| offset -= pageSize - BTreeConstants.LINK_SIZE - BTreeConstants.PAGE_SIZE; |
| |
| if ( offset < 0 ) |
| { |
| return ( int ) pageNb; |
| } |
| |
| pageNb = 1 + offset / ( pageSize - BTreeConstants.LINK_SIZE ); |
| |
| return ( int ) pageNb; |
| /* |
| long pageOffset = offset - recordManagerHeader.pageSize - BTreeConstants.LINK_SIZE - BTreeConstants.PAGE_SIZE; |
| |
| if ( pageOffset < 0 ) |
| { |
| return 0; |
| } |
| |
| return ( int ) ( 1 + pageOffset / ( recordManagerHeader.pageSize - BTreeConstants.LINK_SIZE ) ); |
| */ |
| } |
| |
| |
| /** |
| * Stores a byte[] into one ore more pageIO (depending if the long is stored |
| * across a boundary or not) |
| * |
| * @param position The position in a virtual byte[] if all the pages were contiguous |
| * @param bytes The byte[] to serialize |
| * @param pageIos The pageIOs we have to store the data in |
| * @return The new offset |
| */ |
| /* no qualifier */ long store( RecordManagerHeader recordManagerHeader, long position, byte[] bytes, PageIO... pageIos ) |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| if ( bytes != null ) |
| { |
| // Write the bytes length |
| position = store( recordManagerHeader, position, bytes.length, pageIos ); |
| |
| // Compute the page in which we will store the data given the |
| // current position |
| int pageNb = computePageNb( pageSize, position ); |
| |
| // Get back the buffer in this page |
| ByteBuffer pageData = pageIos[pageNb].getData(); |
| |
| // Compute the position in the current page |
| int pagePos = ( int ) ( position + ( pageNb + 1 ) * BTreeConstants.LONG_SIZE + BTreeConstants.INT_SIZE ) - pageNb * pageSize; |
| |
| // Compute the remaining size in the page |
| int remaining = pageData.capacity() - pagePos; |
| int nbStored = bytes.length; |
| |
| // And now, write the bytes until we have none |
| while ( nbStored > 0 ) |
| { |
| if ( remaining > nbStored ) |
| { |
| pageData.mark(); |
| pageData.position( pagePos ); |
| pageData.put( bytes, bytes.length - nbStored, nbStored ); |
| pageData.reset(); |
| nbStored = 0; |
| } |
| else |
| { |
| pageData.mark(); |
| pageData.position( pagePos ); |
| pageData.put( bytes, bytes.length - nbStored, remaining ); |
| pageData.reset(); |
| pageNb++; |
| pageData = pageIos[pageNb].getData(); |
| pagePos = BTreeConstants.LINK_SIZE; |
| nbStored -= remaining; |
| remaining = pageData.capacity() - pagePos; |
| } |
| } |
| |
| // We are done |
| position += bytes.length; |
| } |
| else |
| { |
| // No bytes : write 0 and return |
| position = store( recordManagerHeader, position, 0, pageIos ); |
| } |
| |
| return position; |
| } |
| |
| |
| /** |
| * Stores a byte[] into one ore more pageIO (depending if the long is stored |
| * across a boundary or not). We don't add the byte[] size, it's already present |
| * in the received byte[]. |
| * |
| * @param position The position in a virtual byte[] if all the pages were contiguous |
| * @param bytes The byte[] to serialize |
| * @param pageIos The pageIOs we have to store the data in |
| * @return The new offset |
| */ |
| /* No qualifier */ long storeRaw( RecordManagerHeader recordManagerHeader, long position, byte[] bytes, PageIO... pageIos ) |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| if ( bytes != null ) |
| { |
| // Compute the page in which we will store the data given the |
| // current position |
| int pageNb = computePageNb( pageSize, position ); |
| |
| // Get back the buffer in this page |
| ByteBuffer pageData = pageIos[pageNb].getData(); |
| |
| // Compute the position in the current page |
| int pagePos = ( int ) ( position + ( pageNb + 1 ) * BTreeConstants.LONG_SIZE + BTreeConstants.INT_SIZE ) - pageNb * pageSize; |
| |
| // Compute the remaining size in the page |
| int remaining = pageData.capacity() - pagePos; |
| int nbStored = bytes.length; |
| |
| // And now, write the bytes until we have none |
| while ( nbStored > 0 ) |
| { |
| if ( remaining > nbStored ) |
| { |
| pageData.mark(); |
| pageData.position( pagePos ); |
| pageData.put( bytes, bytes.length - nbStored, nbStored ); |
| pageData.reset(); |
| nbStored = 0; |
| } |
| else |
| { |
| pageData.mark(); |
| pageData.position( pagePos ); |
| pageData.put( bytes, bytes.length - nbStored, remaining ); |
| pageData.reset(); |
| pageNb++; |
| |
| if ( pageNb == pageIos.length ) |
| { |
| // We can stop here : we have reach the end of the page |
| break; |
| } |
| |
| pageData = pageIos[pageNb].getData(); |
| pagePos = BTreeConstants.LINK_SIZE; |
| nbStored -= remaining; |
| remaining = pageData.capacity() - pagePos; |
| } |
| } |
| |
| // We are done |
| position += bytes.length; |
| } |
| else |
| { |
| // No bytes : write 0 and return |
| position = store( recordManagerHeader, position, 0, pageIos ); |
| } |
| |
| return position; |
| } |
| |
| |
| /** |
| * Stores an Integer into one ore more pageIO (depending if the int is stored |
| * across a boundary or not) |
| * |
| * @param position The position in a virtual byte[] if all the pages were contiguous |
| * @param value The int to serialize |
| * @param pageIos The pageIOs we have to store the data in |
| * @return The new offset |
| */ |
| /* no qualifier */ long store( RecordManagerHeader recordManagerHeader, long position, int value, PageIO... pageIos ) |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // Compute the page in which we will store the data given the |
| // current position |
| int pageNb = computePageNb( pageSize, position ); |
| |
| // Compute the position in the current page |
| int pagePos = ( int ) ( position + ( pageNb + 1 ) * BTreeConstants.LONG_SIZE + BTreeConstants.INT_SIZE ) - pageNb * pageSize; |
| |
| // Get back the buffer in this page |
| ByteBuffer pageData = pageIos[pageNb].getData(); |
| |
| // Compute the remaining size in the page |
| int remaining = pageData.capacity() - pagePos; |
| |
| if ( remaining < BTreeConstants.INT_SIZE ) |
| { |
| // We have to copy the serialized length on two pages |
| |
| switch ( remaining ) |
| { |
| case 3: |
| pageData.put( pagePos + 2, ( byte ) ( value >>> 8 ) ); |
| // Fallthrough !!! |
| |
| case 2: |
| pageData.put( pagePos + 1, ( byte ) ( value >>> 16 ) ); |
| // Fallthrough !!! |
| |
| case 1: |
| pageData.put( pagePos, ( byte ) ( value >>> 24 ) ); |
| break; |
| } |
| |
| // Now deal with the next page |
| pageData = pageIos[pageNb + 1].getData(); |
| pagePos = BTreeConstants.LINK_SIZE; |
| |
| switch ( remaining ) |
| { |
| case 1: |
| pageData.put( pagePos, ( byte ) ( value >>> 16 ) ); |
| // fallthrough !!! |
| |
| case 2: |
| pageData.put( pagePos + 2 - remaining, ( byte ) ( value >>> 8 ) ); |
| // fallthrough !!! |
| |
| case 3: |
| pageData.put( pagePos + 3 - remaining, ( byte ) ( value ) ); |
| break; |
| } |
| } |
| else |
| { |
| // Store the value in the page at the selected position |
| pageData.putInt( pagePos, value ); |
| } |
| |
| // Increment the position to reflect the addition of an Int (4 bytes) |
| position += BTreeConstants.INT_SIZE; |
| |
| return position; |
| } |
| |
| |
| /** |
| * Stores a Long into one ore more pageIO (depending if the long is stored |
| * across a boundary or not) |
| * |
| * @param position The position in a virtual byte[] if all the pages were contiguous |
| * @param value The long to serialize |
| * @param pageIos The pageIOs we have to store the data in |
| * @return The new offset |
| */ |
| /* no qualifier */ long store( RecordManagerHeader recordManagerHeader, long position, long value, PageIO... pageIos ) |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // Compute the page in which we will store the data given the |
| // current position |
| int pageNb = computePageNb( pageSize, position ); |
| |
| // Compute the position in the current page |
| int pagePos = ( int ) ( position + ( pageNb + 1 ) * BTreeConstants.LONG_SIZE + BTreeConstants.INT_SIZE ) - pageNb * pageSize; |
| |
| // Get back the buffer in this page |
| ByteBuffer pageData = pageIos[pageNb].getData(); |
| |
| // Compute the remaining size in the page |
| int remaining = pageData.capacity() - pagePos; |
| |
| if ( remaining < BTreeConstants.LONG_SIZE ) |
| { |
| // We have to copy the serialized length on two pages |
| |
| switch ( remaining ) |
| { |
| case 7: |
| pageData.put( pagePos + 6, ( byte ) ( value >>> 8 ) ); |
| // Fallthrough !!! |
| |
| case 6: |
| pageData.put( pagePos + 5, ( byte ) ( value >>> 16 ) ); |
| // Fallthrough !!! |
| |
| case 5: |
| pageData.put( pagePos + 4, ( byte ) ( value >>> 24 ) ); |
| // Fallthrough !!! |
| |
| case 4: |
| pageData.put( pagePos + 3, ( byte ) ( value >>> 32 ) ); |
| // Fallthrough !!! |
| |
| case 3: |
| pageData.put( pagePos + 2, ( byte ) ( value >>> 40 ) ); |
| // Fallthrough !!! |
| |
| case 2: |
| pageData.put( pagePos + 1, ( byte ) ( value >>> 48 ) ); |
| // Fallthrough !!! |
| |
| case 1: |
| pageData.put( pagePos, ( byte ) ( value >>> 56 ) ); |
| break; |
| } |
| |
| // Now deal with the next page |
| pageData = pageIos[pageNb + 1].getData(); |
| pagePos = BTreeConstants.LINK_SIZE; |
| |
| switch ( remaining ) |
| { |
| case 1: |
| pageData.put( pagePos, ( byte ) ( value >>> 48 ) ); |
| // fallthrough !!! |
| |
| case 2: |
| pageData.put( pagePos + 2 - remaining, ( byte ) ( value >>> 40 ) ); |
| // fallthrough !!! |
| |
| case 3: |
| pageData.put( pagePos + 3 - remaining, ( byte ) ( value >>> 32 ) ); |
| // fallthrough !!! |
| |
| case 4: |
| pageData.put( pagePos + 4 - remaining, ( byte ) ( value >>> 24 ) ); |
| // fallthrough !!! |
| |
| case 5: |
| pageData.put( pagePos + 5 - remaining, ( byte ) ( value >>> 16 ) ); |
| // fallthrough !!! |
| |
| case 6: |
| pageData.put( pagePos + 6 - remaining, ( byte ) ( value >>> 8 ) ); |
| // fallthrough !!! |
| |
| case 7: |
| pageData.put( pagePos + 7 - remaining, ( byte ) ( value ) ); |
| break; |
| } |
| } |
| else |
| { |
| // Store the value in the page at the selected position |
| pageData.putLong( pagePos, value ); |
| } |
| |
| // Increment the position to reflect the addition of an Long (8 bytes) |
| position += BTreeConstants.LONG_SIZE; |
| |
| return position; |
| } |
| |
| |
| /** |
| * Write the page in a serialized form. |
| * |
| * @param btree The persistedBtree we will create a new PageHolder for |
| * @param newPage The page to write on disk |
| * @param newRevision The page's revision |
| * @return A PageHolder containing the copied page |
| * @throws IOException If the page can't be written on disk |
| */ |
| /* No qualifier*/<K, V> long writePage( WriteTransaction transaction, BTreeInfo<K, V> btreeInfo, Page<K, V> newPage, |
| long newRevision ) throws IOException |
| { |
| // We first need to save the new page on disk |
| PageIO[] pageIos = newPage.serialize( transaction ); |
| |
| if ( LOG_PAGES.isDebugEnabled() ) |
| { |
| LOG_PAGES.debug( "Write data for '{}' btree", btreeInfo.getName() ); |
| |
| logPageIos( pageIos ); |
| } |
| |
| // Write the page on disk |
| flushPages( transaction.getRecordManagerHeader(), pageIos ); |
| |
| // Build the resulting reference |
| long offset = pageIos[0].getOffset(); |
| |
| return offset; |
| } |
| |
| |
| /* No qualifier */static void logPageIos( PageIO[] pageIos ) |
| { |
| int pageNb = 0; |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| sb.append( "PageIO[" ).append( pageNb ).append( "]:0x" ); |
| sb.append( Long.toHexString( pageIo.getOffset() ) ).append( "/" ); |
| sb.append( pageIo.getSize() ); |
| pageNb++; |
| |
| ByteBuffer data = pageIo.getData(); |
| |
| int position = data.position(); |
| int dataLength = ( int ) pageIo.getSize() + 12; |
| |
| if ( dataLength > data.limit() ) |
| { |
| dataLength = data.limit(); |
| } |
| |
| byte[] bytes = new byte[dataLength]; |
| |
| data.get( bytes ); |
| data.position( position ); |
| int pos = 0; |
| |
| for ( byte b : bytes ) |
| { |
| int mod = pos % 16; |
| |
| switch ( mod ) |
| { |
| case 0: |
| sb.append( "\n " ); |
| // No break |
| case 4: |
| case 8: |
| case 12: |
| sb.append( " " ); |
| case 1: |
| case 2: |
| case 3: |
| case 5: |
| case 6: |
| case 7: |
| case 9: |
| case 10: |
| case 11: |
| case 13: |
| case 14: |
| case 15: |
| sb.append( Strings.dumpByte( b ) ).append( " " ); |
| } |
| pos++; |
| } |
| |
| LOG_PAGES.debug( sb.toString() ); |
| } |
| } |
| |
| |
| /** |
| * Compute the number of pages needed to store some specific size of data. |
| * |
| * @param dataSize The size of the data we want to store in pages |
| * @return The number of pages needed |
| */ |
| /* No qualifier */ static int computeNbPages( RecordManagerHeader recordManagerHeader, int dataSize ) |
| { |
| if ( dataSize <= 0 ) |
| { |
| return 0; |
| } |
| |
| // Compute the number of pages needed. |
| // Considering that each page can contain PageSize bytes, |
| // but that the first 8 bytes are used for links and we |
| // use 4 bytes to store the data size, the number of needed |
| // pages is : |
| // NbPages = ( (dataSize - (PageSize - 8 - 4 )) / (PageSize - 8) ) + 1 |
| // NbPages += ( if (dataSize - (PageSize - 8 - 4 )) % (PageSize - 8) > 0 : 1 : 0 ) |
| int availableSize = recordManagerHeader.pageSize - BTreeConstants.LONG_SIZE; |
| int nbNeededPages = 1; |
| |
| // Compute the number of pages that will be full but the first page |
| if ( dataSize > availableSize - BTreeConstants.INT_SIZE ) |
| { |
| int remainingSize = dataSize - ( availableSize - BTreeConstants.INT_SIZE ); |
| nbNeededPages += remainingSize / availableSize; |
| int remain = remainingSize % availableSize; |
| |
| if ( remain > 0 ) |
| { |
| nbNeededPages++; |
| } |
| } |
| |
| return nbNeededPages; |
| } |
| |
| |
| /** |
| * Get as many pages as needed to store the data of the given size. The returned |
| * PageIOs are all linked together. |
| * |
| * @param dataSize The data size |
| * @return An array of pages, enough to store the full data |
| */ |
| /* No qualifier */ PageIO[] getFreePageIOs( RecordManagerHeader recordManagerHeader, int dataSize ) throws IOException |
| { |
| if ( dataSize == 0 ) |
| { |
| return new PageIO[] |
| {}; |
| } |
| |
| int nbNeededPages = computeNbPages( recordManagerHeader, dataSize ); |
| |
| PageIO[] pageIOs = new PageIO[nbNeededPages]; |
| |
| // The first page : set the size |
| pageIOs[0] = fetchNewPage( recordManagerHeader ); |
| pageIOs[0].setSize( dataSize ); |
| |
| for ( int i = 1; i < nbNeededPages; i++ ) |
| { |
| pageIOs[i] = fetchNewPage( recordManagerHeader ); |
| |
| // Create the link |
| pageIOs[i - 1].setNextPage( pageIOs[i].getOffset() ); |
| } |
| |
| return pageIOs; |
| } |
| |
| private static Set<Long> usedPages = new HashSet<>(); |
| |
| /** |
| * Return a new Page. We take one of the existing free pages, or we create |
| * a new page at the end of the file. |
| * |
| * @return The fetched PageIO |
| */ |
| /* No qualifier*/ PageIO fetchNewPage( RecordManagerHeader recordManagerHeader ) throws IOException |
| { |
| if ( recordManagerHeader.firstFreePage == BTreeConstants.NO_PAGE ) |
| { |
| nbCreatedPages.incrementAndGet(); |
| |
| // We don't have any free page. Reclaim some new page at the end |
| // of the file |
| long lastOffset = recordManagerHeader.lastOffset; |
| PageIO newPage = new PageIO( lastOffset ); |
| |
| ByteBuffer data = ByteBuffer.allocate( recordManagerHeader.pageSize ); |
| |
| // Move the last offset one page forward |
| recordManagerHeader.lastOffset = lastOffset + recordManagerHeader.pageSize; |
| |
| newPage.setData( data ); |
| newPage.setNextPage( BTreeConstants.NO_PAGE ); |
| newPage.setSize( 0 ); |
| |
| LOG.debug( "Requiring a new page at offset {}", newPage.getOffset() ); |
| |
| return newPage; |
| } |
| else |
| { |
| nbReusedPages.incrementAndGet(); |
| |
| freePageLock.lock(); |
| |
| // We have some existing free page. Fetch it from disk |
| PageIO pageIo = fetchPageIO( recordManagerHeader.pageSize, recordManagerHeader.firstFreePage ); |
| |
| // Update the firstFreePage pointer |
| recordManagerHeader.firstFreePage = pageIo.getNextPage(); |
| |
| if ( !usedPages.add( pageIo.getOffset() ) ) |
| { |
| System.out.println( "Page " + Long.toHexString( pageIo.getOffset() ) + "already used" ); |
| } |
| |
| |
| freePageLock.unlock(); |
| |
| // overwrite the data of old page |
| ByteBuffer data = ByteBuffer.allocate( recordManagerHeader.pageSize ); |
| pageIo.setData( data ); |
| |
| pageIo.setNextPage( BTreeConstants.NO_PAGE ); |
| pageIo.setSize( 0 ); |
| |
| LOG.debug( "Reused page at offset {}", pageIo.getOffset() ); |
| |
| return pageIo; |
| } |
| } |
| |
| |
| /** |
| * Fetch a page from cache, or from disk |
| */ |
| /* no qualifier */<K, V> Page<K, V> getPage( BTreeInfo btreeInfo, int pageSize, long offset ) throws IOException |
| { |
| Page<K, V> page = pageCache.getIfPresent( offset ); |
| |
| if ( page == null ) |
| { |
| nbCacheMisses.incrementAndGet(); |
| PageIO[] pageIos = readPageIOs( pageSize, offset, BTreeConstants.NO_LIMIT ); |
| |
| Page<K, V> foundPage = ( Page<K, V> )readPage( pageSize, btreeInfo, pageIos ); |
| //pageCache.put( offset, foundPage ); |
| |
| return foundPage; |
| } |
| else |
| { |
| nbCacheHits.incrementAndGet(); |
| |
| return page; |
| } |
| } |
| |
| |
| /* No qualifier */ <K, V> void putPage( WALObject<K, V> walObject ) |
| { |
| if ( walObject instanceof Page ) |
| { |
| long key = walObject.getOffset(); |
| |
| if ( key >= 0L ) |
| { |
| //pageCache.put( key, ( Page<K, V> )walObject ); |
| } |
| } |
| } |
| |
| |
| /** |
| * fetch a page from disk, knowing its position in the file. |
| * |
| * @param offset The position in the file |
| * @return The found page |
| */ |
| /* no qualifier */PageIO fetchPageIO( int pageSize, long offset ) throws IOException |
| { |
| checkOffset( offset ); |
| |
| if ( fileChannel.size() < offset + pageSize ) |
| { |
| // Error : we are past the end of the file |
| throw new EndOfFileExceededException( "We are fetching a page on " + offset + |
| " when the file's size is " + fileChannel.size() ); |
| } |
| else |
| { |
| ByteBuffer data= ByteBuffer.allocate( pageSize ); |
| |
| // This needs to be thread safe, as calling position() is not. |
| synchronized ( fileChannel ) |
| { |
| // Read the page |
| fileChannel.position( offset ); |
| fileChannel.read( data ); |
| } |
| |
| data.rewind(); |
| |
| |
| PageIO readPage = new PageIO( offset ); |
| readPage.setData( data ); |
| |
| return readPage; |
| } |
| } |
| |
| |
| /** |
| * fetch a page from disk, knowing its position in the file. |
| * |
| * @param offset The position in the file |
| * @return The found page |
| */ |
| /* no qualifier */static PageIO fetchPageIO( RecordManagerHeader recordManagerHeader, FileChannel fileChannel, int pageSize, long offset ) throws IOException |
| { |
| checkOffset( recordManagerHeader, offset ); |
| |
| if ( fileChannel.size() < offset + pageSize ) |
| { |
| // Error : we are past the end of the file |
| throw new EndOfFileExceededException( "We are fetching a page on " + offset + |
| " when the file's size is " + fileChannel.size() ); |
| } |
| else |
| { |
| ByteBuffer data= ByteBuffer.allocate( pageSize ); |
| |
| // This needs to be thread safe, as calling position() is not. |
| synchronized ( fileChannel ) |
| { |
| // Read the page |
| fileChannel.position( offset ); |
| fileChannel.read( data ); |
| } |
| |
| data.rewind(); |
| |
| |
| PageIO readPage = new PageIO( offset ); |
| readPage.setData( data ); |
| |
| return readPage; |
| } |
| } |
| |
| |
| /** |
| * @return the pageSize |
| */ |
| public int getPageSize( RecordManagerHeader recordManagerHeader ) |
| { |
| return recordManagerHeader.pageSize; |
| } |
| |
| |
| /** |
| * Set the page size, ie the number of bytes a page can store. |
| * |
| * @param pageSize The number of bytes for a page |
| */ |
| /* no qualifier */void setPageSize( Transaction transaction, int pageSize ) |
| { |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| |
| if ( recordManagerHeader.pageSize >= 13 ) |
| { |
| recordManagerHeader.pageSize = pageSize; |
| } |
| else |
| { |
| recordManagerHeader.pageSize = BTreeConstants.DEFAULT_PAGE_SIZE; |
| } |
| } |
| |
| |
| /** |
| * Close the RecordManager immediately. The pending write transaction will |
| * be aborted. |
| */ |
| public void closeNow() throws IOException |
| { |
| |
| } |
| |
| |
| /** |
| * Close the RecordManager and flush everything on disk |
| */ |
| public void close() throws IOException |
| { |
| // Close all the managed B-trees |
| for ( BTree<Object, Object> tree : managedBtrees.values() ) |
| { |
| //tree.close(); |
| } |
| |
| // Close the management B-trees |
| //copiedPageBtree.close(); |
| //listOfBtrees.close(); |
| |
| // Write the data |
| fileChannel.force( true ); |
| |
| // And close the channel |
| fileChannel.close(); |
| } |
| |
| |
| public static String dump( byte octet ) |
| { |
| return new String( new byte[] |
| { BTreeConstants.HEX_CHAR[( octet & 0x00F0 ) >> 4], BTreeConstants.HEX_CHAR[octet & 0x000F] } ); |
| } |
| |
| |
| /** |
| * Dump a pageIO |
| */ |
| private void dump( PageIO pageIo ) |
| { |
| ByteBuffer buffer = pageIo.getData(); |
| buffer.mark(); |
| byte[] longBuffer = new byte[BTreeConstants.LONG_SIZE]; |
| byte[] intBuffer = new byte[BTreeConstants.INT_SIZE]; |
| |
| // get the next page offset |
| buffer.get( longBuffer ); |
| long nextOffset = LongSerializer.deserialize( longBuffer ); |
| |
| // Get the data size |
| buffer.get( intBuffer ); |
| int size = IntSerializer.deserialize( intBuffer ); |
| |
| buffer.reset(); |
| |
| System.out.println( "PageIO[" + Long.toHexString( pageIo.getOffset() ) + "], size = " + size + ", NEXT PageIO:" |
| + Long.toHexString( nextOffset ) ); |
| System.out.println( " 0 1 2 3 4 5 6 7 8 9 A B C D E F " ); |
| System.out.println( "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" ); |
| |
| for ( int i = 0; i < buffer.limit(); i += 16 ) |
| { |
| System.out.print( "|" ); |
| |
| for ( int j = 0; j < 16; j++ ) |
| { |
| System.out.print( dump( buffer.get() ) ); |
| |
| if ( j == 15 ) |
| { |
| System.out.println( "|" ); |
| } |
| else |
| { |
| System.out.print( " " ); |
| } |
| } |
| } |
| |
| System.out.println( "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" ); |
| |
| buffer.reset(); |
| } |
| |
| |
| /** |
| * Dump the RecordManager file |
| * @throws IOException |
| */ |
| public void dump( Thread thread, RecordManagerHeader rmh ) |
| { |
| System.out.println( "Thread: " + thread.getName() ); |
| System.out.println( "/---------------------------- Dump ----------------------------\\" ); |
| |
| try |
| { |
| // The page size |
| long fileSize = fileChannel.size(); |
| int pageSize = rmh.pageSize; |
| long nbPages = fileSize / pageSize; |
| |
| // The content |
| String[] pages = new String[( int ) nbPages]; |
| |
| // The number of managed B-trees |
| int nbBtree = rmh.nbBtree; |
| |
| // The revision |
| long revision = rmh.revision; |
| |
| // The first free page |
| long firstFreePage = rmh.firstFreePage; |
| |
| // The current list of B-trees |
| long currentListOfBtreesPage = rmh.currentListOfBtreesOffset; |
| |
| // The current CopiedPages B-tree |
| long currentCopiedPagesBtreePage = rmh.currentCopiedPagesBtreeOffset; |
| |
| // The current ID |
| long currentID = rmh.idCounter; |
| |
| System.out.println( " RecordManager" ); |
| System.out.println( " -------------" ); |
| System.out.println( " Size = 0x" + Long.toHexString( fileSize ) ); |
| System.out.println( " NbPages = " + nbPages ); |
| System.out.println( " Header " ); |
| System.out.println( " page size : " + pageSize ); |
| System.out.println( " nbTree : " + nbBtree ); |
| System.out.println( " revision : " + revision ); |
| System.out.println( " firstFreePage : 0x" + Long.toHexString( firstFreePage ) ); |
| System.out.println( " current LoB : 0x" + Long.toHexString( currentListOfBtreesPage ) ); |
| System.out.println( " current CopiedPages : 0x" + Long.toHexString( currentCopiedPagesBtreePage ) ); |
| System.out.println( " ID : " + currentID ); |
| |
| // Dump the Free pages list |
| if ( firstFreePage != BTreeConstants.NO_PAGE ) |
| { |
| dumpFreePages( rmh, firstFreePage ); |
| } |
| |
| // Dump the List of B-trees |
| if ( currentListOfBtreesPage != BTreeConstants.NO_PAGE ) |
| { |
| dumpListOfBtrees( rmh, pages, currentListOfBtreesPage ); |
| } |
| |
| // Dump the CopiedPages B-tree |
| //dumpBtreeHeader( recordManagerHeader, currentCopiedPagesBtreePage ); |
| |
| // Dump all the user's B-tree |
| System.out.println( "\\---------------------------- Dump ----------------------------/" ); |
| } |
| catch ( IOException ioe ) |
| { |
| System.out.println( "Exception while dumping the file : " + ioe.getMessage() ); |
| } |
| } |
| |
| |
| /** |
| * Dump the RecordManager file |
| * @throws IOException |
| */ |
| public void dump() |
| { |
| System.out.println( "/---------------------------- Dump ----------------------------\\" ); |
| |
| try |
| { |
| RandomAccessFile randomFile = new RandomAccessFile( file, "r" ); |
| FileChannel fileChannel = randomFile.getChannel(); |
| |
| ByteBuffer recordManagerHeaderBuffer = ByteBuffer.allocate( recordManagerHeaderSize ); |
| |
| // load the RecordManager header |
| fileChannel.read( recordManagerHeaderBuffer ); |
| |
| recordManagerHeaderBuffer.rewind(); |
| |
| // The page size |
| long fileSize = fileChannel.size(); |
| int pageSize = recordManagerHeaderBuffer.getInt(); |
| long nbPages = fileSize / pageSize; |
| |
| // The content |
| String[] pages = new String[( int ) nbPages]; |
| |
| // The number of managed B-trees |
| int nbBtree = recordManagerHeaderBuffer.getInt(); |
| |
| // The revision |
| long revision = recordManagerHeaderBuffer.getLong(); |
| |
| // The first free page |
| long firstFreePage = recordManagerHeaderBuffer.getLong(); |
| |
| // The current list of B-trees |
| long currentListOfBtreesPage = recordManagerHeaderBuffer.getLong(); |
| |
| // The current CopiedPages B-tree |
| long currentCopiedPagesBtreePage = recordManagerHeaderBuffer.getLong(); |
| |
| // The current ID |
| long currentID = recordManagerHeaderBuffer.getLong(); |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( "| RecordManager Header |\n" ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( String.format( "| File size = 0x%016x |\n", fileSize ) ); |
| sb.append( String.format( "| NbPages = %16d |\n", nbPages ) ); |
| sb.append( String.format( "| page size = %16d |\n", pageSize ) ); |
| sb.append( String.format( "| nbTrees = %16d |\n", nbBtree ) ); |
| sb.append( String.format( "| revision = %16d |\n", revision ) ); |
| sb.append( String.format( "| FFP = 0x%016x |\n", firstFreePage ) ); |
| sb.append( String.format( "| LOB = 0x%016x |\n", currentListOfBtreesPage ) ); |
| sb.append( String.format( "| CPB = 0x%016x |\n", currentCopiedPagesBtreePage ) ); |
| sb.append( String.format( "| IdCounter = %16d |\n", currentID ) ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| |
| pages[0] = sb.toString(); |
| |
| RecordManagerHeader recordManagerHeader = recordManagerHeaderReference.get(); |
| |
| // Dump the Free pages list |
| if ( firstFreePage != BTreeConstants.NO_PAGE ) |
| { |
| dumpFreePages( recordManagerHeader, firstFreePage ); |
| } |
| |
| // Dump the List of B-trees |
| if ( currentListOfBtreesPage != BTreeConstants.NO_PAGE ) |
| { |
| dumpListOfBtrees( recordManagerHeader, pages, currentListOfBtreesPage ); |
| } |
| |
| // Dump the CopiedPages B-tree |
| if ( currentCopiedPagesBtreePage != BTreeConstants.NO_PAGE ) |
| { |
| dumpBtreeHeader( pages, recordManagerHeader, currentCopiedPagesBtreePage ); |
| } |
| |
| // Dump all the user's B-tree |
| randomFile.close(); |
| |
| long offset = 0L; |
| |
| for ( String page : pages ) |
| { |
| System.out.println( String.format( "0x%08x", offset ) ); |
| System.out.println( page ); |
| offset += pageSize; |
| } |
| |
| System.out.println( "\\---------------------------- Dump ----------------------------/" ); |
| } |
| catch ( IOException ioe ) |
| { |
| System.out.println( "Exception while dumping the file : " + ioe.getMessage() ); |
| } |
| } |
| |
| |
| /** |
| * Dump the RecordManager file |
| * @throws IOException |
| */ |
| public static void dump( String file ) |
| { |
| System.out.println( "/---------------------------- Dump ----------------------------\\" ); |
| |
| try |
| { |
| RandomAccessFile randomFile = new RandomAccessFile( file, "r" ); |
| FileChannel fileChannel = randomFile.getChannel(); |
| |
| ByteBuffer recordManagerHeaderBuffer = ByteBuffer.allocate( recordManagerHeaderSize ); |
| RecordManagerHeader recordManagerHeader = new RecordManagerHeader(); |
| |
| // load the RecordManager header |
| fileChannel.read( recordManagerHeaderBuffer ); |
| |
| recordManagerHeaderBuffer.rewind(); |
| |
| // The page size |
| long fileSize = fileChannel.size(); |
| recordManagerHeader.lastOffset = fileSize; |
| |
| recordManagerHeader.pageSize = recordManagerHeaderBuffer.getInt(); |
| |
| long nbPages = fileSize / recordManagerHeader.pageSize; |
| |
| // The content |
| String[] pages = new String[( int ) nbPages]; |
| |
| // The number of managed B-trees |
| recordManagerHeader.nbBtree = recordManagerHeaderBuffer.getInt(); |
| |
| // The revision |
| recordManagerHeader.revision = recordManagerHeaderBuffer.getLong(); |
| |
| // The first free page |
| recordManagerHeader.firstFreePage = recordManagerHeaderBuffer.getLong(); |
| |
| // The current list of B-trees |
| recordManagerHeader.currentListOfBtreesOffset = recordManagerHeaderBuffer.getLong(); |
| |
| // The current CopiedPages B-tree |
| recordManagerHeader.currentCopiedPagesBtreeOffset = recordManagerHeaderBuffer.getLong(); |
| |
| // The current ID |
| recordManagerHeader.idCounter = recordManagerHeaderBuffer.getLong(); |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( "| RecordManager |\n" ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( String.format( "| File size = 0x%08x +\n", fileSize ) ); |
| sb.append( String.format( "| NbPages %8d +\n", fileSize ) ); |
| sb.append( "| NbPages = " + nbPages ); |
| sb.append( "| page size : " + recordManagerHeader.pageSize ); |
| sb.append( "| nbTree : " + recordManagerHeader.nbBtree ); |
| sb.append( "| revision : " + recordManagerHeader.revision ); |
| sb.append( "| firstFreePage : 0x" + Long.toHexString( recordManagerHeader.firstFreePage ) ); |
| sb.append( "| current LoB : 0x" + Long.toHexString( recordManagerHeader.currentListOfBtreesOffset ) ); |
| sb.append( "| current CopiedPages : 0x" + Long.toHexString( recordManagerHeader.currentCopiedPagesBtreeOffset ) ); |
| sb.append( "| ID : " + recordManagerHeader.idCounter ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| |
| pages[0] = sb.toString(); |
| |
| |
| // Dump the Free pages list |
| if ( recordManagerHeader.firstFreePage != BTreeConstants.NO_PAGE ) |
| { |
| dumpFreePages( fileChannel, recordManagerHeader, recordManagerHeader.firstFreePage ); |
| } |
| |
| // Dump the List of B-trees |
| if ( recordManagerHeader.currentListOfBtreesOffset != BTreeConstants.NO_PAGE ) |
| { |
| dumpListOfBtrees( recordManagerHeader, fileChannel, recordManagerHeader.currentListOfBtreesOffset ); |
| } |
| |
| // Dump the CopiedPages B-tree |
| dumpBtreeHeader( " ", recordManagerHeader, fileChannel, recordManagerHeader.currentCopiedPagesBtreeOffset ); |
| |
| // Dump all the user's B-tree |
| randomFile.close(); |
| System.out.println( "\\---------------------------- Dump ----------------------------/" ); |
| |
| for ( String page : pages ) |
| { |
| System.out.println( page ); |
| } |
| } |
| catch ( IOException ioe ) |
| { |
| System.out.println( "Exception while dumping the file : " + ioe.getMessage() ); |
| } |
| } |
| |
| |
| /** |
| * Dump the free pages |
| */ |
| private void dumpFreePages( RecordManagerHeader recordManagerHeader, long freePageOffset ) throws IOException |
| { |
| System.out.println( " FreePages : " ); |
| int pageNb = 1; |
| |
| while ( freePageOffset != BTreeConstants.NO_PAGE ) |
| { |
| PageIO pageIo = fetchPageIO( recordManagerHeader.pageSize, freePageOffset ); |
| |
| System.out.println( " freePage[" + pageNb + "] : 0x" + Long.toHexString( pageIo.getOffset() ) ); |
| |
| freePageOffset = pageIo.getNextPage(); |
| pageNb++; |
| } |
| } |
| |
| |
| /** |
| * Dump the free pages |
| */ |
| private static void dumpFreePages( FileChannel fileChannel, RecordManagerHeader recordManagerHeader, long freePageOffset ) throws IOException |
| { |
| System.out.println( "\n FreePages : " ); |
| int pageNb = 1; |
| |
| while ( freePageOffset != BTreeConstants.NO_PAGE ) |
| { |
| PageIO pageIo = fetchPageIO( recordManagerHeader, fileChannel, recordManagerHeader.pageSize, freePageOffset ); |
| |
| System.out.println( " freePage[" + pageNb + "] : 0x" + Long.toHexString( pageIo.getOffset() ) ); |
| |
| freePageOffset = pageIo.getNextPage(); |
| pageNb++; |
| } |
| } |
| |
| |
| /** |
| * Dump the list of B-trees |
| */ |
| private void dumpListOfBtrees( RecordManagerHeader recordManagerHeader, String[] pages, |
| long listOffset ) throws EndOfFileExceededException, IOException |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // First read the List of B-tree data |
| PageIO[] pageIos = readPageIOs( pageSize, listOffset, Long.MAX_VALUE ); |
| |
| // Read the common infos |
| long dataPos = 0L; |
| |
| // The page ID |
| long pageId = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The number of lists |
| int nbLists = readInt( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE; |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( "| List Of Btrees |\n" ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( String.format( "| LoB ID = %16d |\n", pageId ) ); |
| sb.append( String.format( "| NbLists = %16d |\n", nbLists ) ); |
| |
| for ( int i = 0; i < nbLists; i++ ) |
| { |
| if ( i == nbLists - 1 ) |
| { |
| sb.append( "| ---- Current list |\n" ); |
| } |
| else |
| { |
| sb.append( String.format( "| ---- List[%04d] |\n", i ) ); |
| } |
| |
| // The number of B-trees in the list |
| int nbBtrees = readInt( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE; |
| |
| // The revision |
| long revision = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| sb.append( String.format( "| NbBtrees = %16d |\n", nbBtrees ) ); |
| sb.append( String.format( "| Revision = %16d |\n", revision ) ); |
| |
| boolean isFirst = true; |
| |
| if ( nbBtrees > 0 ) |
| { |
| int btreeIndex = 0; |
| for ( ; nbBtrees> 0; nbBtrees-- ) |
| { |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| //System.out.println( " ........" ); |
| } |
| |
| long btreeOffset = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| sb.append( String.format( "| BH[%04d] = 0x%016x |\n", btreeIndex, btreeOffset ) ); |
| btreeIndex++; |
| |
| dumpBtreeHeader( pages, recordManagerHeader, btreeOffset ); |
| } |
| } |
| else |
| { |
| sb.append( "| No Btree |\n" ); |
| } |
| } |
| |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| |
| pages[( int ) ( listOffset / pageSize )] = sb.toString(); |
| } |
| |
| |
| /** |
| * Dump the list of B-trees |
| */ |
| private static void dumpListOfBtrees( RecordManagerHeader recordManagerHeader, |
| FileChannel fileChannel, long listOffset ) throws EndOfFileExceededException, IOException |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // First read the List of B-tree data |
| PageIO[] pageIos = readPageIOs( recordManagerHeader, fileChannel, pageSize, listOffset, Long.MAX_VALUE ); |
| |
| // Read the common infos |
| long dataPos = 0L; |
| |
| // The number of lists |
| int nbLists = readInt( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE; |
| |
| System.out.println( " LoB (0x" + Long.toHexString( listOffset ) + ')' ); |
| System.out.println( " Number of lists : " + nbLists ); |
| |
| for ( int i = 0; i < nbLists; i++ ) |
| { |
| // The number of B-trees in the list |
| int nbBtrees = readInt( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE; |
| |
| // The revision |
| long revision = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| System.out.println( " List of BTrees[" + ( i + 1 ) + ']' ); |
| System.out.println( " -------------------" ); |
| System.out.println( " Revision = " + revision ); |
| |
| boolean isFirst = true; |
| |
| if ( nbBtrees > 0 ) |
| { |
| System.out.println( " nbBtrees = " + nbBtrees ); |
| |
| for ( ; nbBtrees> 0; nbBtrees-- ) |
| { |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| System.out.println( " ........" ); |
| } |
| |
| long btreeOffset = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| System.out.println( " B-tree header offset : 0x" + Long.toHexString( btreeOffset ) ); |
| dumpBtreeHeader( " ", recordManagerHeader, fileChannel, btreeOffset ); |
| } |
| } |
| else |
| { |
| System.out.println( " No B-trees" ); |
| } |
| } |
| } |
| |
| |
| /** |
| * Dump the B-tree info content |
| * |
| * <pre> |
| * +------------+ |
| * | pageNbElem | The B-tree number of elements per page max |
| * +------------+ |
| * | nameSize | The B-tree name size |
| * +------------+ |
| * | name | The B-tree name |
| * +------------+ |
| * | keySerSize | The keySerializer FQCN size |
| * +------------+ |
| * | keySerFQCN | The keySerializer FQCN |
| * +------------+ |
| * | valSerSize | The Value serializer FQCN size |
| * +------------+ |
| * | valSerFQCN | The valueSerializer FQCN |
| * +------------+ |
| * </pre> |
| * @param recordManagerHeader |
| * @param btreeInfoOffset |
| * @throws EndOfFileExceededException |
| * @throws IOException |
| */ |
| private BTreeInfo<?, ?> dumpBtreeInfo( String[] pages, RecordManagerHeader recordManagerHeader, long btreeInfoOffset, String btreeHeader ) throws EndOfFileExceededException, IOException |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // First read the B-tree header |
| PageIO[] pageIos = readPageIOs( pageSize, btreeInfoOffset, Long.MAX_VALUE ); |
| |
| long dataPos = 0L; |
| |
| // The B-tree nb-elem |
| int btreenbElem = readInt( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE; |
| |
| // The tree name |
| ByteBuffer btreeNameBytes = readBytes( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE + btreeNameBytes.limit(); |
| String btreeName = Strings.utf8ToString( btreeNameBytes ); |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( String.format( "| Btree info %-68s|\n", btreeName ) ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( String.format( "| NbElem/pg = %16d |\n", btreenbElem ) ); |
| |
| // The keySerializer FQCN |
| ByteBuffer keySerializerBytes = readBytes( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE + keySerializerBytes.limit(); |
| |
| String keySerializerFqcn = ""; |
| |
| if ( keySerializerBytes != null ) |
| { |
| keySerializerFqcn = Strings.utf8ToString( keySerializerBytes ); |
| } |
| |
| sb.append( String.format( "| keySer = %-65s|\n", keySerializerFqcn ) ); |
| |
| // The valueSerialier FQCN |
| ByteBuffer valueSerializerBytes = readBytes( pageSize, pageIos, dataPos ); |
| |
| String valueSerializerFqcn = ""; |
| dataPos += BTreeConstants.INT_SIZE + valueSerializerBytes.limit(); |
| |
| if ( valueSerializerBytes != null ) |
| { |
| valueSerializerFqcn = Strings.utf8ToString( valueSerializerBytes ); |
| } |
| |
| sb.append( String.format( "| valSer = %-65s|\n", valueSerializerFqcn ) ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| |
| pages[( int ) ( btreeInfoOffset / pageSize )] = sb.toString(); |
| |
| BTreeInfo<?, ?> btreeInfo = new BTreeInfo<>(); |
| btreeInfo.setName( btreeName ); |
| btreeInfo.setKeySerializerFQCN( keySerializerFqcn ); |
| btreeInfo.setValueSerializerFQCN( valueSerializerFqcn ); |
| |
| return btreeInfo; |
| } |
| |
| |
| |
| /** |
| * Dump the B-tree info content |
| * |
| * <pre> |
| * +------------+ |
| * | pageNbElem | The B-tree number of elements per page max |
| * +------------+ |
| * | nameSize | The B-tree name size |
| * +------------+ |
| * | name | The B-tree name |
| * +------------+ |
| * | keySerSize | The keySerializer FQCN size |
| * +------------+ |
| * | keySerFQCN | The keySerializer FQCN |
| * +------------+ |
| * | valSerSize | The Value serializer FQCN size |
| * +------------+ |
| * | valSerFQCN | The valueSerializer FQCN |
| * +------------+ |
| * </pre> |
| * @param recordManagerHeader |
| * @param btreeInfoOffset |
| * @throws EndOfFileExceededException |
| * @throws IOException |
| */ |
| private static <K, V> BTreeInfo<K, V> dumpBtreeInfo( String tabs, RecordManagerHeader recordManagerHeader, FileChannel fileChannel, long btreeInfoOffset ) throws EndOfFileExceededException, IOException |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // First read the B-tree header |
| PageIO[] pageIos = readPageIOs( recordManagerHeader, fileChannel, pageSize, btreeInfoOffset, Long.MAX_VALUE ); |
| |
| long dataPos = 0L; |
| BTreeInfo<K, V> btreeInfo = new BTreeInfo<K, V>(); |
| |
| // The B-tree nb-elem |
| btreeInfo.setPageNbElem( readInt( pageSize, pageIos, dataPos ) ); |
| dataPos += BTreeConstants.INT_SIZE; |
| |
| // The tree name |
| ByteBuffer btreeNameBytes = readBytes( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE + btreeNameBytes.limit(); |
| btreeInfo.setName( Strings.utf8ToString( btreeNameBytes ) ); |
| |
| System.out.println( tabs + " B-tree name: " + btreeInfo.getName() ); |
| |
| // The keySerializer FQCN |
| ByteBuffer keySerializerBytes = readBytes( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.INT_SIZE + keySerializerBytes.limit(); |
| |
| if ( keySerializerBytes != null ) |
| { |
| btreeInfo.setKeySerializerFQCN( Strings.utf8ToString( keySerializerBytes ) ); |
| } |
| |
| System.out.println( tabs + " Key serializer: " + btreeInfo.getKeySerializerFQCN() ); |
| |
| // The valueSerialier FQCN |
| ByteBuffer valueSerializerBytes = readBytes( pageSize, pageIos, dataPos ); |
| |
| dataPos += BTreeConstants.INT_SIZE + valueSerializerBytes.limit(); |
| |
| if ( valueSerializerBytes != null ) |
| { |
| btreeInfo.setValueSerializerFQCN( Strings.utf8ToString( valueSerializerBytes ) ); |
| } |
| |
| System.out.println( tabs + " Value serializer: " + btreeInfo.getValueSerializerFQCN() ); |
| |
| return btreeInfo; |
| } |
| |
| |
| /** |
| * Dump a B-tree Header |
| * <ul> |
| * <li>the Page ID : a long</li> |
| * <li>the revision : a long</li> |
| * <li>the number of elements in the B-tree : a long</li> |
| * <li>the root page offset : a long</li> |
| * <li>the B-tree info page offset : a long</li> |
| * </ul> |
| */ |
| private void dumpRootPage( String[] pages, BTreeInfo<?, ?> btreeInfo, RecordManagerHeader recordManagerHeader, long btreeOffset ) throws EndOfFileExceededException, IOException |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // First read the rootPage |
| PageIO[] pageIos = readPageIOs( pageSize, btreeOffset, Long.MAX_VALUE ); |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( String.format( "| RootPage %-69s |\n", btreeInfo.getName() ) ); |
| |
| dumpPage( sb, btreeInfo, pageSize, pageIos ); |
| |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| |
| pages[ ( int ) btreeOffset / pageSize] = sb.toString(); |
| } |
| |
| |
| /** |
| * Dump a B-tree Header |
| * <ul> |
| * <li>the Page ID : a long</li> |
| * <li>the revision : a long</li> |
| * <li>the number of elements in the B-tree : a long</li> |
| * <li>the root page offset : a long</li> |
| * <li>the B-tree info page offset : a long</li> |
| * </ul> |
| */ |
| private static void dumpRootPage( String tabs, RecordManagerHeader recordManagerHeader, FileChannel fileChannel, |
| long btreeOffset, BTreeInfo btreeInfo ) throws EndOfFileExceededException, IOException |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // First read the rootPage |
| PageIO[] pageIos = readPageIOs( recordManagerHeader, fileChannel, pageSize, btreeOffset, Long.MAX_VALUE ); |
| |
| dumpPage( tabs, recordManagerHeader, fileChannel, pageSize, pageIos, btreeInfo ); |
| } |
| |
| |
| /** |
| * Dump a B-tree Header |
| * <ul> |
| * <li>the Page ID : a long</li> |
| * <li>the revision : a long</li> |
| * <li>the number of elements in the B-tree : a long</li> |
| * <li>the root page offset : a long</li> |
| * <li>the B-tree info page offset : a long</li> |
| * </ul> |
| */ |
| private long dumpBtreeHeader( String[] pages, RecordManagerHeader recordManagerHeader, |
| long btreeOffset ) throws EndOfFileExceededException, IOException |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // First read the B-tree header |
| PageIO[] pageIos = readPageIOs( pageSize, btreeOffset, Long.MAX_VALUE ); |
| |
| long dataPos = 0L; |
| |
| // The B-tree current ID |
| long currentID = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The B-tree current revision |
| long revision = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The nb elems in the tree |
| long nbElems = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The B-tree rootPage offset |
| long rootPageOffset = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The B-tree Info offset |
| long infoPageOffset = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| BTreeInfo<?, ?> btreeInfo = dumpBtreeInfo( pages, recordManagerHeader, infoPageOffset, "(ID:" + currentID + ", 0x" + Long.toHexString( btreeOffset ) + ")" ); |
| |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( String.format( "| Btree Header %-66s|\n", btreeInfo.getName() ) ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| sb.append( String.format( "| ID = %16d |\n", currentID ) ); |
| sb.append( String.format( "| revision = %16d |\n", revision ) ); |
| sb.append( String.format( "| NbElems = %16d |\n", nbElems ) ); |
| sb.append( String.format( "| RootPage = 0x%016x |\n", rootPageOffset ) ); |
| sb.append( String.format( "| BtInfo = 0x%016x |\n", infoPageOffset ) ); |
| sb.append( "+--------------------------------------------------------------------------------+\n" ); |
| |
| pages[ ( int ) btreeOffset / pageSize ] = sb.toString(); |
| |
| dumpRootPage( pages, btreeInfo, recordManagerHeader, rootPageOffset ); |
| |
| /* |
| if ( LOG.isDebugEnabled() ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| boolean isFirst = true; |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| sb.append( ", " ); |
| } |
| |
| sb.append( "0x" ).append( Long.toHexString( pageIo.getOffset() ) ); |
| } |
| |
| String pageIoList = sb.toString(); |
| |
| LOG.debug( " PageIOs[{}] = {}", pageIos.length, pageIoList ); |
| |
| // System.out.println( " dataSize = "+ pageIos[0].getSize() ); |
| LOG.debug( " dataSize = {}", pageIos[0].getSize() ); |
| |
| LOG.debug( " B-tree '{}'", btreeName ); |
| LOG.debug( " revision : {}", revision ); |
| LOG.debug( " nbElems : {}", nbElems ); |
| LOG.debug( " rootPageOffset : 0x{}", Long.toHexString( rootPageOffset ) ); |
| LOG.debug( " B-tree page size : {}", btreePageSize ); |
| LOG.debug( " keySerializer : '{}'", keySerializerFqcn ); |
| LOG.debug( " valueSerializer : '{}'", valueSerializerFqcn ); |
| LOG.debug( " dups allowed : {}", dupsAllowed ); |
| // |
| // System.out.println( " B-tree '" + btreeName + "'" ); |
| // System.out.println( " revision : " + revision ); |
| // System.out.println( " nbElems : " + nbElems ); |
| // System.out.println( " rootPageOffset : 0x" + Long.toHexString( rootPageOffset ) ); |
| // System.out.println( " B-tree page size : " + btreePageSize ); |
| // System.out.println( " keySerializer : " + keySerializerFqcn ); |
| // System.out.println( " valueSerializer : " + valueSerializerFqcn ); |
| // System.out.println( " dups allowed : " + dupsAllowed ); |
| } |
| */ |
| |
| return rootPageOffset; |
| } |
| |
| |
| /** |
| * Dump a B-tree Header |
| * <ul> |
| * <li>the Page ID : a long</li> |
| * <li>the revision : a long</li> |
| * <li>the number of elements in the B-tree : a long</li> |
| * <li>the root page offset : a long</li> |
| * <li>the B-tree info page offset : a long</li> |
| * </ul> |
| */ |
| private static long dumpBtreeHeader( String tabs, RecordManagerHeader recordManagerHeader, FileChannel fileChannel, long btreeOffset ) throws EndOfFileExceededException, IOException |
| { |
| int pageSize = recordManagerHeader.pageSize; |
| |
| // First read the B-tree header |
| PageIO[] pageIos = readPageIOs( recordManagerHeader, fileChannel, pageSize, btreeOffset, Long.MAX_VALUE ); |
| |
| long dataPos = 0L; |
| |
| // The B-tree current ID |
| long currentID = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The B-tree current revision |
| long revision = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The nb elems in the tree |
| long nbElems = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The B-tree rootPage offset |
| long rootPageOffset = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| // The B-tree Info offset |
| long infoPageOffset = readLong( pageSize, pageIos, dataPos ); |
| dataPos += BTreeConstants.LONG_SIZE; |
| |
| BTreeInfo btreeInfo = dumpBtreeInfo( tabs, recordManagerHeader, fileChannel, infoPageOffset ); |
| |
| System.out.println( tabs + " revision : " + revision ); |
| System.out.println( tabs + " nbElems : " + nbElems ); |
| System.out.println( tabs + " rootPageOffset : 0x" + Long.toHexString( rootPageOffset ) ); |
| |
| dumpRootPage( tabs + " ", recordManagerHeader, fileChannel, rootPageOffset, btreeInfo ); |
| |
| /* |
| if ( LOG.isDebugEnabled() ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| boolean isFirst = true; |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| sb.append( ", " ); |
| } |
| |
| sb.append( "0x" ).append( Long.toHexString( pageIo.getOffset() ) ); |
| } |
| |
| String pageIoList = sb.toString(); |
| |
| LOG.debug( " PageIOs[{}] = {}", pageIos.length, pageIoList ); |
| |
| // System.out.println( " dataSize = "+ pageIos[0].getSize() ); |
| LOG.debug( " dataSize = {}", pageIos[0].getSize() ); |
| |
| LOG.debug( " B-tree '{}'", btreeName ); |
| LOG.debug( " revision : {}", revision ); |
| LOG.debug( " nbElems : {}", nbElems ); |
| LOG.debug( " rootPageOffset : 0x{}", Long.toHexString( rootPageOffset ) ); |
| LOG.debug( " B-tree page size : {}", btreePageSize ); |
| LOG.debug( " keySerializer : '{}'", keySerializerFqcn ); |
| LOG.debug( " valueSerializer : '{}'", valueSerializerFqcn ); |
| LOG.debug( " dups allowed : {}", dupsAllowed ); |
| // |
| // System.out.println( " B-tree '" + btreeName + "'" ); |
| // System.out.println( " revision : " + revision ); |
| // System.out.println( " nbElems : " + nbElems ); |
| // System.out.println( " rootPageOffset : 0x" + Long.toHexString( rootPageOffset ) ); |
| // System.out.println( " B-tree page size : " + btreePageSize ); |
| // System.out.println( " keySerializer : " + keySerializerFqcn ); |
| // System.out.println( " valueSerializer : " + valueSerializerFqcn ); |
| // System.out.println( " dups allowed : " + dupsAllowed ); |
| } |
| */ |
| |
| return rootPageOffset; |
| } |
| |
| |
| /** |
| * Get the number of managed trees. We don't count the CopiedPage B-tree and the B-tree of B-trees |
| * |
| * @return The number of managed B-trees |
| */ |
| public int getNbManagedTrees( RecordManagerHeader recordManagerHeader ) |
| { |
| return recordManagerHeader.nbBtree; |
| } |
| |
| |
| /** |
| * Get the managed B-trees. We don't return the CopiedPage B-tree nor the B-tree of B-trees. |
| * |
| * @return The managed B-trees |
| */ |
| public Set<String> getManagedTrees() |
| { |
| return new HashSet<>( getCurrentRecordManagerHeader().btreeMap.keySet() ); |
| } |
| |
| |
| /** |
| * Stores the copied pages into the CopiedPages B-tree |
| * |
| * @param name The B-tree name |
| * @param revision The revision |
| * @param copiedPages The pages that have been copied while creating this revision |
| * @throws IOException If we weren't able to store the data on disk |
| */ |
| /* No Qualifier */void storeCopiedPages( WriteTransaction transaction, String name, long revision, long[] copiedPages ) throws IOException |
| { |
| RevisionName revisionName = new RevisionName( revision, name ); |
| |
| copiedPageBtree.insert( transaction, revisionName, copiedPages ); |
| } |
| |
| |
| /** |
| * Store a reference to an old rootPage into the Revision B-tree |
| * |
| * @param btree The B-tree we want to keep an old RootPage for |
| * @param rootPage The old rootPage |
| * @throws IOException If we have an issue while writing on disk |
| */ |
| /* No qualifier */<K, V> void storeRootPage( WriteTransaction transaction, BTree<K, V> btree, Page<K, V> rootPage ) throws IOException |
| { |
| if ( !isKeepRevisions() ) |
| { |
| return; |
| } |
| |
| if ( btree == copiedPageBtree ) |
| { |
| return; |
| } |
| |
| NameRevision nameRevision = new NameRevision( btree.getName(), rootPage.getRevision() ); |
| |
| if ( LOG_CHECK.isDebugEnabled() ) |
| { |
| MavibotInspector.check( this ); |
| } |
| } |
| |
| |
| /** |
| * Get one managed B-tree, knowing its name. It will return the B-tree latest version. |
| * |
| * @param transaction The {@link Transaction} we are running in |
| * @param name The B-tree name we are looking for |
| * @return The managed b-tree |
| * @throws IOException If we can't find the B-tree in the file |
| */ |
| public <K, V> BTree<K, V> getBtree( Transaction transaction, String name ) throws IOException |
| { |
| return getBtree( transaction, name, 0L ); |
| } |
| |
| |
| /** |
| * Get one managed B-tree, knowing its name and its version. It will return the B-tree requested version |
| * |
| * @param transaction The {@link Transaction} we are running in |
| * @param name The B-tree name we are looking for |
| * @param revision The B-tree} revision we are looking for (if <=0, the latest one) |
| * @return The managed b-tree |
| * @throws IOException If we can't find the B-tree in the file |
| */ |
| public <K, V> BTree<K, V> getBtree( Transaction transaction, String name, long revision ) throws IOException |
| { |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| |
| // Get the btree from the recordManagerHeader if we have it |
| BTree<K, V> btree = transaction.getBTree( name ); |
| |
| if ( btree != null ) |
| { |
| return btree; |
| } |
| |
| /* |
| // Not already loaded, so load it |
| BTree<NameRevision, Long> listOfBtrees = recordManagerHeader.btreeMap.get( key )getListOfBtrees(); |
| |
| // Find the next B-tree reference in the LOB (so revision +1, as we will pick the prev revision) |
| if ( ( revision <= 0L ) || ( revision == Long.MAX_VALUE ) ) |
| { |
| revision = Long.MAX_VALUE; |
| } |
| else |
| { |
| revision++; |
| } |
| |
| NameRevision nameRevision = new NameRevision( name, revision ); |
| |
| TupleCursor<NameRevision, Long> cursor = listOfBtrees.browseFrom( transaction, nameRevision ); |
| |
| if ( cursor.hasPrev() ) |
| { |
| Tuple<NameRevision, Long> tuple = cursor.prev(); |
| |
| if ( tuple != null ) |
| { |
| try |
| { |
| // The B-tree has been found. We got back its offset, so load it |
| return readBTree( transaction, tuple.getValue() ); |
| } |
| catch ( Exception e ) |
| { |
| throw new IOException( e.getMessage() ); |
| } |
| } |
| } |
| */ |
| |
| return null; |
| } |
| |
| |
| /** |
| * Move a list of pages to the free page list. A logical page is associated with one |
| * or more physical PageIOs, which are on the disk. We have to move all those PagIO instances |
| * to the free list, and do the same in memory (we try to keep a reference to a set of |
| * free pages. |
| * |
| * @param btree The B-tree which were owning the pages |
| * @param revision The current revision |
| * @param pages The pages to free |
| * @throws IOException If we had a problem while updating the file |
| * @throws EndOfFileExceededException If we tried to write after the end of the file |
| */ |
| /* Package protected */<K, V> void freePages( WriteTransaction transaction, BTree<K, V> btree, long revision, List<Page<K, V>> pages ) |
| throws EndOfFileExceededException, IOException |
| { |
| if ( ( pages == null ) || pages.isEmpty() ) |
| { |
| return; |
| } |
| |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| |
| if ( !keepRevisions ) |
| { |
| // if the B-tree doesn't keep revisions, we can safely move |
| // the pages to the freed page list. |
| if ( LOG.isDebugEnabled() ) |
| { |
| LOG.debug( "Freeing the following pages :" ); |
| |
| for ( Page<K, V> page : pages ) |
| { |
| LOG.debug( " {}", page ); |
| } |
| } |
| |
| for ( Page<K, V> page : pages ) |
| { |
| long pageOffset = ( ( AbstractPage<K, V> ) page ).getOffset(); |
| |
| PageIO[] pageIos = readPageIOs( recordManagerHeader.pageSize, pageOffset, Long.MAX_VALUE ); |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| freedPages.add( pageIo ); |
| } |
| } |
| } |
| else |
| { |
| // We are keeping revisions of standard B-trees, so we move the pages to the CopiedPages B-tree |
| // but only for non managed B-trees |
| if ( LOG.isDebugEnabled() ) |
| { |
| LOG.debug( "Moving the following pages to the CopiedBtree :" ); |
| |
| for ( Page<K, V> page : pages ) |
| { |
| LOG.debug( " {}", page ); |
| } |
| } |
| |
| long[] pageOffsets = new long[pages.size()]; |
| int pos = 0; |
| |
| for ( Page<K, V> page : pages ) |
| { |
| pageOffsets[pos++] = ( ( AbstractPage<K, V> ) page ).offset; |
| } |
| |
| if ( ( btree.getType() != BTreeTypeEnum.BTREE_OF_BTREES ) |
| && ( btree.getType() != BTreeTypeEnum.COPIED_PAGES_BTREE ) ) |
| { |
| // Deal with standard B-trees |
| RevisionName revisionName = new RevisionName( revision, btree.getName() ); |
| |
| copiedPageBtree.insert( transaction, revisionName, pageOffsets ); |
| |
| // Update the RecordManager Copiedpage Offset |
| recordManagerHeader.currentCopiedPagesBtreeOffset = ( ( BTree<RevisionName, long[]> ) copiedPageBtree ) |
| .getBtreeOffset(); |
| } |
| else |
| { |
| // Managed B-trees : we simply free the copied pages |
| for ( long pageOffset : pageOffsets ) |
| { |
| PageIO[] pageIos = readPageIOs( recordManagerHeader.pageSize, pageOffset, Long.MAX_VALUE ); |
| |
| for ( PageIO pageIo : pageIos ) |
| { |
| freedPages.add( pageIo ); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Add a PageIO to the list of free PageIOs |
| * |
| * @param pageIo The page to free |
| * @throws IOException If we weren't capable of updating the file |
| */ |
| /* no qualifier */ void free( WriteTransaction transaction, PageIO pageIo ) throws IOException |
| { |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| |
| freePageLock.lock(); |
| |
| // We add the Page's PageIOs before the |
| // existing free pages. |
| // Link it to the first free page |
| pageIo.setNextPage( recordManagerHeader.firstFreePage ); |
| |
| LOG.debug( "Flushing the first free page" ); |
| |
| // And flush it to disk |
| //FIXME can be flushed last after releasing the lock |
| flushPages( recordManagerHeader, pageIo ); |
| |
| // We can update the firstFreePage offset |
| recordManagerHeader.firstFreePage = pageIo.getOffset(); |
| |
| freePageLock.unlock(); |
| } |
| |
| |
| /** |
| * Add an array of PageIOs to the list of free PageIOs |
| * |
| * @param offsets The offsets of the pages whose associated PageIOs will be fetched and freed. |
| * @throws IOException If we weren't capable of updating the file |
| */ |
| /*no qualifier*/ void free( Transaction transaction, long... offsets ) throws IOException |
| { |
| RecordManagerHeader recordManagerHeader = transaction.getRecordManagerHeader(); |
| |
| freePageLock.lock(); |
| |
| List<PageIO> pageIos = new ArrayList<>(); |
| int pageIndex = 0; |
| for ( int i = 0; i < offsets.length; i++ ) |
| { |
| PageIO[] ios = readPageIOs( recordManagerHeader.pageSize, offsets[i], Long.MAX_VALUE ); |
| |
| for ( PageIO io : ios ) |
| { |
| pageIos.add( io ); |
| |
| if ( pageIndex > 0 ) |
| { |
| pageIos.get( pageIndex - 1 ).setNextPage( io.getOffset() ); |
| } |
| |
| pageIndex++; |
| } |
| } |
| |
| // We add the Page's PageIOs before the |
| // existing free pages. |
| // Link it to the first free page |
| pageIos.get( pageIndex - 1 ).setNextPage( recordManagerHeader.firstFreePage ); |
| |
| LOG.debug( "Flushing the first free page" ); |
| |
| // And flush it to disk |
| //FIXME can be flushed last after releasing the lock |
| flushPages( recordManagerHeader, pageIos.toArray( new PageIO[0] ) ); |
| |
| // We can update the firstFreePage offset |
| recordManagerHeader.firstFreePage = pageIos.get( 0 ).getOffset(); |
| |
| freePageLock.unlock(); |
| } |
| |
| |
| /** |
| * @return the keepRevisions flag |
| */ |
| public boolean isKeepRevisions() |
| { |
| return keepRevisions; |
| } |
| |
| |
| /** |
| * @param keepRevisions the keepRevisions flag to set |
| */ |
| public void setKeepRevisions( boolean keepRevisions ) |
| { |
| this.keepRevisions = keepRevisions; |
| } |
| |
| |
| /** |
| * Creates a B-tree and automatically adds it to the list of managed btrees |
| * |
| * @param name the name of the B-tree |
| * @param keySerializer key serializer |
| * @param valueSerializer value serializer |
| * @return a managed B-tree |
| * @throws IOException If we weren't able to update the file on disk |
| * @throws BTreeAlreadyManagedException If the B-tree is already managed |
| */ |
| public <K, V> BTree<K, V> addBTree( WriteTransaction transaction, String name, ElementSerializer<K> keySerializer, |
| ElementSerializer<V> valueSerializer ) |
| throws IOException, BTreeAlreadyManagedException |
| { |
| BTreeConfiguration<K, V> config = new BTreeConfiguration<>(); |
| |
| config.setName( name ); |
| config.setKeySerializer( keySerializer ); |
| config.setValueSerializer( valueSerializer ); |
| |
| BTree<K, V> btree = new BTree<>( transaction, config ); |
| |
| manage( transaction, btree ); |
| |
| return btree; |
| } |
| |
| |
| /** |
| * Get the current BTreeHeader for a given Btree. It might not exist |
| */ |
| public BTreeHeader getBTreeHeader( String name ) |
| { |
| // Get a lock |
| btreeHeadersLock.readLock().lock(); |
| |
| // get the current BTree Header for this BTree and revision |
| BTreeHeader<?, ?> btreeHeader = currentBTreeHeaders.get( name ); |
| |
| // And unlock |
| btreeHeadersLock.readLock().unlock(); |
| |
| return btreeHeader; |
| } |
| |
| |
| /** |
| * Get the new BTreeHeader for a given Btree. It might not exist |
| */ |
| public BTreeHeader getNewBTreeHeader( String name ) |
| { |
| // get the current BTree Header for this BTree and revision |
| return newBTreeHeaders.get( name ); |
| } |
| |
| |
| /** |
| * @return The byte[] that will contain the Recoed Manager Header |
| */ |
| /* No qualifier */ byte[] getRecordManagerHeaderBytes() |
| { |
| return recordManagerHeaderBytes; |
| } |
| |
| |
| /** |
| * @return The ByteBuffer that will contain the Record Manager Header |
| */ |
| /* No qualifier */ ByteBuffer getRecordManagerHeaderBuffer() |
| { |
| return recordManagerHeaderBuffer; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void updateNewBTreeHeaders( BTreeHeader btreeHeader ) |
| { |
| newBTreeHeaders.put( btreeHeader.getName(), btreeHeader ); |
| } |
| |
| |
| private void checkFreePages() throws EndOfFileExceededException, IOException |
| { |
| RecordManagerHeader recordManagerHeader = recordManagerHeaderReference.get(); |
| |
| // read all the free pages, add them into a set, to be sure we don't have a cycle |
| Set<Long> freePageOffsets = new HashSet<>(); |
| |
| long currentFreePageOffset = recordManagerHeader.firstFreePage; |
| |
| while ( currentFreePageOffset != BTreeConstants.NO_PAGE ) |
| { |
| if ( ( currentFreePageOffset % recordManagerHeader.pageSize ) != 0 ) |
| { |
| throw new InvalidOffsetException( "Wrong offset : " + Long.toHexString( currentFreePageOffset ) ); |
| } |
| |
| if ( freePageOffsets.contains( currentFreePageOffset ) ) |
| { |
| throw new InvalidOffsetException( "Offset : " + Long.toHexString( currentFreePageOffset ) |
| + " already read, there is a cycle" ); |
| } |
| |
| freePageOffsets.add( currentFreePageOffset ); |
| PageIO pageIO = fetchPageIO( recordManagerHeader.pageSize, currentFreePageOffset ); |
| |
| currentFreePageOffset = pageIO.getNextPage(); |
| } |
| |
| return; |
| } |
| |
| |
| /* no qualifier */void _disableReclaimer( boolean toggle ) |
| { |
| this.disableReclaimer = toggle; |
| } |
| |
| |
| /** |
| * @return A copy of the current RecordManager header |
| */ |
| /* No qualifier */ RecordManagerHeader getRecordManagerHeaderCopy() |
| { |
| return recordManagerHeaderReference.get().copy(); |
| } |
| |
| |
| /** |
| * @return The current RecordManager header |
| */ |
| /* No qualifier */ RecordManagerHeader getCurrentRecordManagerHeader() |
| { |
| return recordManagerHeaderReference.get(); |
| } |
| |
| |
| /** |
| * @see Object#toString() |
| */ |
| @Override |
| public String toString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( "RM free pages : [" ); |
| |
| RecordManagerHeader recordManagerHeader = recordManagerHeaderReference.get(); |
| |
| if ( recordManagerHeader.firstFreePage != BTreeConstants.NO_PAGE ) |
| { |
| long current = recordManagerHeader.firstFreePage; |
| boolean isFirst = true; |
| |
| while ( current != BTreeConstants.NO_PAGE ) |
| { |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| sb.append( ", " ); |
| } |
| |
| PageIO pageIo; |
| |
| try |
| { |
| pageIo = fetchPageIO( recordManagerHeader.pageSize, current ); |
| sb.append( pageIo.getOffset() ); |
| current = pageIo.getNextPage(); |
| } |
| catch ( EndOfFileExceededException e ) |
| { |
| e.printStackTrace(); |
| } |
| catch ( IOException e ) |
| { |
| e.printStackTrace(); |
| } |
| |
| } |
| } |
| |
| sb.append( "]" ); |
| |
| return sb.toString(); |
| } |
| } |