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

org.jgroups.util.Buffer Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
package org.jgroups.util;

import org.jgroups.Message;
import org.jgroups.annotations.GuardedBy;

import java.io.Closeable;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Base class for message buffers. Used on the senders (keeping track of sent messages and purging
 * delivered messages) and receivers (delivering messages in the correct order and asking senders for missing messages).
 * @author Bela Ban
 * @since  5.4
 */
public abstract class Buffer implements Iterable, Closeable {
    protected final Lock          lock=new ReentrantLock();
    protected final AtomicInteger adders=new AtomicInteger(0);
    protected long                offset;

    /** sender: highest seqno seen by everyone, receiver: highest delivered seqno */
    protected long                low;

    /** The highest delivered (=removed) seqno */
    protected long                hd;

    /** The highest received/sent seqno. Moved forward by add(). The next message to be added is high+1.
     low <= hd <= high always holds */
    protected long                high;

    /** The number of non-null elements */
    protected int                 size;


    public Lock          lock()               {return lock;}
    public AtomicInteger getAdders()          {return adders;}
    public long          offset()             {return offset;}
    public long          low()                {return low;}
    public long          highestDelivered()   {return hd;}
    public long          hd()                 {return hd;}
    public long          high()               {return high;}
    public int           size()               {return size;}
    public boolean       isEmpty()            {return size <= 0;}


    /** Returns the current capacity in the buffer. This value is fixed in a fixed-size buffer
     * (e.g. {@link FixedBuffer}), but can change in a dynamic buffer ({@link DynamicBuffer}) */
    public abstract int  capacity();
    public void          resetStats() {}
    public void          open(boolean b) {}
    @Override
    public void          close() {open(false);}

    /**
     * Adds an element if the element at the given index is null. Returns true if no element existed at the given index,
     * else returns false and doesn't set the element.
     * @param seqno
     * @param element
     * @return True if the element at the computed index was null, else false
     */
    // used: single message received
    public boolean add(long seqno, T element) {
        return add(seqno, element, null, Options.DEFAULT(), false);
    }

    /**
     * Adds an element if the element at the given index is null. Returns true if no element existed at the given index,
     * else returns false and doesn't set the element.
     *
     * @param seqno         The seqno of the element
     * @param element       The element to be added
     * @param remove_filter A filter used to remove all consecutive messages passing the filter (and non-null). This
     *                      doesn't necessarily null a removed message, but may simply advance an index
     *                      (e.g. highest delivered). Ignored if null.
     * @param options       The options passed to the call
     * @param dont_block    If true, don't block when no space is available, but instead drop the element. This
     *                      parameter is set by calling Message.isFlagSet(DONT_BLOCK)
     * @return True if the element at the computed index was null, else false
     */
    // used: send message
    public abstract boolean add(long seqno, T element, Predicate remove_filter, Options options, boolean dont_block);

    // used: MessageBatch received
    public abstract boolean add(MessageBatch batch, Function seqno_getter, boolean remove_from_batch, T const_value);

    /**
     * Adds elements from the list
     * @param list The list of tuples of seqnos and elements. If remove_added_elements is true, if elements could
     *             not be added (e.g. because they were already present or the seqno was < HD), those
     *             elements will be removed from list
     * @param remove_added_elements If true, elements that could not be added to the table are removed from list
     * @param const_value If non-null, this value should be used rather than the values of the list tuples
     * @return True if at least 1 element was added successfully, false otherwise.
     */
    // used: MessageBatch received by UNICAST3/4
    public abstract boolean add(final List> list, boolean remove_added_elements, T const_value);

    // used: retransmision etc
    public abstract T get(long seqno);

    public abstract T _get(long seqno);

    public T remove() {
        return remove(true);
    }

    public abstract T remove(boolean nullify);

    public List removeMany(boolean nullify, int max_results) {
        return removeMany(nullify, max_results, null);
    }

    public abstract List removeMany(boolean nullify, int max_results, Predicate filter);

    // used in removeAndDeliver()
    public abstract  R removeMany(boolean nullify, int max_results, Predicate filter,
                                     Supplier result_creator, BiConsumer accumulator);

    /** Removes all elements <= seqno from the buffer. Does this by nulling all elements < index(seqno) */
    public int purge(long seqno) {
        return purge(seqno, false);
    }

    /**
     * Purges (nulls) all elements <= seqno.
     * @param seqno All elements <= seqno will be purged.
     * @param force If false, seqno is max(seqno,hd), else max(seqno,high). In the latter case (seqno > hd), we might
     *              purge elements that have not yet been received
     * @return 0. The number of purged elements
     */
    public abstract int purge(long seqno, boolean force);

    public void forEach(Visitor visitor, boolean nullify) {
        forEach(highestDelivered()+1, high(), visitor, nullify);
    }

    public abstract void forEach(long from, long to, Visitor visitor, boolean nullify);

    public abstract Iterator iterator(long from, long to);

    // used
    public abstract Stream stream();

    public abstract Stream stream(long from, long to);

    /** Iterates from hd to high and adds up non-null values. Caller must hold the lock. */
    @GuardedBy("lock")
    public int computeSize() {
        return (int)stream().filter(Objects::nonNull).count();
    }

    /** Returns the number of null elements in the range [hd+1 .. hr-1] excluding hd and hr */
    public int numMissing() {
        lock.lock();
        try {
            return (int)(high - hd - size);
        }
        finally {
            lock.unlock();
        }
    }

    /**
     * Returns a list of missing (= null) elements
     * @return A SeqnoList of missing messages, or null if no messages are missing
     */
    public SeqnoList getMissing() {
        return getMissing(0);
    }

    /**
     * Returns a list of missing messages
     * @param max_msgs If > 0, the max number of missing messages to be returned (oldest first), else no limit
     * @return A SeqnoList of missing messages, or null if no messages are missing
     */
    public SeqnoList getMissing(int max_msgs) {
        lock.lock();
        try {
            if(isEmpty())
                return null;
            long start_seqno=getHighestDeliverable() + 1;
            int capacity=(int)(high - start_seqno);
            int max_size=max_msgs > 0? Math.min(max_msgs, capacity) : capacity;
            if(max_size <= 0)
                return null;
            Missing missing=new Missing(start_seqno, max_size);
            long to=max_size > 0? Math.min(start_seqno + max_size - 1, high - 1) : high - 1;
            forEach(start_seqno, to, missing, false);
            return missing.getMissingElements();
        }
        finally {
            lock.unlock();
        }
    }

    /** Returns the number of messages that can be delivered */
    public int getNumDeliverable() {
        NumDeliverable visitor=new NumDeliverable();
        lock.lock();
        try {
            forEach(visitor, false);
            return visitor.getResult();
        }
        finally {
            lock.unlock();
        }
    }

    /** Returns the highest deliverable (= removable) seqno. This may be higher than {@link #highestDelivered()},
     * e.g. if elements have been added but not yet removed */
    // used in retransmissions
    public long getHighestDeliverable() {
        HighestDeliverable visitor=new HighestDeliverable();
        lock.lock();
        try {
            forEach(visitor, false);
            long retval=visitor.getResult();
            return retval == -1? highestDelivered() : retval;
        }
        finally {
            lock.unlock();
        }
    }

    public long[] getDigest() {
        lock.lock();
        try {
            return new long[]{hd, high};
        }
        finally {
            lock.unlock();
        }
    }

    /** Only used internally on a state transfer (setting the digest). Don't use this in application code! */
    public Buffer highestDelivered(long seqno) {
        lock.lock();
        try {
            hd=seqno;
            return this;
        }
        finally {
            lock.unlock();
        }
    }

    /** Dumps all non-null messages (used for testing) */
    public String dump() {
        lock.lock();
        try {
            return stream(low(), high()).filter(Objects::nonNull).map(Object::toString)
              .collect(Collectors.joining(", "));
        }
        finally {
            lock.unlock();
        }
    }

    @Override
    public String toString() {
        return String.format("[%,d | %,d | %,d] (size: %,d, missing: %,d)", low, hd, high, size, numMissing());
    }

    public static class Options {
        protected boolean              block;
        protected boolean              remove_from_batch;
        protected Predicate   remove_filter;
        protected static final Options DEFAULT=new Options();
        public static Options     DEFAULT() {return DEFAULT;}
        public boolean            block()                            {return block;}
        public Options            block(boolean block)               {this.block=block;return this;}
        public boolean            removeFromBatch()                  {return remove_from_batch;}
        public Options            removeFromBatch(boolean r)         {this.remove_from_batch=r; return this;}
        public Predicate remove_filter()                    {return remove_filter;}
        public Options            removeFilter(Predicate f) {this.remove_filter=f; return this;}

        @Override
        public String toString() {
            return String.format("block=%b remove_from_batch=%b", block, remove_from_batch);
        }
    }

    public interface Visitor {
        /**
         * Iteration over the table, used by {@link DynamicBuffer#forEach(long, long, Buffer.Visitor, boolean)}.
         *
         * @param seqno   The current seqno
         * @param element The element at matrix[row][column]
         * @return True if we should continue the iteration, false if we should break out of the iteration
         */
        boolean visit(long seqno, T element);
    }

    protected class Remover implements Visitor {
        protected final int          max_results;
        protected int                num_results;
        protected final Predicate filter;
        protected R                  result;
        protected Supplier        result_creator;
        protected BiConsumer    result_accumulator;

        public Remover(int max_results, Predicate filter, Supplier creator, BiConsumer accumulator) {
            this.max_results=max_results;
            this.filter=filter;
            this.result_creator=creator;
            this.result_accumulator=accumulator;
        }

        public R getResult() {return result;}

        @GuardedBy("lock")
        public boolean visit(long seqno, T element) {
            if(element == null)
                return false;
            if(filter == null || filter.test(element)) {
                if(result == null)
                    result=result_creator.get();
                result_accumulator.accept(result, element);
                num_results++;
            }
            size=Math.max(size-1, 0); // cannot be < 0 (well that would be a bug, but let's have this 2nd line of defense !)
            if(seqno - hd > 0)
                hd=seqno;
            return max_results == 0 || num_results < max_results;
        }
    }


    protected class NumDeliverable implements Visitor {
        protected int num_deliverable=0;

        public int getResult() {return num_deliverable;}

        public boolean visit(long seqno, T element) {
            if(element == null)
                return false;
            num_deliverable++;
            return true;
        }
    }

    protected class HighestDeliverable implements Visitor {
        protected long highest_deliverable=-1;

        public long getResult() {return highest_deliverable;}

        public boolean visit(long seqno, T element) {
            if(element == null)
                return false;
            highest_deliverable=seqno;
            return true;
        }
    }

    protected class Missing implements Visitor {
        protected final SeqnoList missing_elements;
        protected final int       max_num_msgs;
        protected int             num_msgs;

        protected Missing(long start, int max_number_of_msgs) {
            missing_elements=new SeqnoList(max_number_of_msgs, start);
            this.max_num_msgs=max_number_of_msgs;
        }

        protected SeqnoList getMissingElements() {return missing_elements;}

        public boolean visit(long seqno, T element) {
            if(element == null) {
                if(++num_msgs > max_num_msgs)
                    return false;
                missing_elements.add(seqno);
            }
            return true;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy