/** * 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.timeout; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import org.neo4j.cluster.com.message.Message; import org.neo4j.cluster.protocol.atomicbroadcast.multipaxos.LearnerContext; import org.neo4j.cluster.protocol.cluster.ClusterContext; import org.neo4j.helpers.Listeners; import org.neo4j.helpers.Specification; import org.neo4j.helpers.collection.Iterables; /** * Context used by the {@link HeartbeatState} state machine. */ public class HeartbeatContext { private ClusterContext clusterContext; private LearnerContext learnerContext; private Executor executor; List<URI> failed = new ArrayList<URI>(); Map<URI, Set<URI>> nodeSuspicions = new HashMap<URI, Set<URI>>(); Iterable<HeartbeatListener> listeners = Listeners.newListeners(); public HeartbeatContext( ClusterContext clusterContext, LearnerContext learnerContext, Executor executor ) { this.clusterContext = clusterContext; this.learnerContext = learnerContext; this.executor = executor; } public void started() { failed.clear(); } public boolean alive( final URI node ) { Set<URI> serverSuspicions = getSuspicionsFor( clusterContext.getMe() ); boolean suspected = serverSuspicions.remove( node ); if ( !isFailed( node ) && failed.remove( node ) ) { Listeners.notifyListeners( listeners, new Listeners.Notification<HeartbeatListener>() { @Override public void notify( HeartbeatListener listener ) { listener.alive( node ); } } ); } return suspected; } public void suspect( final URI node ) { Set<URI> serverSuspicions = getSuspicionsFor( clusterContext.getMe() ); serverSuspicions.add( node ); if ( isFailed( node ) && !failed.contains( node ) ) { failed.add( node ); Listeners.notifyListeners( listeners, executor, new Listeners.Notification<HeartbeatListener>() { @Override public void notify( HeartbeatListener listener ) { listener.failed( node ); } } ); } } public void suspicions( URI from, Set<URI> suspicions ) { Set<URI> serverSuspicions = getSuspicionsFor( from ); serverSuspicions.clear(); serverSuspicions.addAll( suspicions ); for ( final URI node : suspicions ) { if ( isFailed( node ) && !failed.contains( node ) ) { failed.add( node ); Listeners.notifyListeners( listeners, executor, new Listeners.Notification<HeartbeatListener>() { @Override public void notify( HeartbeatListener listener ) { listener.failed( node ); } } ); } } } public List<URI> getFailed() { return failed; } public Iterable<URI> getAlive() { return Iterables.filter( new Specification<URI>() { @Override public boolean satisfiedBy( URI item ) { return !isFailed( item ); } }, clusterContext.getConfiguration().getMembers() ); } public ClusterContext getClusterContext() { return clusterContext; } public LearnerContext getLearnerContext() { return learnerContext; } public void addHeartbeatListener( HeartbeatListener listener ) { listeners = Listeners.addListener( listener, listeners ); } public void removeHeartbeatListener( HeartbeatListener listener ) { listeners = Listeners.removeListener( listener, listeners ); } public void startHeartbeatTimers( Message<?> message ) { // Start timers for sending and receiving heartbeats for ( URI server : clusterContext.getConfiguration().getMembers() ) { if ( !clusterContext.isMe( server ) ) { clusterContext.timeouts.setTimeout( HeartbeatMessage.i_am_alive + "-" + server, timeout( HeartbeatMessage.timed_out, message, server ) ); clusterContext.timeouts.setTimeout( HeartbeatMessage.sendHeartbeat + "-" + server, timeout( HeartbeatMessage.sendHeartbeat, message, server ) ); } } } public void serverLeftCluster( URI node ) { failed.remove( node ); for ( Set<URI> uris : nodeSuspicions.values() ) { uris.remove( node ); } } public boolean isFailed( URI node ) { List<URI> suspicions = getSuspicionsOf( node ); return suspicions.size() > (clusterContext.getConfiguration().getMembers().size() - failed.size()) / 2; } public List<URI> getSuspicionsOf( URI uri ) { List<URI> suspicions = new ArrayList<URI>(); for ( Map.Entry<URI, Set<URI>> uriSetEntry : nodeSuspicions.entrySet() ) { if ( uriSetEntry.getValue().contains( uri ) ) { suspicions.add( uriSetEntry.getKey() ); } } return suspicions; } public Set<URI> getSuspicionsFor( URI uri ) { Set<URI> serverSuspicions = nodeSuspicions.get( uri ); if ( serverSuspicions == null ) { serverSuspicions = new HashSet<URI>(); nodeSuspicions.put( uri, serverSuspicions ); } return serverSuspicions; } }