/*
 * 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.cassandra.net;

import java.util.concurrent.atomic.AtomicLongFieldUpdater;

public abstract class ResourceLimits
{
    
Represents permits to utilise a resource and ways to allocate and release them. Two implementations are currently provided: 1. Concurrent, for shared limits, which is thread-safe; 2. Basic, for limits that are not shared between threads, is not thread-safe.
/** * Represents permits to utilise a resource and ways to allocate and release them. * * Two implementations are currently provided: * 1. {@link Concurrent}, for shared limits, which is thread-safe; * 2. {@link Basic}, for limits that are not shared between threads, is not thread-safe. */
public interface Limit {
Returns:total amount of permits represented by this Limit - the capacity
/** * @return total amount of permits represented by this {@link Limit} - the capacity */
long limit();
Returns:remaining, unallocated permit amount
/** * @return remaining, unallocated permit amount */
long remaining();
Returns:amount of permits currently in use
/** * @return amount of permits currently in use */
long using();
Attempts to allocate an amount of permits from this limit. If allocated, MUST eventually be released back with release(long).
Returns:true if the allocation was successful, false otherwise
/** * Attempts to allocate an amount of permits from this limit. If allocated, <em>MUST</em> eventually * be released back with {@link #release(long)}. * * @return {@code true} if the allocation was successful, {@code false} otherwise */
boolean tryAllocate(long amount);
Allocates an amount independent of permits available from this limit. MUST eventually be released back with release(long).
/** * Allocates an amount independent of permits available from this limit. <em>MUST</em> eventually * be released back with {@link #release(long)}. * */
void allocate(long amount);
Params:
  • amount – return the amount of permits back to this limit
Returns:ABOVE_LIMIT if there aren't enough permits available even after the release, or BELOW_LIMIT if there are enough permits available after the releaese.
/** * @param amount return the amount of permits back to this limit * @return {@code ABOVE_LIMIT} if there aren't enough permits available even after the release, or * {@code BELOW_LIMIT} if there are enough permits available after the releaese. */
Outcome release(long amount); }
A thread-safe permit container.
/** * A thread-safe permit container. */
public static class Concurrent implements Limit { private final long limit; private volatile long using; private static final AtomicLongFieldUpdater<Concurrent> usingUpdater = AtomicLongFieldUpdater.newUpdater(Concurrent.class, "using"); public Concurrent(long limit) { this.limit = limit; } public long limit() { return limit; } public long remaining() { return limit - using; } public long using() { return using; } public boolean tryAllocate(long amount) { long current, next; do { current = using; next = current + amount; if (next > limit) return false; } while (!usingUpdater.compareAndSet(this, current, next)); return true; } public void allocate(long amount) { long current, next; do { current = using; next = current + amount; } while (!usingUpdater.compareAndSet(this, current, next)); } public Outcome release(long amount) { assert amount >= 0; long using = usingUpdater.addAndGet(this, -amount); assert using >= 0; return using >= limit ? Outcome.ABOVE_LIMIT : Outcome.BELOW_LIMIT; } }
A cheaper, thread-unsafe permit container to be used for unshared limits.
/** * A cheaper, thread-unsafe permit container to be used for unshared limits. */
static class Basic implements Limit { private final long limit; private long using; Basic(long limit) { this.limit = limit; } public long limit() { return limit; } public long remaining() { return limit - using; } public long using() { return using; } public boolean tryAllocate(long amount) { if (using + amount > limit) return false; using += amount; return true; } public void allocate(long amount) { using += amount; } public Outcome release(long amount) { assert amount >= 0 && amount <= using; using -= amount; return using >= limit ? Outcome.ABOVE_LIMIT : Outcome.BELOW_LIMIT; } }
A convenience class that groups a per-endpoint limit with the global one to allow allocating/releasing permits from/to both limits as one logical operation.
/** * A convenience class that groups a per-endpoint limit with the global one * to allow allocating/releasing permits from/to both limits as one logical operation. */
public static class EndpointAndGlobal { final Limit endpoint; final Limit global; public EndpointAndGlobal(Limit endpoint, Limit global) { this.endpoint = endpoint; this.global = global; } public Limit endpoint() { return endpoint; } public Limit global() { return global; }
Returns:INSUFFICIENT_GLOBAL if there weren't enough permits in the global limit, or INSUFFICIENT_ENDPOINT if there weren't enough permits in the per-endpoint limit, or SUCCESS if there were enough permits to take from both.
/** * @return {@code INSUFFICIENT_GLOBAL} if there weren't enough permits in the global limit, or * {@code INSUFFICIENT_ENDPOINT} if there weren't enough permits in the per-endpoint limit, or * {@code SUCCESS} if there were enough permits to take from both. */
public Outcome tryAllocate(long amount) { if (!global.tryAllocate(amount)) return Outcome.INSUFFICIENT_GLOBAL; if (endpoint.tryAllocate(amount)) return Outcome.SUCCESS; global.release(amount); return Outcome.INSUFFICIENT_ENDPOINT; } public void allocate(long amount) { global.allocate(amount); endpoint.allocate(amount); } public Outcome release(long amount) { Outcome endpointReleaseOutcome = endpoint.release(amount); Outcome globalReleaseOutcome = global.release(amount); return (endpointReleaseOutcome == Outcome.ABOVE_LIMIT || globalReleaseOutcome == Outcome.ABOVE_LIMIT) ? Outcome.ABOVE_LIMIT : Outcome.BELOW_LIMIT; } } public enum Outcome { SUCCESS, INSUFFICIENT_ENDPOINT, INSUFFICIENT_GLOBAL, BELOW_LIMIT, ABOVE_LIMIT } }