/** * 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.heartbeat; import static org.neo4j.cluster.com.message.Message.internal; import static org.neo4j.cluster.com.message.Message.timeout; import static org.neo4j.cluster.com.message.Message.to; import java.net.URI; import org.neo4j.cluster.com.message.Message; import org.neo4j.cluster.com.message.MessageProcessor; import org.neo4j.cluster.protocol.atomicbroadcast.multipaxos.LearnerMessage; import org.neo4j.cluster.statemachine.State; /** * State machine that implements the {@link Heartbeat} API */ public enum HeartbeatState implements State<HeartbeatContext, HeartbeatMessage> { start { @Override public HeartbeatState handle( HeartbeatContext context, Message<HeartbeatMessage> message, MessageProcessor outgoing ) throws Throwable { switch ( message.getMessageType() ) { case addHeartbeatListener: { context.addHeartbeatListener( (HeartbeatListener) message.getPayload() ); break; } case removeHeartbeatListener: { context.removeHeartbeatListener( (HeartbeatListener) message.getPayload() ); break; } case join: { // Setup heartbeat timeouts context.startHeartbeatTimers( message ); return heartbeat; } } return this; } }, heartbeat { @Override public HeartbeatState handle( HeartbeatContext context, Message<HeartbeatMessage> message, MessageProcessor outgoing ) throws Throwable { switch ( message.getMessageType() ) { case i_am_alive: { HeartbeatMessage.IAmAliveState state = (HeartbeatMessage.IAmAliveState) message .getPayload(); if ( context.alive( state.getServer() ) ) { // Send suspicions messages to all non-failed servers for ( URI aliveServer : context.getAlive() ) { if ( !aliveServer.equals( context.getClusterContext().getMe() ) ) { outgoing.process( Message.to( HeartbeatMessage.suspicions, aliveServer, new HeartbeatMessage.SuspicionsState( context.getSuspicionsFor( context .getClusterContext() .getMe() ) ) ) ); } } } context.getClusterContext().timeouts.cancelTimeout( HeartbeatMessage.i_am_alive + "-" + state.getServer() ); context.getClusterContext().timeouts.setTimeout( HeartbeatMessage.i_am_alive + "-" + state.getServer(), timeout( HeartbeatMessage.timed_out, message, state .getServer() ) ); // Check if this server knows something that we don't if ( message.hasHeader( "last-learned" ) ) { long lastLearned = Long.parseLong( message.getHeader( "last-learned" ) ); if ( lastLearned > context.getLearnerContext().getLastKnownLearnedInstanceInCluster() ) { outgoing.process( internal( LearnerMessage.catchUp, lastLearned ) ); } } break; } case timed_out: { URI server = message.getPayload(); // Check if this node is no longer a part of the cluster if ( context.getClusterContext().getConfiguration().getMembers().contains( server ) ) { context.suspect( server ); context.getClusterContext().timeouts.setTimeout( HeartbeatMessage.i_am_alive + "-" + server, timeout( HeartbeatMessage.timed_out, message, server ) ); // Send suspicions messages to all non-failed servers for ( URI aliveServer : context.getAlive() ) { if ( !aliveServer.equals( context.getClusterContext().getMe() ) ) { outgoing.process( Message.to( HeartbeatMessage.suspicions, aliveServer, new HeartbeatMessage.SuspicionsState( context.getSuspicionsFor( context .getClusterContext() .getMe() ) ) ) ); } } } else { // If no longer part of cluster, then don't bother context.serverLeftCluster( server ); } break; } case sendHeartbeat: { URI to = message.getPayload(); // Check if this node is no longer a part of the cluster if ( context.getClusterContext().getConfiguration().getMembers().contains( to ) ) { // Send heartbeat message to given server outgoing.process( to( HeartbeatMessage.i_am_alive, to, new HeartbeatMessage.IAmAliveState( context.getClusterContext().getMe() ) ) .setHeader( "last-learned", context.getLearnerContext().getLastLearnedInstanceId() + "" ) ); // Set new timeout to send heartbeat to this host context.getClusterContext().timeouts.setTimeout( HeartbeatMessage.sendHeartbeat + "-" + to, timeout( HeartbeatMessage.sendHeartbeat, message, to ) ); } break; } case reset_send_heartbeat: { URI to = message.getPayload(); String timeoutName = HeartbeatMessage.sendHeartbeat + "-" + to; context.getClusterContext().timeouts.cancelTimeout( timeoutName ); context.getClusterContext().timeouts.setTimeout( timeoutName, Message.timeout( HeartbeatMessage.sendHeartbeat, message, to ) ); break; } case suspicions: { HeartbeatMessage.SuspicionsState suspicions = message.getPayload(); URI from = new URI( message.getHeader( Message.FROM ) ); context.suspicions( from, suspicions.getSuspicions() ); break; } case leave: { return start; } } return this; } } }