/**
* 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.cluster.protocol.atomicbroadcast.multipaxos;
import java.net.URI;
import java.util.List;
import org.neo4j.cluster.com.message.Message;
import org.neo4j.cluster.com.message.MessageProcessor;
import org.neo4j.cluster.statemachine.State;
/**
* State machine for Paxos Learner
*/
public enum LearnerState
implements State<MultiPaxosContext, LearnerMessage>
{
start
{
@Override
public LearnerState handle( MultiPaxosContext context,
Message<LearnerMessage> message,
MessageProcessor outgoing
)
throws Throwable
{
switch ( message.getMessageType() )
{
case join:
{
return learner;
}
}
return this;
}
},
learner
{
@Override
public LearnerState handle( MultiPaxosContext context,
Message<LearnerMessage> message,
MessageProcessor outgoing
)
throws Throwable
{
switch ( message.getMessageType() )
{
case learn:
{
LearnerMessage.LearnState learnState = message.getPayload();
InstanceId instanceId = new InstanceId( message );
PaxosInstance instance = context.getPaxosInstances().getPaxosInstance( instanceId );
// Skip if we already know about this
if ( instanceId.getId() <= context.learnerContext
.getLastDeliveredInstanceId() )
{
break;
}
context.learnerContext.learnedInstanceId( instanceId.getId() );
instance.closed( learnState.getValue() );
// If this is the next instance to be learned, then do so and check if we have anything
// pending to be learnt
if ( instanceId.getId() == context.learnerContext.getLastDeliveredInstanceId() + 1 )
{
instance.delivered();
outgoing.process( Message.internal( AtomicBroadcastMessage.broadcastResponse,
learnState.getValue() ) );
context.learnerContext.setLastDeliveredInstanceId( instanceId.getId() );
long checkInstanceId = instanceId.getId() + 1;
while ( (instance = context.getPaxosInstances().getPaxosInstance( new InstanceId(
checkInstanceId ) )).isState( PaxosInstance.State.closed ) )
{
instance.delivered();
context.learnerContext.setLastDeliveredInstanceId( checkInstanceId );
outgoing.process( Message.internal( AtomicBroadcastMessage.broadcastResponse,
instance.value_2 ) );
checkInstanceId++;
}
if ( checkInstanceId == context.learnerContext.getLastKnownLearnedInstanceInCluster()
+ 1 )
{
// No hole - all is ok
// Cancel potential timeout, if one is active
context.timeouts.cancelTimeout( "learn" );
}
else
{
// Found hole - we're waiting for this to be filled, i.e. timeout already set
context.clusterContext.getLogger().debug( "*** HOLE! WAITING FOR " +
instanceId );
}
}
else
{
outgoing.process( Message.internal( LearnerMessage.learnTimedout ) );
}
break;
}
case learnTimedout:
{
// Timed out waiting for learned values - send explicit request to someone
if ( !context.learnerContext.hasDeliveredAllKnownInstances() )
{
for ( long instanceId = context.learnerContext.getLastDeliveredInstanceId() + 1;
instanceId < context.learnerContext.getLastKnownLearnedInstanceInCluster();
instanceId++ )
{
InstanceId id = new InstanceId( instanceId );
PaxosInstance instance = context.getPaxosInstances().getPaxosInstance( id );
if ( !instance.isState( PaxosInstance.State.closed ) && !instance.isState(
PaxosInstance.State.delivered ) )
{
for ( URI node : context.heartbeatContext.getAlive() )
{
if ( !node.equals( context.clusterContext.getMe() ) )
{
outgoing.process( Message.to( LearnerMessage.learnRequest, node,
new LearnerMessage.LearnRequestState() ).setHeader(
InstanceId.INSTANCE,
id.toString() ) );
break;
}
}
}
}
// Set another timeout
context.timeouts.setTimeout( "learn", Message.timeout( LearnerMessage.learnTimedout,
message ) );
}
break;
}
case learnRequest:
{
// Someone wants to learn a value that we might have
LearnerMessage.LearnRequestState state = message.getPayload();
InstanceId instanceId = new InstanceId( message );
PaxosInstance instance = context.getPaxosInstances().getPaxosInstance( instanceId );
if ( instance.isState( PaxosInstance.State.closed ) || instance.isState( PaxosInstance
.State.delivered ) )
{
outgoing.process( Message.respond( LearnerMessage.learn, message,
new LearnerMessage.LearnState( instance.value_2 ) ).setHeader( InstanceId
.INSTANCE,
instanceId.toString() ) );
}
else
{
context.clusterContext.getLogger().debug( "Did not have learned value for" +
" " +
"instance " + instanceId );
outgoing.process( message.copyHeadersTo( Message.respond( LearnerMessage.learnFailed,
message,
new LearnerMessage.LearnFailedState() ), InstanceId.INSTANCE ) );
}
break;
}
case learnFailed:
{
LearnerMessage.LearnFailedState state = message.getPayload();
InstanceId instanceId = new InstanceId( message );
PaxosInstance instance = context.getPaxosInstances().getPaxosInstance( instanceId );
if ( !(instance.isState( PaxosInstance.State.closed ) || instance.isState( PaxosInstance
.State.delivered )) )
{
List<URI> nodes = context.clusterContext.getConfiguration().getMembers();
URI learnDeniedNode = new URI( message.getHeader( Message.FROM ) );
int nextPotentialLearnerIndex = (nodes.indexOf( learnDeniedNode ) + 1) % nodes.size();
URI learnerNode = context.clusterContext.getConfiguration().getMembers().get(
nextPotentialLearnerIndex );
outgoing.process( message.copyHeadersTo( Message.to( LearnerMessage.learnRequest,
learnerNode,
new LearnerMessage.LearnRequestState() ), InstanceId.INSTANCE ) );
}
break;
}
case catchUp:
{
Long catchUpTo = message.getPayload();
if ( context.learnerContext.getLastKnownLearnedInstanceInCluster() < catchUpTo )
{
context.proposerContext.lastInstanceId = catchUpTo + 1;
// Try to get up to date
for ( long instanceId = context.learnerContext.getLastLearnedInstanceId() + 1;
instanceId <= catchUpTo; instanceId++ )
{
InstanceId id = new InstanceId( instanceId );
PaxosInstance instance = context.getPaxosInstances().getPaxosInstance( id );
if ( !instance.isState( PaxosInstance.State.closed ) && !instance.isState(
PaxosInstance.State.delivered ) )
{
for ( URI node : context.heartbeatContext.getAlive() )
{
if ( !node.equals( context.clusterContext.getMe() ) )
{
outgoing.process( Message.to( LearnerMessage.learnRequest, node,
new LearnerMessage.LearnRequestState() ).setHeader(
InstanceId.INSTANCE,
id.toString() ) );
break;
}
}
}
}
context.learnerContext.setLastKnownLearnedInstanceInCluster( catchUpTo );
}
break;
}
case leave:
{
context.learnerContext.leave();
return start;
}
}
return this;
}
}
}