This is an automated email from the ASF dual-hosted git repository.

xyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git


The following commit(s) were added to refs/heads/master by this push:
     new c50f4afeae6 [fix] [broker] fix compile error for 
PersistentStickyKeyDispatcherMultipleConsumers (#23055)
c50f4afeae6 is described below

commit c50f4afeae610d3f1994aa2dd53c761589bbb4e2
Author: fengyubiao <[email protected]>
AuthorDate: Sun Jul 21 19:44:52 2024 +0800

    [fix] [broker] fix compile error for 
PersistentStickyKeyDispatcherMultipleConsumers (#23055)
---
 .../ConcurrentOpenLongPairRangeSet.java            | 420 +++++++++++++++++++++
 1 file changed, 420 insertions(+)

diff --git 
a/pulsar-common/src/main/java/org/apache/pulsar/common/util/collections/ConcurrentOpenLongPairRangeSet.java
 
b/pulsar-common/src/main/java/org/apache/pulsar/common/util/collections/ConcurrentOpenLongPairRangeSet.java
new file mode 100644
index 00000000000..6e454019785
--- /dev/null
+++ 
b/pulsar-common/src/main/java/org/apache/pulsar/common/util/collections/ConcurrentOpenLongPairRangeSet.java
@@ -0,0 +1,420 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pulsar.common.util.collections;
+
+import static java.util.Objects.requireNonNull;
+import com.google.common.collect.BoundType;
+import com.google.common.collect.Range;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.commons.lang.mutable.MutableInt;
+
+/**
+ * A Concurrent set comprising zero or more ranges of type {@link LongPair}. 
This can be alternative of
+ * {@link com.google.common.collect.RangeSet} and can be used if {@code range} 
type is {@link LongPair}
+ *
+ * <pre>
+ * Usage:
+ * a. This can be used if one doesn't want to create object for every new 
inserted {@code range}
+ * b. It creates {@link BitSet} for every unique first-key of the range.
+ * So, this rangeSet is not suitable for large number of unique keys.
+ * </pre>
+ */
+public class ConcurrentOpenLongPairRangeSet<T extends Comparable<T>> 
implements LongPairRangeSet<T> {
+
+    protected final NavigableMap<Long, BitSet> rangeBitSetMap = new 
ConcurrentSkipListMap<>();
+    private boolean threadSafe = true;
+    private final int bitSetSize;
+    private final LongPairConsumer<T> consumer;
+
+    // caching place-holder for cpu-optimization to avoid calculating ranges 
again
+    private volatile int cachedSize = 0;
+    private volatile String cachedToString = "[]";
+    private volatile boolean updatedAfterCachedForSize = true;
+    private volatile boolean updatedAfterCachedForToString = true;
+
+    public ConcurrentOpenLongPairRangeSet(LongPairConsumer<T> consumer) {
+        this(1024, true, consumer);
+    }
+
+    public ConcurrentOpenLongPairRangeSet(int size, LongPairConsumer<T> 
consumer) {
+        this(size, true, consumer);
+    }
+
+    public ConcurrentOpenLongPairRangeSet(int size, boolean threadSafe, 
LongPairConsumer<T> consumer) {
+        this.threadSafe = threadSafe;
+        this.bitSetSize = size;
+        this.consumer = consumer;
+    }
+
+    /**
+     * Adds the specified range to this {@code RangeSet} (optional operation). 
That is, for equal range sets a and b,
+     * the result of {@code a.add(range)} is that {@code a} will be the 
minimal range set for which both
+     * {@code a.enclosesAll(b)} and {@code a.encloses(range)}.
+     *
+     * <p>Note that {@code range} will merge given {@code range} with any 
ranges in the range set that are
+     * {@linkplain Range#isConnected(Range) connected} with it. Moreover, if 
{@code range} is empty, this is a no-op.
+     */
+    @Override
+    public void addOpenClosed(long lowerKey, long lowerValueOpen, long 
upperKey, long upperValue) {
+        long lowerValue = lowerValueOpen + 1;
+        if (lowerKey != upperKey) {
+            // (1) set lower to last in lowerRange.getKey()
+            if (isValid(lowerKey, lowerValue)) {
+                BitSet rangeBitSet = rangeBitSetMap.get(lowerKey);
+                // if lower and upper has different key/ledger then set ranges 
for lower-key only if
+                // a. bitSet already exist and given value is not the last 
value in the bitset.
+                // it will prevent setting up values which are not actually 
expected to set
+                // eg: (2:10..4:10] in this case, don't set any value for 2:10 
and set [4:0..4:10]
+                if (rangeBitSet != null && 
(rangeBitSet.previousSetBit(rangeBitSet.size()) > lowerValueOpen)) {
+                    int lastValue = 
rangeBitSet.previousSetBit(rangeBitSet.size());
+                    rangeBitSet.set((int) lowerValue, (int) 
Math.max(lastValue, lowerValue) + 1);
+                }
+            }
+            // (2) set 0th-index to upper-index in upperRange.getKey()
+            if (isValid(upperKey, upperValue)) {
+                BitSet rangeBitSet = rangeBitSetMap.computeIfAbsent(upperKey, 
(key) -> createNewBitSet());
+                if (rangeBitSet != null) {
+                    rangeBitSet.set(0, (int) upperValue + 1);
+                }
+            }
+            // No-op if values are not valid eg: if lower == LongPair.earliest 
or upper == LongPair.latest then nothing
+            // to set
+        } else {
+            long key = lowerKey;
+            BitSet rangeBitSet = rangeBitSetMap.computeIfAbsent(key, (k) -> 
createNewBitSet());
+            rangeBitSet.set((int) lowerValue, (int) upperValue + 1);
+        }
+        updatedAfterCachedForSize = true;
+        updatedAfterCachedForToString = true;
+    }
+
+    private boolean isValid(long key, long value) {
+        return key != LongPair.earliest.getKey() && value != 
LongPair.earliest.getValue()
+                && key != LongPair.latest.getKey() && value != 
LongPair.latest.getValue();
+    }
+
+    @Override
+    public boolean contains(long key, long value) {
+
+        BitSet rangeBitSet = rangeBitSetMap.get(key);
+        if (rangeBitSet != null) {
+            return rangeBitSet.get(getSafeEntry(value));
+        }
+        return false;
+    }
+
+    @Override
+    public Range<T> rangeContaining(long key, long value) {
+        BitSet rangeBitSet = rangeBitSetMap.get(key);
+        if (rangeBitSet != null) {
+            if (!rangeBitSet.get(getSafeEntry(value))) {
+                // if position is not part of any range then return null
+                return null;
+            }
+            int lowerValue = rangeBitSet.previousClearBit(getSafeEntry(value)) 
+ 1;
+            final T lower = consumer.apply(key, lowerValue);
+            final T upper = consumer.apply(key,
+                    Math.max(rangeBitSet.nextClearBit(getSafeEntry(value)) - 
1, lowerValue));
+            return Range.closed(lower, upper);
+        }
+        return null;
+    }
+
+    @Override
+    public void removeAtMost(long key, long value) {
+        this.remove(Range.atMost(new LongPair(key, value)));
+    }
+
+    @Override
+    public boolean isEmpty() {
+        if (rangeBitSetMap.isEmpty()) {
+            return true;
+        }
+        for (BitSet rangeBitSet : rangeBitSetMap.values()) {
+            if (!rangeBitSet.isEmpty()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void clear() {
+        rangeBitSetMap.clear();
+        updatedAfterCachedForSize = true;
+        updatedAfterCachedForToString = true;
+    }
+
+    @Override
+    public Range<T> span() {
+        if (rangeBitSetMap.isEmpty()) {
+            return null;
+        }
+        Entry<Long, BitSet> firstSet = rangeBitSetMap.firstEntry();
+        Entry<Long, BitSet> lastSet = rangeBitSetMap.lastEntry();
+        int first = firstSet.getValue().nextSetBit(0);
+        int last = 
lastSet.getValue().previousSetBit(lastSet.getValue().size());
+        return Range.openClosed(consumer.apply(firstSet.getKey(), first - 1), 
consumer.apply(lastSet.getKey(), last));
+    }
+
+    @Override
+    public List<Range<T>> asRanges() {
+        List<Range<T>> ranges = new ArrayList<>();
+        forEach((range) -> {
+            ranges.add(range);
+            return true;
+        });
+        return ranges;
+    }
+
+    @Override
+    public void forEach(RangeProcessor<T> action) {
+        forEach(action, consumer);
+    }
+
+    @Override
+    public void forEach(RangeProcessor<T> action, LongPairConsumer<? extends 
T> consumerParam) {
+        forEachRawRange((lowerKey, lowerValue, upperKey, upperValue) -> {
+            Range<T> range = Range.openClosed(
+                    consumerParam.apply(lowerKey, lowerValue),
+                    consumerParam.apply(upperKey, upperValue)
+            );
+            return action.process(range);
+        });
+    }
+
+    @Override
+    public void forEachRawRange(RawRangeProcessor processor) {
+        AtomicBoolean completed = new AtomicBoolean(false);
+        rangeBitSetMap.forEach((key, set) -> {
+            if (completed.get()) {
+                return;
+            }
+            if (set.isEmpty()) {
+                return;
+            }
+            int first = set.nextSetBit(0);
+            int last = set.previousSetBit(set.size());
+            int currentClosedMark = first;
+            while (currentClosedMark != -1 && currentClosedMark <= last) {
+                int nextOpenMark = set.nextClearBit(currentClosedMark);
+                if (!processor.processRawRange(key, currentClosedMark - 1,
+                        key, nextOpenMark - 1)) {
+                    completed.set(true);
+                    break;
+                }
+                currentClosedMark = set.nextSetBit(nextOpenMark);
+            }
+        });
+    }
+
+
+    @Override
+    public Range<T> firstRange() {
+        if (rangeBitSetMap.isEmpty()) {
+            return null;
+        }
+        Entry<Long, BitSet> firstSet = rangeBitSetMap.firstEntry();
+        int lower = firstSet.getValue().nextSetBit(0);
+        int upper = Math.max(lower, firstSet.getValue().nextClearBit(lower) - 
1);
+        return Range.openClosed(consumer.apply(firstSet.getKey(), lower - 1), 
consumer.apply(firstSet.getKey(), upper));
+    }
+
+    @Override
+    public Range<T> lastRange() {
+        if (rangeBitSetMap.isEmpty()) {
+            return null;
+        }
+        Entry<Long, BitSet> lastSet = rangeBitSetMap.lastEntry();
+        int upper = 
lastSet.getValue().previousSetBit(lastSet.getValue().size());
+        int lower = Math.min(lastSet.getValue().previousClearBit(upper), 
upper);
+        return Range.openClosed(consumer.apply(lastSet.getKey(), lower), 
consumer.apply(lastSet.getKey(), upper));
+    }
+
+    @Override
+    public int cardinality(long lowerKey, long lowerValue, long upperKey, long 
upperValue) {
+        NavigableMap<Long, BitSet> subMap = rangeBitSetMap.subMap(lowerKey, 
true, upperKey, true);
+        MutableInt v = new MutableInt(0);
+        subMap.forEach((key, bitset) -> {
+            if (key == lowerKey || key == upperKey) {
+                BitSet temp = (BitSet) bitset.clone();
+                // Trim the bitset index which < lowerValue
+                if (key == lowerKey) {
+                    temp.clear(0, (int) Math.max(0, lowerValue));
+                }
+                // Trim the bitset index which > upperValue
+                if (key == upperKey) {
+                    temp.clear((int) Math.min(upperValue + 1, temp.length()), 
temp.length());
+                }
+                v.add(temp.cardinality());
+            } else {
+                v.add(bitset.cardinality());
+            }
+        });
+        return v.intValue();
+    }
+
+    @Override
+    public int size() {
+        if (updatedAfterCachedForSize) {
+            MutableInt size = new MutableInt(0);
+
+            // ignore result because we just want to count
+            forEachRawRange((lowerKey, lowerValue, upperKey, upperValue) -> {
+                size.increment();
+                return true;
+            });
+
+            cachedSize = size.intValue();
+            updatedAfterCachedForSize = false;
+        }
+        return cachedSize;
+    }
+
+    @Override
+    public String toString() {
+        if (updatedAfterCachedForToString) {
+            StringBuilder toString = new StringBuilder();
+            AtomicBoolean first = new AtomicBoolean(true);
+            if (toString != null) {
+                toString.append("[");
+            }
+            forEach((range) -> {
+                if (!first.get()) {
+                    toString.append(",");
+                }
+                toString.append(range);
+                first.set(false);
+                return true;
+            });
+            toString.append("]");
+            cachedToString = toString.toString();
+            updatedAfterCachedForToString = false;
+        }
+        return cachedToString;
+    }
+
+    /**
+     * Adds the specified range to this {@code RangeSet} (optional operation). 
That is, for equal range sets a and b,
+     * the result of {@code a.add(range)} is that {@code a} will be the 
minimal range set for which both
+     * {@code a.enclosesAll(b)} and {@code a.encloses(range)}.
+     *
+     * <p>Note that {@code range} will merge given {@code range} with any 
ranges in the range set that are
+     * {@linkplain Range#isConnected(Range) connected} with it. Moreover, if 
{@code range} is empty/invalid, this is a
+     * no-op.
+     */
+    public void add(Range<LongPair> range) {
+        LongPair lowerEndpoint = range.hasLowerBound() ? range.lowerEndpoint() 
: LongPair.earliest;
+        LongPair upperEndpoint = range.hasUpperBound() ? range.upperEndpoint() 
: LongPair.latest;
+
+        long lowerValueOpen = (range.hasLowerBound() && 
range.lowerBoundType().equals(BoundType.CLOSED))
+                ? getSafeEntry(lowerEndpoint) - 1
+                : getSafeEntry(lowerEndpoint);
+        long upperValueClosed = (range.hasUpperBound() && 
range.upperBoundType().equals(BoundType.CLOSED))
+                ? getSafeEntry(upperEndpoint)
+                : getSafeEntry(upperEndpoint) + 1;
+
+        // #addOpenClosed doesn't create bitSet for lower-key because it 
avoids setting up values for non-exist items
+        // into the key-ledger. so, create bitSet and initialize so, it can't 
be ignored at #addOpenClosed
+        rangeBitSetMap.computeIfAbsent(lowerEndpoint.getKey(), (key) -> 
createNewBitSet())
+                .set((int) lowerValueOpen + 1);
+        this.addOpenClosed(lowerEndpoint.getKey(), lowerValueOpen, 
upperEndpoint.getKey(), upperValueClosed);
+    }
+
+    public boolean contains(LongPair position) {
+        requireNonNull(position, "argument can't be null");
+        return contains(position.getKey(), position.getValue());
+    }
+
+    public void remove(Range<LongPair> range) {
+        LongPair lowerEndpoint = range.hasLowerBound() ? range.lowerEndpoint() 
: LongPair.earliest;
+        LongPair upperEndpoint = range.hasUpperBound() ? range.upperEndpoint() 
: LongPair.latest;
+
+        long lower = (range.hasLowerBound() && 
range.lowerBoundType().equals(BoundType.CLOSED))
+                ? getSafeEntry(lowerEndpoint)
+                : getSafeEntry(lowerEndpoint) + 1;
+        long upper = (range.hasUpperBound() && 
range.upperBoundType().equals(BoundType.CLOSED))
+                ? getSafeEntry(upperEndpoint)
+                : getSafeEntry(upperEndpoint) - 1;
+
+        // if lower-bound is not set then remove all the keys less than given 
upper-bound range
+        if (lowerEndpoint.equals(LongPair.earliest)) {
+            // remove all keys with
+            rangeBitSetMap.forEach((key, set) -> {
+                if (key < upperEndpoint.getKey()) {
+                    rangeBitSetMap.remove(key);
+                }
+            });
+        }
+
+        // if upper-bound is not set then remove all the keys greater than 
given lower-bound range
+        if (upperEndpoint.equals(LongPair.latest)) {
+            // remove all keys with
+            rangeBitSetMap.forEach((key, set) -> {
+                if (key > lowerEndpoint.getKey()) {
+                    rangeBitSetMap.remove(key);
+                }
+            });
+        }
+
+        // remove all the keys between two endpoint keys
+        rangeBitSetMap.forEach((key, set) -> {
+            if (lowerEndpoint.getKey() == upperEndpoint.getKey() && key == 
upperEndpoint.getKey()) {
+                set.clear((int) lower, (int) upper + 1);
+            } else {
+                // eg: remove-range: [(3,5) - (5,5)] -> Delete all items from 
3,6->3,N,4.*,5,0->5,5
+                if (key == lowerEndpoint.getKey()) {
+                    // remove all entries from given position to last position
+                    set.clear((int) lower, set.previousSetBit(set.size()));
+                } else if (key == upperEndpoint.getKey()) {
+                    // remove all entries from 0 to given position
+                    set.clear(0, (int) upper + 1);
+                } else if (key > lowerEndpoint.getKey() && key < 
upperEndpoint.getKey()) {
+                    rangeBitSetMap.remove(key);
+                }
+            }
+            // remove bit-set if set is empty
+            if (set.isEmpty()) {
+                rangeBitSetMap.remove(key);
+            }
+        });
+
+        updatedAfterCachedForSize = true;
+        updatedAfterCachedForToString = true;
+    }
+
+    private int getSafeEntry(LongPair position) {
+        return (int) Math.max(position.getValue(), -1);
+    }
+
+    private int getSafeEntry(long value) {
+        return (int) Math.max(value, -1);
+    }
+
+    private BitSet createNewBitSet() {
+        return this.threadSafe ? new ConcurrentBitSet(bitSetSize) : new 
BitSet(bitSetSize);
+    }
+
+}
\ No newline at end of file

Reply via email to