com.swirlds.common.utility.ThresholdLimitingHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swirlds-common Show documentation
Show all versions of swirlds-common Show documentation
Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed 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 com.swirlds.common.utility;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Provides a simple reusable thresholding mechanism to effectively apply rate limiting on arbitrary events. This
* implementation groups the events by {@code Strings} that are extracted from the supplied elements.
*
* @param
* the type of element to be rate limited
*/
public class ThresholdLimitingHandler {
/** the threshold at which to begin suppression */
private final long threshold;
/** the function that extract the key from a given element */
private final Function keyExtractor;
/** the state of each threshold counter for each element type */
private final Map state;
/**
* Constructor that initializes the underlying threshold.
*
* The {@code keyExtractor} groups the events by the underlying {@link Class} of the supplied elements.
*
* @param threshold
* the threshold at which events are discarded
*/
public ThresholdLimitingHandler(final long threshold) {
this(threshold, ThresholdLimitingHandler::resolveElementClass);
}
/**
* Constructor that initializes the underlying threshold.
*
* @param threshold
* the threshold at which events are discarded
* @param keyExtractor
* function that extracts the key
*/
public ThresholdLimitingHandler(final long threshold, final Function keyExtractor) {
this.threshold = threshold;
this.keyExtractor = keyExtractor;
this.state = new ConcurrentHashMap<>();
}
/**
* Applies thresholding to a given {@code element} based on it's {@link Class} and invokes the {@code callback}
* method if the number of times we have seen this element is less than or equal to the threshold limit.
*
* @param element
* the item for which thresholding should be applied
* @param callback
* the {@link Consumer} to notify if the supplied element is within the threshold limits
* @throws IllegalArgumentException
* if the {@code element} parameter is {@code null}
*/
public void handle(final E element, final Consumer callback) {
if (element == null) {
throw new IllegalArgumentException("The element argument may not be a null value");
}
final String key = keyExtractor.apply(element);
final long counter = state.compute(key, (k, oldValue) -> {
if (oldValue == null) {
return 1L;
}
return oldValue + 1;
});
if (counter <= threshold && callback != null) {
callback.accept(element);
}
}
/**
* Returns the current counter for the given element. Uses the {@link Class} of the element to locate the counter.
*
* @param element
* the element for which to retrieve the counter
* @return the number of times we have seen elements with the same {@link Class} as the one provided
* @throws IllegalArgumentException
* if the {@code element} parameter is {@code null}
*/
public long getCurrentThreshold(final E element) {
if (element == null) {
throw new IllegalArgumentException("The element argument may not be a null value");
}
final String key = keyExtractor.apply(element);
return state.getOrDefault(key, 0L);
}
/**
* Clears all counters.
*/
public void reset() {
state.clear();
}
/**
* Clears the counter for the provided element. Uses the {@link Class} of the element to locate the counter.
*
* @param element
* the element for which the counter should be cleared
*/
public void reset(final E element) {
final String key = keyExtractor.apply(element);
state.remove(key);
}
private static String resolveElementClass(final E element) {
@SuppressWarnings("unchecked")
final Class elClass = (element != null) ? (Class) element.getClass() : null;
return "" + elClass;
}
}