Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore bytecode compatibility for limiter builders #197

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,6 @@ public abstract class AbstractLimiter<ContextT> implements Limiter<ContextT> {
public static final String ID_TAG = "id";
public static final String STATUS_TAG = "status";

/**
* Constructs a new builder with a list of bypass resolvers.
* If the predicate condition in any of the resolver is satisfied,
* the call is bypassed without increasing the limiter inflight count
* and affecting the algorithm.
*/
public abstract static class BypassLimiterBuilder<BuilderT extends BypassLimiterBuilder<BuilderT, ContextT>, ContextT> extends Builder<BuilderT> {

private final Predicate<ContextT> ALWAYS_FALSE = (context) -> false;
private Predicate<ContextT> bypassResolver = ALWAYS_FALSE;

/**
* Add a chainable bypass resolver predicate from context. Multiple resolvers may be added and if any of the
* predicate condition returns true the call is bypassed without increasing the limiter inflight count and
* affecting the algorithm. Will not bypass any calls by default if no resolvers are added.
* @param shouldBypass Predicate condition to bypass limit
* @return Chainable builder
*/
public BuilderT bypassLimitResolver(Predicate<ContextT> shouldBypass) {
if (this.bypassResolver == ALWAYS_FALSE) {
this.bypassResolver = shouldBypass;
} else {
this.bypassResolver = bypassResolver.or(shouldBypass);
}
return self();
}
}

public abstract static class Builder<BuilderT extends Builder<BuilderT>> {
private static final AtomicInteger idCounter = new AtomicInteger();

Expand All @@ -68,6 +40,9 @@ public abstract static class Builder<BuilderT extends Builder<BuilderT>> {
protected String name = "unnamed-" + idCounter.incrementAndGet();
protected MetricRegistry registry = EmptyMetricRegistry.INSTANCE;

private final Predicate<Object> ALWAYS_FALSE = (context) -> false;
private Predicate<Object> bypassResolver = ALWAYS_FALSE;

public BuilderT named(String name) {
this.name = name;
return self();
Expand All @@ -89,6 +64,26 @@ public BuilderT metricRegistry(MetricRegistry registry) {
}

protected abstract BuilderT self();

/**
* Add a chainable bypass resolver predicate from context. Multiple resolvers may be added and if any of the
* predicate condition returns true the call is bypassed without increasing the limiter inflight count and
* affecting the algorithm. Will not bypass any calls by default if no resolvers are added.
*
* Due to the builders not having access to the ContextT, it is the duty of subclasses to ensure that
* implementations are type safe.
*
* @param shouldBypass Predicate condition to bypass limit
* @return Chainable builder
*/
protected BuilderT bypassLimitResolverInternal(Predicate<?> shouldBypass) {
if (this.bypassResolver == ALWAYS_FALSE) {
this.bypassResolver = (Predicate<Object>) shouldBypass;
} else {
this.bypassResolver = bypassResolver.or((Predicate<Object>) shouldBypass);
}
return self();
}
}

private final AtomicInteger inFlight = new AtomicInteger();
Expand All @@ -108,9 +103,8 @@ protected AbstractLimiter(Builder<?> builder) {
this.limitAlgorithm = builder.limit;
this.limit = limitAlgorithm.getLimit();
this.limitAlgorithm.notifyOnChange(this::onNewLimit);
if (builder instanceof BypassLimiterBuilder) {
this.bypassResolver = ((BypassLimiterBuilder) builder).bypassResolver;
}
this.bypassResolver = (Predicate<ContextT>) builder.bypassResolver;

builder.registry.gauge(MetricIds.LIMIT_NAME, this::getLimit);
this.successCounter = builder.registry.counter(MetricIds.CALL_NAME, ID_TAG, builder.name, STATUS_TAG, "success");
this.droppedCounter = builder.registry.counter(MetricIds.CALL_NAME, ID_TAG, builder.name, STATUS_TAG, "dropped");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public abstract class AbstractPartitionedLimiter<ContextT> extends AbstractLimit
private static final Logger LOG = LoggerFactory.getLogger(AbstractPartitionedLimiter.class);
private static final String PARTITION_TAG_NAME = "partition";

public abstract static class Builder<BuilderT extends AbstractLimiter.BypassLimiterBuilder<BuilderT, ContextT>, ContextT> extends AbstractLimiter.BypassLimiterBuilder<BuilderT, ContextT> {
public abstract static class Builder<BuilderT extends AbstractLimiter.Builder<BuilderT>, ContextT> extends AbstractLimiter.Builder<BuilderT> {
private List<Function<ContextT, String>> partitionResolvers = new ArrayList<>();
private final Map<String, Partition> partitions = new LinkedHashMap<>();
private int maxDelayedThreads = 100;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,6 @@
import java.util.concurrent.Semaphore;

public class SimpleLimiter<ContextT> extends AbstractLimiter<ContextT> {

public static class BypassLimiterBuilder<ContextT> extends AbstractLimiter.BypassLimiterBuilder<BypassLimiterBuilder<ContextT>, ContextT> {
public SimpleLimiter<ContextT> build() {
return new SimpleLimiter<>(this);
}

@Override
protected BypassLimiterBuilder<ContextT> self() {
return this;
}
}

public static class Builder extends AbstractLimiter.Builder<Builder> {
public <ContextT> SimpleLimiter<ContextT> build() {
return new SimpleLimiter<>(this);
Expand All @@ -46,10 +34,6 @@ protected Builder self() {
}
}

public static <ContextT> BypassLimiterBuilder<ContextT> newBypassLimiterBuilder() {
return new BypassLimiterBuilder<>();
}

public static Builder newBuilder() {
return new Builder();
}
Expand All @@ -58,6 +42,7 @@ public static Builder newBuilder() {

public SimpleLimiter(AbstractLimiter.Builder<?> builder) {
super(builder);

this.inflightDistribution = builder.registry.distribution(MetricIds.INFLIGHT_NAME);
this.semaphore = new AdjustableSemaphore(getLimit());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public void testBypassPartitionedLimiter() {
.partition("batch", 0.1)
.partition("live", 0.9)
.limit(FixedLimit.of(10))
.bypassLimitResolver(new ShouldBypassPredicate())
.bypassLimitResolverInternal(new ShouldBypassPredicate())
.build();

Assert.assertTrue(limiter.acquire("batch").isPresent());
Expand All @@ -200,7 +200,7 @@ public void testBypassSimpleLimiter() {

SimpleLimiter<String> limiter = (SimpleLimiter<String>) TestPartitionedLimiter.newBuilder()
.limit(FixedLimit.of(10))
.bypassLimitResolver(new ShouldBypassPredicate())
.bypassLimitResolverInternal(new ShouldBypassPredicate())
.build();

int inflightCount = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public void testReleaseLimit() {

@Test
public void testSimpleBypassLimiter() {
SimpleLimiter<String> limiter = SimpleLimiter.<String>newBypassLimiterBuilder()
SimpleLimiter<String> limiter = SimpleLimiter.<String>newBuilder()
.limit(FixedLimit.of(10))
.bypassLimitResolver((context) -> context.equals("admin"))
.bypassLimitResolverInternal((context) -> context.equals("admin"))
.build();

for (int i = 0; i < 10; i++) {
Expand All @@ -68,7 +68,7 @@ public void testSimpleBypassLimiter() {

@Test
public void testSimpleBypassLimiterDefault() {
SimpleLimiter<String> limiter = SimpleLimiter.<String>newBypassLimiterBuilder()
SimpleLimiter<String> limiter = SimpleLimiter.<String>newBuilder()
.limit(FixedLimit.of(10))
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.netflix.concurrency.limits.limiter.AbstractPartitionedLimiter;
import com.netflix.concurrency.limits.limiter.BlockingLimiter;
import io.grpc.CallOptions;
import java.util.function.Predicate;

/**
* Builder to simplify creating a {@link Limiter} specific to GRPC clients.
Expand All @@ -34,6 +35,18 @@ public GrpcClientLimiterBuilder partitionByCallOption(CallOptions.Key<String> op
return partitionResolver(context -> context.getCallOptions().getOption(option));
}

/**
* Add a chainable bypass resolver predicate from context. Multiple resolvers may be added and if any of the
* predicate condition returns true the call is bypassed without increasing the limiter inflight count and
* affecting the algorithm. Will not bypass any calls by default if no resolvers are added.
*
* @param shouldBypass Predicate condition to bypass limit
* @return Chainable builder
*/
public GrpcClientLimiterBuilder bypassLimitResolver(Predicate<GrpcClientRequestContext> shouldBypass) {
return bypassLimitResolverInternal(shouldBypass);
}

/**
* Bypass limit if the request's full method name matches the specified gRPC method's full name.
* @param fullMethodName The full method name to check against the {@link GrpcClientRequestContext}'s method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.netflix.concurrency.limits.limiter.AbstractPartitionedLimiter;
import io.grpc.Attributes;
import io.grpc.Metadata;
import java.util.function.Predicate;

public class GrpcServerLimiterBuilder extends AbstractPartitionedLimiter.Builder<GrpcServerLimiterBuilder, GrpcServerRequestContext> {
/**
Expand All @@ -44,6 +45,19 @@ public GrpcServerLimiterBuilder partitionByAttribute(Attributes.Key<String> attr
return partitionResolver(context -> context.getCall().getAttributes().get(attribute));
}


/**
* Add a chainable bypass resolver predicate from context. Multiple resolvers may be added and if any of the
* predicate condition returns true the call is bypassed without increasing the limiter inflight count and
* affecting the algorithm. Will not bypass any calls by default if no resolvers are added.
*
* @param shouldBypass Predicate condition to bypass limit
* @return Chainable builder
*/
public GrpcServerLimiterBuilder bypassLimitResolver(Predicate<GrpcServerRequestContext> shouldBypass) {
return bypassLimitResolverInternal(shouldBypass);
}

/**
* Bypass limit if the request's full method name matches the specified gRPC method's full name.
* @param fullMethodName The full method name to check against the {@link GrpcServerRequestContext}'s method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.security.Principal;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

/**
* Builder to simplify creating a {@link Limiter} specific to a Servlet filter. By default,
Expand Down Expand Up @@ -73,6 +74,18 @@ public ServletLimiterBuilder partitionByPathInfo(Function<String, String> pathTo
return partitionResolver(request -> Optional.ofNullable(request.getPathInfo()).map(pathToGroup).orElse(null));
}

/**
* Add a chainable bypass resolver predicate from context. Multiple resolvers may be added and if any of the
* predicate condition returns true the call is bypassed without increasing the limiter inflight count and
* affecting the algorithm. Will not bypass any calls by default if no resolvers are added.
*
* @param shouldBypass Predicate condition to bypass limit
* @return Chainable builder
*/
public ServletLimiterBuilder bypassLimitResolver(Predicate<HttpServletRequest> shouldBypass) {
return bypassLimitResolverInternal(shouldBypass);
}

/**
* Bypass limit if the value of the provided header name matches the specified value.
* @param name The name of the header to check.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.netflix.concurrency.limits.Limiter;
import com.netflix.concurrency.limits.limiter.AbstractPartitionedLimiter;

import java.util.function.Predicate;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Optional;
Expand Down Expand Up @@ -73,6 +74,18 @@ public ServletLimiterBuilder partitionByPathInfo(Function<String, String> pathTo
return partitionResolver(request -> Optional.ofNullable(request.getPathInfo()).map(pathToGroup).orElse(null));
}

/**
* Add a chainable bypass resolver predicate from context. Multiple resolvers may be added and if any of the
* predicate condition returns true the call is bypassed without increasing the limiter inflight count and
* affecting the algorithm. Will not bypass any calls by default if no resolvers are added.
*
* @param shouldBypass Predicate condition to bypass limit
* @return Chainable builder
*/
public ServletLimiterBuilder bypassLimitResolver(Predicate<HttpServletRequest> shouldBypass) {
return bypassLimitResolverInternal(shouldBypass);
}

/**
* Bypass the limit if the value of the provided header name matches the specified value.
* @param name The name of the header to check.
Expand Down
Loading