All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.voltcore.agreement.AgreementSeeker Maven / Gradle / Ivy

/* This file is part of VoltDB.
 * Copyright (C) 2008-2020 VoltDB Inc.
 *
 * This program 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 VoltDB.  If not, see .
 */

package org.voltcore.agreement;

import static com.google_voltpatches.common.base.Predicates.in;
import static com.google_voltpatches.common.base.Predicates.not;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.voltcore.messaging.FaultMessage;
import org.voltcore.messaging.SiteFailureForwardMessage;
import org.voltcore.messaging.SiteFailureMessage;
import org.voltcore.utils.CoreUtils;
import org.voltcore.utils.Pair;

import com.google_voltpatches.common.base.Predicate;
import com.google_voltpatches.common.collect.ImmutableSet;
import com.google_voltpatches.common.collect.ImmutableSortedSet;
import com.google_voltpatches.common.collect.Maps;
import com.google_voltpatches.common.collect.Multimap;
import com.google_voltpatches.common.collect.Multimaps;
import com.google_voltpatches.common.collect.Sets;
import com.google_voltpatches.common.collect.TreeMultimap;

public class AgreementSeeker {

    protected final ArbitrationStrategy m_strategy;
    /** hsid of the site holding this instance */

    protected final long m_selfHsid;
    /** mesh hsids before agreement is sought */
    protected Set m_hsids;

    /** the set of hsids which this site can see */
    protected Set m_survivors;

    /**
     * graph where each key is a site, and their corresponding values are sites
     * that have reported it
     */
    protected final TreeMultimap m_reported = TreeMultimap.create();
    /**
     * graph were each key denotes a site, and their corresponding values are sites
     * that see it dead
     */
    protected final TreeMultimap m_dead = TreeMultimap.create();
    /**
     * graph were each key denotes a site, and their corresponding values are sites
     * that see it alive
     */
    protected final TreeMultimap m_alive = TreeMultimap.create();

    public AgreementSeeker(final ArbitrationStrategy strategy, long selfHsid) {
        m_strategy = strategy;
        m_selfHsid = selfHsid;

        m_hsids = ImmutableSet.of();
        m_survivors = ImmutableSet.of();
    }

    /**
     * Start accumulate site links graphing information
     *
     * @param hsids pre-failure mesh hsids
     * @param inTrouble a map where each key is a failed site, and its value is
     *   a boolean that indicates whether or not the failure was witnessed directly
     *   or reported by some other site
     */
    public void startSeekingFor(
            final Set hsids, final Map inTrouble) {

        // if the mesh hsids change we need to reset
        if (!m_hsids.equals(hsids)) {
            if (!m_hsids.isEmpty()) clear();
            m_hsids = ImmutableSortedSet.copyOf(hsids);
        }
        // determine the survivors
        m_survivors = m_strategy.accept(survivorPicker, Pair.of(m_hsids, inTrouble));
        // start accumulating link failure graphing info
        add(m_selfHsid, inTrouble);
    }

    public void clear() {
        m_reported.clear();
        m_dead.clear();
        m_alive.clear();

        m_hsids = ImmutableSet.of();
        m_survivors = ImmutableSet.of();
    }

    /**
     * Convenience method that remove all instances of the given values
     * from the given map
     * @param mm a multimap
     * @param values a set of values that need to be removed
     */
    static protected void removeValues(TreeMultimap mm, Set values) {
        Iterator> itr = mm.entries().iterator();
        while (itr.hasNext()) {
            Map.Entry e = itr.next();
            if (values.contains(e.getValue())) {
                itr.remove();
            }
        }
    }

    /**
     * a visitor that accepts a pair consisting of pre-failure mesh hsids,
     * and its failed sites map, and returns a set of survivors
     */
    static protected final ArbitrationStrategy.Visitor, Pair, Map>> survivorPicker =
            new ArbitrationStrategy.Visitor, Pair,Map>>() {

        @Override
        public Set visitMatchingCardinality(Pair, Map> p) {
            Set dead =
                    Maps.filterEntries(p.getSecond(), amongDeadHsids(p.getFirst())).keySet();
            return ImmutableSortedSet.copyOf(Sets.difference(p.getFirst(), dead));
        }

        @Override
        public Set visitNoQuarter(Pair, Map> p) {
            Set reported = Maps.filterKeys(p.getSecond(), in(p.getFirst())).keySet();
            return ImmutableSortedSet.copyOf(Sets.difference(p.getFirst(), reported));
        }
    };

    /**
     * returns a map entry predicate that tests whether or not the given
     * map entry describes a dead site
     * @param hsids pre-failure mesh hsids
     * @return
     */
    public static Predicate> amongDeadHsids(final Set hsids) {
        return new Predicate>() {
            @Override
            public boolean apply(Entry e) {
                return hsids.contains(e.getKey()) && e.getValue();
            }
        };
    }

    /**
     * a site predicate that tests whether or not a site is
     * among the set of survivors
     */
    public final Predicate amongSurvivors = new Predicate() {
        @Override
        public boolean apply(Long site) {
            return m_survivors.contains(site);
        }
    };

    /**
     * returns the current set of survivors
     * @return the current set of survivors
     */
    public Set getSurvivors() {
        return m_survivors;
    }

    /**
     * Convenience method that remove all instances of the given value
     * from the given map
     * @param mm a multimap
     * @param value a value that needs to be removed
     */
    private void removeValue(TreeMultimap mm, long value) {
        Iterator> itr = mm.entries().iterator();
        while (itr.hasNext()) {
            Map.Entry e = itr.next();
            if (e.getValue().equals(value)) {
                itr.remove();
            }
        }
    }

    protected String dumpGraph(Multimap mm, StringBuilder sb) {
        sb.append("{ ");
        int count = 0;
        for (long h: mm.keySet()) {
            if (count++ > 0) sb.append(", ");
            sb.append(CoreUtils.hsIdToString(h)).append(": [");
            sb.append(CoreUtils.hsIdCollectionToString(mm.get(h)));
            sb.append("]");
        }
        sb.append("}");
        return sb.toString();
    }

    public String dumpAlive() {
        StringBuilder sb = new StringBuilder();
        sb.append("Alive: ");
        dumpGraph(m_alive, sb);
        return sb.toString();
    }

    public String dumpDead() {
        StringBuilder sb = new StringBuilder();
        sb.append("Dead: ");
        dumpGraph(m_dead, sb);
        return sb.toString();
    }

    public String dumpReported() {
        StringBuilder sb = new StringBuilder();
        sb.append("Reported: ");
        dumpGraph(m_reported, sb);
        return sb.toString();
    }

    public String dumpSurvivors() {
        StringBuilder sb = new StringBuilder();
        sb.append("Survivor: ");
        sb.append("{ ");
        int count = 0;
        for (Long hsId : m_survivors) {
            if (count++ > 0) sb.append(", ");
            sb.append(CoreUtils.hsIdToString(hsId));
        }
        sb.append(" }");
        return sb.toString();
    }

    /**
     *  Adds alive and dead graph information
     *  @param reportingHsid site reporting failures
     *  @param failures seen by the reporting site
     */
    void add(long reportingHsid, final Map failed) {
        // skip if the reporting site did not belong to the pre
        // failure mesh
        if (!m_hsids.contains(reportingHsid)) return;

        // ship if the reporting site is reporting itself dead
        Boolean harakiri = failed.get(reportingHsid);
        if (harakiri != null && harakiri.booleanValue()) return;

        Set dead = Sets.newHashSet();

        for (Map.Entry e: failed.entrySet()) {
            // skip if the failed site did not belong to the
            // pre failure mesh
            if (!m_hsids.contains(e.getKey())) continue;

            m_reported.put(e.getKey(), reportingHsid);
            // if the failure is witnessed add it to the dead graph
            if (e.getValue()) {
                m_dead.put(e.getKey(), reportingHsid);
                dead.add(e.getKey());
            }
        }
        // once you are witnessed dead you cannot become undead,
        // but it is not the case for alive nodes, as they can
        // die. So remove all what the reporting site thought
        // was alive before this invocation
        removeValue(m_alive, reportingHsid);

        for (Long alive: Sets.difference(m_hsids, dead)) {
            m_alive.put(alive, reportingHsid);
        }
    }

    /**
     *  Adds alive and dead graph information from a reporting
     *  site survivor set
     *  @param reportingHsid the reporting site
     *  @param sfm a {@link SiteFailureMessage} containing that
     *      site's survivor set
     */
    public void add(long reportingHsid, SiteFailureMessage sfm) {
        // skip if the reporting site did not belong to the pre
        // failure mesh, or the reporting site is reporting itself
        // dead, or none of the sites in the safe transaction map
        // are among the known hsids
        if (   !m_hsids.contains(reportingHsid)
            || !sfm.m_survivors.contains(reportingHsid)) return;

        Set survivors = sfm.m_survivors;
        if (Sets.filter(sfm.getObservedFailedSites(), in(m_hsids)).isEmpty()) {
            survivors = m_hsids;
        }

        // dead = pre failure mesh - survivors
        Set dead = Sets.difference(m_hsids, survivors);

        removeValue(m_dead, reportingHsid);

        // add dead graph nodes
        for (long w: dead) {
            if (!m_hsids.contains(w)) continue;
            m_dead.put(w,reportingHsid);
        }

        // Remove all what the reporting site thought
        // was alive before this invocation
        removeValue(m_alive, reportingHsid);

        // add alive graph nodes
        for (long s: survivors) {
            if (!m_hsids.contains(s)) continue;
            m_alive.put(s, reportingHsid);
        }

        for (long s: sfm.getFailedSites()) {
            if (!m_hsids.contains(s)) continue;
            m_reported.put(s, reportingHsid);
        }
    }

    /**
     *  Adds alive and dead graph information from a reporting
     *  site survivor set
     *  @param sfm a {@link SiteFailureMessage} containing that
     *      site's survivor set
     */
    public void add(SiteFailureMessage sfm) {
        add(sfm.m_sourceHSId, sfm);
    }

    /**
     *  Adds alive and dead graph information from a reporting
     *  site survivor set
     *  @param reportingHsid the reporting site
     *  @param sfm a {@link SiteFailureForwardMessage} containing that
     *      site's survivor set
     */
    public void add(SiteFailureForwardMessage fsfm) {
        add(fsfm.m_reportingHSId,fsfm);
    }

    /**
     * Does the given graph scenario meet the criteria of
     * having reached an agreement?
     * @param sc a graph scenario
     */
    protected Boolean haveAgreement(Scenario sc) {
        return m_strategy.accept(agreementSeeker, sc);
    }

    /**
     * a visitor that accepts a graph scenario and returns whether or not
     * the graph scenario has reached an agreement
     */
    protected final ArbitrationStrategy.Visitor agreementSeeker =
            new ArbitrationStrategy.Visitor() {

        @Override
        public Boolean visitNoQuarter(Scenario sc) {
            Iterator itr = sc.reported.keySet().iterator();
            boolean agree = true;
            while (agree && itr.hasNext()) {
                agree = sc.reported.get(itr.next()).containsAll(sc.survivors);
            }
            return agree;
        }

        /**
         * a quorum is comprised of sites that the given scenario
         * has left alive. This returns true if each dead node in
         * the scenario is seen dead by all in the quorum
         */
        @Override
        public Boolean visitMatchingCardinality(Scenario sc) {
            boolean agree = true;
            Set quorum = Sets.intersection(sc.alive.keySet(),sc.survivors);
            for (Long dead: sc.dead.keySet()) {
                agree = agree && quorum.equals(sc.dead.get(dead));
            }
            return agree;
        }
    };

    public boolean needForward(int [] countdown) {
        return --countdown[0] > 0 && m_strategy.accept(forwardDemander, (Void)null);
    }

    /**
     * Is anyone in the mesh alive and connected to sites I consider dead?
     */
    public boolean needForward() {
        return m_strategy.accept(forwardDemander, (Void)null);
    }

    /**
     * a visitor that tests whether or not there is a connected path
     * between myself and any site I consider dead
     */
    protected ArbitrationStrategy.Visitor forwardDemander =
            new ArbitrationStrategy.Visitor() {

        @Override
        public Boolean visitNoQuarter(Void nads) {
            return false;
        }

        /**
         * Tests whether or not there is a connected path between myself,
         * and any site I consider dead
         * @param nada
         * @return true if there are peer sites that can tell me about
         *    sites that I consider dead
         */
        @Override
        public Boolean visitMatchingCardinality(Void nada) {
            if (m_survivors.size() == 1) return false;

            Set unreachable = Sets.filter(m_hsids, not(in(m_survivors)));
            Set butAlive = Sets.intersection(m_alive.keySet(), unreachable);

            return !butAlive.isEmpty()
                && seenByInterconnectedPeers(butAlive, Sets.newTreeSet(m_survivors))
                && !m_dead.get(m_selfHsid).containsAll(butAlive);
        }
    };

    /**
     * Walk the alive graph to see if there is a connected path between origins,
     * and destinations
     * @param destinations set of sites that we are looking a path to
     * @param origins set of sites that we are looking a path from
     * @return true origins have path to destinations
     */
    protected boolean seenByInterconnectedPeers( Set destinations, Set origins) {
        Set seers = Multimaps.filterValues(m_alive, in(origins)).keySet();
        int before = origins.size();

        origins.addAll(seers);
        if (origins.containsAll(destinations)) {
            return true;
        } else if (origins.size() == before) {
            return false;
        }
        return seenByInterconnectedPeers(destinations, origins);
    }

    /**
     * Determine the set of nodes to kill to accomplish a fully connected
     * mesh with the remaining sites
     * @return a set of nodes to kill
     */
    public Set nextKill() {
        return m_strategy.accept(killPicker, m_selfHsid);
    }

    protected ArbitrationStrategy.Visitor, Long> killPicker =
            new ArbitrationStrategy.Visitor, Long>() {

                @Override
                public Set visitNoQuarter(Long self) {
                    return ImmutableSet.copyOf(m_reported.keySet());
                }

                // if a is picked then you need to remove all alive by (a)
                // and add them to dead
                // alive(a) is c,d,e,f now dead(a) += alive(a)
                // you also have to remove all (a)'s from deadBy values,
                // as (a) can no longer see anyone dead or alive
                /**
                 * This strategy picks first the sites that are considered
                 * dead by most of the remaining sites, and for ties breakers
                 * it picks the sites with highest hsids
                 * @param self the invoking site hsid
                 * @return a set of nodes to kill
                 */
                @Override
                public Set visitMatchingCardinality(Long self) {
                    Set picks = Sets.newHashSet();
                    Scenario sc = new Scenario();
                    while (!haveAgreement(sc)) {
                        Long pick = null;
                        for (Long s: sc.dead.keySet()) {

                            // cannot pick self or the ones already picked
                            if (s.equals(self) || picks.contains(s)) continue;

                            Set deadBy = sc.dead.get(s);
                            if (deadBy.isEmpty()) continue;

                            if (pick != null) {
                                int cmp = deadBy.size()
                                        - sc.dead.get(pick).size();
                                if (cmp > 0 || (cmp == 0 && s.compareTo(pick) > 0)) {
                                    pick = s;
                                }
                            } else {
                                pick = s;
                            }
                        }
                        if (pick == null) {
                            /*
                             * You only get here if and ONLY if yourself are the ONLY viable kill
                             */
                            return ImmutableSet.of();/*ImmutableSet.copyOf(
                                    Sets.filter(m_hsids, not(equalTo(m_selfHsid))));*/
                        }

                        // pick can no longer see anyone dead or alive
                        removeValue(sc.dead,pick);
                        removeValue(sc.alive, pick);

                        sc.dead.putAll(pick, sc.alive.removeAll(pick));

                        picks.add(pick);
                    }
                    return ImmutableSet.copyOf(sc.dead.keySet());
                }
            };


    /**
     * Is the given hsid considered dead by anyone in my survivor set?
     * @param hsid a site hsid
     * @return a subset of my survivor set that considers the given site dead
     */
    public Set forWhomSiteIsDead(long hsid) {
        ImmutableSet.Builder isb = ImmutableSet.builder();
        Set deadBy = m_dead.get(hsid);
        if (   !deadBy.isEmpty()
            && m_survivors.contains(hsid)
            && m_strategy == ArbitrationStrategy.MATCHING_CARDINALITY) {
            isb.addAll(Sets.filter(deadBy, amongSurvivors));
        }
        return isb.build();
    }

    public long bestKillCandidateAmong(Set candidates) {
        long pick = Long.MIN_VALUE;
        int dings = Integer.MIN_VALUE;
        for (long candidate: candidates) {
            int siteDings = m_dead.get(candidate).size();
            if (siteDings > dings) {
                dings = siteDings;
                pick = candidate;
            } else if (dings == siteDings && candidate > pick) {
                pick = candidate;
            }
        }
        return pick;
    }

    public boolean alreadyKnow(FaultMessage fm) {
        for (Long survivor : fm.survivors) {
            // Do we already know all the survivors?
            if (!m_alive.get(survivor).contains(fm.reportingSite)) {
                return false;
            }
        }
        // Do we already know the dead?
        if (!m_dead.get(fm.failedSite).contains(fm.reportingSite)) {
            return false;
        }
        // Nothing new!! No need to report to fault resolver.
        return true;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("AgreementSeeker { hsId: ").append(CoreUtils.hsIdToString(m_selfHsid));
        sb.append(", survivors: [").append(CoreUtils.hsIdCollectionToString(m_survivors));
        sb.append("], alive: ");
        dumpGraph(m_alive, sb);
        sb.append(", dead: ");
        dumpGraph(m_dead, sb);
        sb.append("}");
        return sb.toString();
    }

    protected class Scenario {

        protected TreeMultimap reported;
        protected TreeMultimap dead;
        protected TreeMultimap alive;
        protected Set survivors;

        protected Scenario() {
            reported = TreeMultimap.create(m_reported);
            dead = TreeMultimap.create(m_dead);
            survivors = Sets.newTreeSet(m_survivors);
            alive = TreeMultimap.create(m_alive);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy