blob: d9267a0a925a0630e48c9f6b5ea80736002e2cc8 [file] [log] [blame]
/*
* 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.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* A data structure used to manage a write transaction. We keep a track of all the modified
* {@link WALObject}s, and all the copied {@link WALObject}s, that will be flushed at
* the end of the transaction, if it is successful.
*
* We also keep a track of the last position in the file, as we may reclaim some
* new pages when the FreePage list is empty.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class WriteTransaction extends AbstractTransaction
{
/** The List containing all the user modified pages, in the order they have been created */
private Map<Long, WALObject<?, ?>> newPages = new LinkedHashMap<>();
/** The map containing all the copied pages, using their offset as a key */
private Map<Long, WALObject<?, ?>> copiedPageMap = new HashMap<>();
/**
* Creates a new WriteTransaction instance
*
* @param recordManager The associated {@link RecordManager}
*/
WriteTransaction( RecordManager recordManager )
{
super( recordManager );
// Get a copy of the RMH
recordManagerHeader = recordManager.getRecordManagerHeaderCopy();
// We have to increment the revision
recordManagerHeader.revision++;
}
/**
* {@inheritDoc}
* @throws IOException
*/
@Override
public void close() throws IOException
{
if ( aborted || isClosed() )
{
// We have had an exception, or the txn has been closed : rollback the transaction
newPages.clear();
copiedPageMap.clear();
super.close();
}
else
{
commit();
}
}
private <K, V> void updateRefs( Node<K, V> node )
{
for ( int i = 0; i < node.pageNbElems + 1; i++ )
{
if ( node.children[i] < 0L )
{
// This is a Page ID, replace it with the page offset
WALObject<?, ?> child = newPages.get( -node.children[i] );
node.children[i] = child.getOffset();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void commit() throws IOException
{
if ( !isClosed() && !aborted )
{
// First, find the modified users B-trees, and flush the user's pages
Set<BTreeInfo<?, ?>> btreeInfos = new HashSet<>();
for ( WALObject<?, ?> walObject : newPages.values() )
{
BTreeInfo<?, ?> btreeInfo = walObject.getBtreeInfo();
// Flush the page
if ( walObject instanceof Node )
{
updateRefs( ( Node ) walObject );
}
walObject.serialize( this );
recordManager.flushPages( recordManagerHeader, walObject.getPageIOs() );
// and update the B-tree list if needed
if ( walObject.isBTreeUser() )
{
btreeInfos.add( btreeInfo );
// Also update the recordManagerHeader B-tree map
if ( walObject instanceof BTreeHeader )
{
BTree btree = recordManagerHeader.getBTree( btreeInfo.getName() );
btree.setBtreeHeader( ( BTreeHeader ) walObject );
recordManagerHeader.addBTree( btree );
}
}
}
// We can clean the user's list
newPages.clear();
// Update the LOB, if we aren't already processing the LOB
for ( BTreeInfo<?, ?> btreeInfo : btreeInfos )
{
recordManager.insertInListOfBtrees( recordManagerHeader, this, recordManagerHeader.btreeMap.get( btreeInfo.getName() ) );
}
// Write the list on disk.
recordManager.serializeListOfBtrees( recordManagerHeader, this );
// Flush the newly updated pages
flushNewPages();
// LOB done, clear the list
newPages.clear();
// Add the copied pages in the CPB
recordManager.insertInCopiedPagesBtree( this );
// Last not least, Flush the CPB pages
flushNewPages();
long newListOfBtreesOffet = recordManagerHeader.currentListOfBtreesOffset;
long newCopiedPagesBtreeOffset = recordManagerHeader.copiedPagesBtree.getBtreeHeader().offset;
// And update the RecordManagerHeader
recordManager.updateRecordManagerHeader( recordManagerHeader, newListOfBtreesOffet, newCopiedPagesBtreeOffset );
recordManager.writeRecordManagerHeader( recordManagerHeader );
// close the transaction
//newAdminPages.clear();
newPages.clear();
copiedPageMap.clear();
super.commit();
// Finally add the new RecordManagerHeader in the transaction list
RecordManagerHeader previousRMH = recordManager.activeTransactionsList.peek();
// Corner case : it might be null, if this is the first revision
if ( ( previousRMH != null ) && ( previousRMH.txnCounter.get() == 0 ) )
{
// Ok, we can get rid of it
RecordManagerHeader deadRmh = recordManager.activeTransactionsList.poll();
recordManager.activeTransactionsList.offerFirst( recordManagerHeader );
// And we can clean it up
recordManager.deadTransactionsList.add( deadRmh );
}
else
{
recordManager.activeTransactionsList.offerFirst( recordManagerHeader );
}
cleanup();
}
}
private void cleanup()
{
// We need to get rid of unused RMHs, and the associated B-trees pages.
}
private void flushNewPages() throws IOException
{
for ( WALObject<?, ?> walObject : newPages.values() )
{
if ( walObject instanceof Node )
{
updateRefs( ( Node ) walObject );
}
walObject.serialize( this );
recordManager.flushPages( recordManagerHeader, walObject.getPageIOs() );
}
}
/**
* {@inheritDoc}
*/
@Override
public void abort() throws IOException
{
// We just have to empty the maps
newPages.clear();
copiedPageMap.clear();
aborted = true;
super.close();
}
/**
* Updates the map of new BTreeHeaders
*
* @param <K> The b-tree key type
* @param <V> The b-tree value type
* @param btreeHeader The new BtreeHeader
*/
<K, V> void updateNewBTreeHeaders( BTreeHeader<K, V> btreeHeader )
{
}
/**
* @return The offset of the first free page
*/
/* No qualifier */ long getFirstFreePage()
{
return recordManagerHeader.firstFreePage;
}
/**
* @return The offset of the end of the file
*/
/* No qualifier */ long getLastOffset()
{
return recordManagerHeader.lastOffset;
}
/**
* Change the last offset in the file
*
* @param The new offset of the end of the file
*/
/* No qualifier */ void setLastOffset( long lastOffset )
{
recordManagerHeader.lastOffset = lastOffset;
}
/**
* Add a page in the list of modified {@link WALObject}s. If there is an existing
* {@link WALObject}, it will be replaced with the new content. Note that a {@link WALObject}
* is associated to one to many {@link PageIO}s.
*
* @param walObject The {@link WALObject} to store
*/
/* No qualifier */void addWALObject( WALObject walObject )
{
newPages.put( walObject.getId(), walObject );
recordManager.putPage( walObject );
}
/**
* {@inheritDoc}
*/
@Override
public <K, V> Page<K, V> getPage( BTreeInfo<K, V> btreeInfo, long offset ) throws IOException
{
// Offset will be > 0 of it's a real page offset, and < 0 if it's an ID
if ( offset >= 0 )
{
return ( Page<K, V> ) recordManager.getPage( btreeInfo, recordManagerHeader.pageSize, offset );
}
else
{
return ( Page<K, V> )newPages.get( -offset );
}
}
/**
* Add a page in the list of modified {@link WALObject}s. If there is an existing
* {@link WALObject}, it will be replaced with the new content. Note that a {@link WALObject}
* is associated to one to many {@link PageIO}s.
*
* @param walObject The {@link WALObject} to store
*/
/* No qualifier */WALObject removeWALObject( long id )
{
return newPages.remove( id );
}
/**
* Add a WALObject in the list of copied {@link WALObject}s. If there is an existing
* {@link WALObject}, it will be replaced with the new content. Note that a {@link WALObject}
* is associated to one to many {@link PageIO}s.
*
* @param walObject The {@link WALObject} to store
*/
/* No qualifier */<K, V> void addCopiedWALObject( WALObject<K, V> walObject )
{
if ( ( walObject != null ) && ( walObject.getOffset() != BTreeConstants.NO_PAGE ) && !copiedPageMap.containsKey( walObject.getId() ) )
{
copiedPageMap.put( walObject.getId(), walObject );
}
}
/**
* Search for a {@link WALObject}. It may be present in the cache, or if it's not, we will
* have to fetch it from the disk.
*
* @param offset The position of this {@link WALObject} on disk
* @return The found {@link WALObject}
*/
public WALObject<?, ?> getWALObject( long id )
{
return newPages.get( id );
}
/* No qualifier */ Map<Long, WALObject<?, ?>> getCopiedPageMap()
{
return copiedPageMap;
}
/**
* Update the WAL
*
* @param revision The previous revision
* @param oldPage The previous page
* @param newPage The new page
* @throws IOException If we had an error storing the
**/
/* No qualifier */ <K, V> void updateWAL( long previousRevision, WALObject<K, V> oldPage, WALObject<K, V> newPage )
{
addWALObject( newPage );
if ( ( previousRevision != getRevision() ) && newPage.isBTreeUser() )
{
// Now, move the new child into the copied pages if needed
addCopiedWALObject( oldPage );
}
}
/**
* Creates a new {@link Leaf}, with a valid offset.
*
* @param btreeInfo The {@link BTreeInfo} reference
* @return A new {@link Leaf} instance
*/
/* No qualifier */ <K, V> Leaf<K, V> newLeaf( BTreeInfo<K, V> btreeInfo, int nbElems )
{
Leaf<K, V> leaf = new Leaf<>( btreeInfo, getRevision(), nbElems );
leaf.initId( recordManagerHeader );
return leaf;
}
/**
* Creates a new {@link Leaf}, with a valid offset.
*
* @param btreeInfo The {@link BTreeInfo} reference
* @return A new {@link Leaf} instance
*/
/* No qualifier */ <K, V> Leaf<K, V> newLeaf( BTreeInfo<K, V> btreeInfo, long id, int nbElems )
{
Leaf<K, V> leaf = new Leaf<>( btreeInfo, getRevision(), nbElems );
leaf.setId( id );
return leaf;
}
/**
* Creates a new {@link Node}, with a valid offset.
*
* @param btreeInfo The {@link BTreeInfo} reference
* @return A new {@link Leaf} instance
*/
/* No qualifier */ <K, V> Node<K, V> newNode( BTreeInfo<K, V> btreeInfo, int nbElems )
{
Node<K, V> node = new Node<>( btreeInfo, getRevision(), nbElems );
node.initId( recordManagerHeader );
return node;
}
/**
* Creates a new {@link Node}, with a valid offset.
*
* @param btreeInfo The {@link BTreeInfo} reference
* @return A new {@link Leaf} instance
*/
/* No qualifier */ <K, V> Node<K, V> newNode( BTreeInfo<K, V> btreeInfo, long id, int nbElems )
{
Node<K, V> node = new Node<>( btreeInfo, getRevision(), nbElems );
node.setId( id );
return node;
}
/**
* Creates a new Node which will contain only one key, with references to
* a left and right page. This is a specific constructor used by the btree
* when the root was full when we added a new value.
*
* @param btreeInfo the {@link BTreeInfo} reference
* @param key The new key
* @param leftPage The left page offset
* @param rightPage The right page offset
*/
/* No qualifier */ <K, V> Node<K, V> newNode( BTreeInfo<K, V> btreeInfo, K key, long leftPage, long rightPage )
{
Node<K, V> node = new Node<>( btreeInfo, getRevision(), key, leftPage, rightPage );
node.initId( recordManagerHeader );
return node;
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "W:" );
sb.append( super.toString() );
sb.append( ", last offset" );
sb.append( String.format( " %x", getLastOffset() ) ).append( '\n' );
if ( newPages.size() > 0 )
{
sb.append( " pageList :[\n " );
boolean isFirst = true;
for ( WALObject walObject : newPages.values() )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ",\n " );
}
sb.append( walObject.prettyPrint() );
}
sb.append( '\n' );
}
else
{
sb.append( " UserPageList empty\n" );
}
if ( copiedPageMap.size() > 0 )
{
sb.append( " CopiedPagesMap :[\n " );
boolean isFirst = true;
for ( Entry<Long, WALObject<?, ?>> entry : copiedPageMap.entrySet() )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ",\n " );
}
sb.append( String.format( "%x", entry.getKey() ) );
sb.append( ':' );
sb.append( '<' ).append( entry.getValue().getName() ).append( '>' );
sb.append( entry.getValue().getClass().getSimpleName() );
}
sb.append( "]\n" );
}
else
{
sb.append( " CopiedPagesMap empty\n" );
}
return sb.toString();
}
}