/**
* Copyright (c) 2002-2012 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.ha;
import java.util.EnumMap;
import java.util.Map;
import org.neo4j.com.Response;
import org.neo4j.kernel.DefaultIdGeneratorFactory;
import org.neo4j.kernel.IdGeneratorFactory;
import org.neo4j.kernel.IdType;
import org.neo4j.kernel.ha.cluster.ClusterMemberChangeEvent;
import org.neo4j.kernel.ha.cluster.ClusterMemberListener;
import org.neo4j.kernel.ha.cluster.ClusterMemberState;
import org.neo4j.kernel.ha.cluster.ClusterMemberStateMachine;
import org.neo4j.kernel.impl.nioneo.store.FileSystemAbstraction;
import org.neo4j.kernel.impl.nioneo.store.IdGenerator;
import org.neo4j.kernel.impl.nioneo.store.IdRange;
public class HaIdGeneratorFactory implements IdGeneratorFactory
{
private final Map<IdType, HaIdGenerator> generators =
new EnumMap<IdType, HaIdGenerator>( IdType.class );
private final IdGeneratorFactory localFactory = new DefaultIdGeneratorFactory();
private final Master master;
public HaIdGeneratorFactory( Master master, ClusterMemberStateMachine stateHandler )
{
this.master = master;
stateHandler.addClusterMemberListener( new HaIdGeneratorFactoryClusterMemberListener() );
}
@Override
public IdGenerator open( FileSystemAbstraction fs, String fileName, int grabSize, IdType idType )
{
IdGenerator initialIdGenerator = localFactory.open( fs, fileName, grabSize, idType );
HaIdGenerator haIdGenerator = new HaIdGenerator( initialIdGenerator, fs, fileName, grabSize, idType );
generators.put( idType, haIdGenerator );
return haIdGenerator;
}
@Override
public void create( FileSystemAbstraction fs, String fileName, long highId )
{
localFactory.create( fs, fileName, highId );
}
@Override
public IdGenerator get( IdType idType )
{
return generators.get( idType );
}
private class HaIdGeneratorFactoryClusterMemberListener implements ClusterMemberListener
{
@Override
public void masterIsElected( ClusterMemberChangeEvent event )
{
if ( event.getNewState() == event.getOldState() )
{
return;
}
for ( HaIdGenerator generator : generators.values() )
{
generator.stateChanged( event );
}
}
@Override
public void masterIsAvailable( ClusterMemberChangeEvent event )
{
if ( event.getNewState() == event.getOldState() )
{
return;
}
for ( HaIdGenerator generator : generators.values() )
{
generator.stateChanged( event );
}
}
@Override
public void slaveIsAvailable( ClusterMemberChangeEvent event )
{
if ( event.getNewState() == event.getOldState() )
{
return;
}
for ( HaIdGenerator generator : generators.values() )
{
generator.stateChanged( event );
}
}
@Override
public void instanceStops( ClusterMemberChangeEvent event )
{
if ( event.getNewState() == event.getOldState() )
{
return;
}
for ( HaIdGenerator generator : generators.values() )
{
generator.stateChanged( event );
}
}
}
private static final long VALUE_REPRESENTING_NULL = -1;
private enum IdGeneratorState
{
TBD, SLAVE, MASTER;
}
private class HaIdGenerator implements IdGenerator
{
private IdGenerator delegate;
private final FileSystemAbstraction fs;
private final String fileName;
private final int grabSize;
private final IdType idType;
private volatile IdGeneratorState state = IdGeneratorState.TBD;
HaIdGenerator( IdGenerator initialDelegate, FileSystemAbstraction fs, String fileName, int grabSize,
IdType idType )
{
delegate = initialDelegate;
this.fs = fs;
this.fileName = fileName;
this.grabSize = grabSize;
this.idType = idType;
}
/*
* I know what you're thinking. You're thinking "shouldn't this be a ClusterInstanceListener instead". To tell
* you the truth, with all the stuff happening around, i'm not even sure myself. But given that getting the
* state transition here wrong will result in store corruption, the question you should be asking yourself is
* "do I feel lucky?". So, do you feel lucky, punk?
*
* State transitioning for IdGenerators cannot be done with the AbstractModeSwitcher as they are now without
* severely violating the expected contract. State has to be kept because the actions on transitions depend
* not only on the member state we are moving to but also the previous state of the IdGenerator - if it
* was a slave or a master IdGenerator. For that reason, it is implemented completely separately.
*/
public void stateChanged( ClusterMemberChangeEvent event )
{
// Assume blockade is up and no active threads are running here
if ( event.getNewState() == ClusterMemberState.PENDING
|| event.getNewState() == ClusterMemberState.MASTER )
{
return;
}
long highId = delegate.getHighId();
if ( event.getNewState() == ClusterMemberState.TO_MASTER )
{
if ( state == IdGeneratorState.SLAVE )
{
delegate.close();
if ( !fs.fileExists( fileName ) )
{
localFactory.create( fs, fileName, highId );
}
delegate = localFactory.open( fs, fileName, grabSize, idType );
}
// Otherwise we're master or TBD (initial state) which is the same
state = IdGeneratorState.MASTER;
}
else if ( event.getNewState() == ClusterMemberState.TO_SLAVE
|| event.getNewState() == ClusterMemberState.SLAVE )
{
if ( state == IdGeneratorState.SLAVE )
{
// I'm already slave, just forget about ids from the previous master
((SlaveIdGenerator) delegate).forgetIdAllocationFromMaster( master );
}
else
{
delegate.close();
delegate.delete();
delegate = new SlaveIdGenerator( idType, highId, master );
}
state = IdGeneratorState.SLAVE;
}
}
public String toString()
{
return delegate.toString();
}
public final boolean equals( Object other )
{
return delegate.equals( other );
}
public final int hashCode()
{
return delegate.hashCode();
}
public long nextId()
{
return delegate.nextId();
}
public IdRange nextIdBatch( int size )
{
return delegate.nextIdBatch( size );
}
public void setHighId( long id )
{
delegate.setHighId( id );
}
public long getHighId()
{
return delegate.getHighId();
}
public void freeId( long id )
{
delegate.freeId( id );
}
public void close()
{
delegate.close();
}
public long getNumberOfIdsInUse()
{
return delegate.getNumberOfIdsInUse();
}
public long getDefragCount()
{
return delegate.getDefragCount();
}
public void delete()
{
delegate.delete();
}
}
private static class SlaveIdGenerator implements IdGenerator
{
private volatile long highestIdInUse;
private volatile long defragCount;
private volatile IdRangeIterator idQueue = EMPTY_ID_RANGE_ITERATOR;
private volatile Master master;
private final IdType idType;
SlaveIdGenerator( IdType idType, long highId, Master master )
{
this.idType = idType;
this.highestIdInUse = highId;
this.master = master;
}
void forgetIdAllocationFromMaster( Master master )
{
if ( this.master.equals( master ) )
{
return;
}
this.idQueue = EMPTY_ID_RANGE_ITERATOR;
this.master = master;
}
@Override
public void close()
{
}
public void freeId( long id )
{
}
public long getHighId()
{
return highestIdInUse;
}
public long getNumberOfIdsInUse()
{
return highestIdInUse - defragCount;
}
public synchronized long nextId()
{
long nextId = nextLocalId();
if ( nextId == VALUE_REPRESENTING_NULL )
{
// If we dont have anymore grabbed ids from master, grab a bunch
Response<IdAllocation> response = master.allocateIds( idType );
IdAllocation allocation = response.response();
response.close();
nextId = storeLocally( allocation );
}
// TODO necessary check?
// else if ( !master.equals( stuff.getMaster() ) )
// throw new ComException( "Master changed" );
return nextId;
}
public IdRange nextIdBatch( int size )
{
throw new UnsupportedOperationException( "Should never be called" );
}
private long storeLocally( IdAllocation allocation )
{
this.highestIdInUse = allocation.getHighestIdInUse();
this.defragCount = allocation.getDefragCount();
this.idQueue = new IdRangeIterator( allocation.getIdRange() );
return idQueue.next();
}
private long nextLocalId()
{
return this.idQueue.next();
}
public void setHighId( long id )
{
// TODO Check for if it's lower than what I have?
this.highestIdInUse = id;
}
public long getDefragCount()
{
return this.defragCount;
}
@Override
public void delete()
{
}
}
private static class IdRangeIterator
{
private int position = 0;
private final long[] defrag;
private final long start;
private final int length;
IdRangeIterator( IdRange idRange )
{
this.defrag = idRange.getDefragIds();
this.start = idRange.getRangeStart();
this.length = idRange.getRangeLength();
}
long next()
{
try
{
if ( position < defrag.length )
{
return defrag[position];
}
else
{
int offset = position - defrag.length;
return (offset < length) ? (start + offset) : VALUE_REPRESENTING_NULL;
}
}
finally
{
++position;
}
}
}
private static IdRangeIterator EMPTY_ID_RANGE_ITERATOR =
new IdRangeIterator( new IdRange( new long[0], 0, 0 ) )
{
@Override
long next()
{
return VALUE_REPRESENTING_NULL;
}
;
};
}