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

io.github.bucket4j.local.SynchronizedBucket Maven / Gradle / Ivy

There is a newer version: 8.0.1
Show newest version
/*-
 * ========================LICENSE_START=================================
 * Bucket4j
 * %%
 * Copyright (C) 2015 - 2020 Vladimir Bukhtoyarov
 * %%
 * 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.
 * =========================LICENSE_END==================================
 */

package io.github.bucket4j.local;


import io.github.bucket4j.*;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SynchronizedBucket extends AbstractBucket implements LocalBucket {

    private BucketConfiguration configuration;
    private Bandwidth[] bandwidths;
    private final TimeMeter timeMeter;
    private BucketState state;
    private final Lock lock;

    public SynchronizedBucket(BucketConfiguration configuration, TimeMeter timeMeter) {
        this(configuration, timeMeter, new ReentrantLock());
    }

    SynchronizedBucket(BucketConfiguration configuration, TimeMeter timeMeter, Lock lock) {
        this(BucketListener.NOPE, configuration, timeMeter, lock, BucketState.createInitialState(configuration, timeMeter.currentTimeNanos()));
    }

    private SynchronizedBucket(BucketListener listener, BucketConfiguration configuration, TimeMeter timeMeter, Lock lock, BucketState initialState) {
        super(listener);
        this.configuration = configuration;
        this.bandwidths = configuration.getBandwidths();
        this.timeMeter = timeMeter;
        this.state = initialState;
        this.lock = lock;
    }

    @Override
    public Bucket toListenable(BucketListener listener) {
        return new SynchronizedBucket(listener, configuration, timeMeter, lock, state);
    }

    @Override
    public boolean isAsyncModeSupported() {
        return true;
    }

    @Override
    protected long consumeAsMuchAsPossibleImpl(long limit) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableToConsume = state.getAvailableTokens(bandwidths);
            long toConsume = Math.min(limit, availableToConsume);
            if (toConsume == 0) {
                return 0;
            }
            state.consume(bandwidths, toConsume);
            return toConsume;
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected boolean tryConsumeImpl(long tokensToConsume) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableToConsume = state.getAvailableTokens(bandwidths);
            if (tokensToConsume > availableToConsume) {
                return false;
            }
            state.consume(bandwidths, tokensToConsume);
            return true;
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected ConsumptionProbe tryConsumeAndReturnRemainingTokensImpl(long tokensToConsume) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableToConsume = state.getAvailableTokens(bandwidths);
            if (tokensToConsume > availableToConsume) {
                long nanosToWaitForRefill = state.calculateDelayNanosAfterWillBePossibleToConsume(bandwidths, tokensToConsume, currentTimeNanos);
                return ConsumptionProbe.rejected(availableToConsume, nanosToWaitForRefill);
            }
            state.consume(bandwidths, tokensToConsume);
            return ConsumptionProbe.consumed(availableToConsume - tokensToConsume);
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected EstimationProbe estimateAbilityToConsumeImpl(long tokensToEstimate) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableToConsume = state.getAvailableTokens(bandwidths);
            if (tokensToEstimate > availableToConsume) {
                long nanosToWaitForRefill = state.calculateDelayNanosAfterWillBePossibleToConsume(bandwidths, tokensToEstimate, currentTimeNanos);
                return EstimationProbe.canNotBeConsumed(availableToConsume, nanosToWaitForRefill);
            }
            return EstimationProbe.canBeConsumed(availableToConsume);
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected long reserveAndCalculateTimeToSleepImpl(long tokensToConsume, long waitIfBusyNanosLimit) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long nanosToCloseDeficit = state.calculateDelayNanosAfterWillBePossibleToConsume(bandwidths, tokensToConsume, currentTimeNanos);

            if (nanosToCloseDeficit == Long.MAX_VALUE || nanosToCloseDeficit > waitIfBusyNanosLimit) {
                return Long.MAX_VALUE;
            }

            state.consume(bandwidths, tokensToConsume);
            return nanosToCloseDeficit;
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected long consumeIgnoringRateLimitsImpl(long tokensToConsume) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long nanosToCloseDeficit = state.calculateDelayNanosAfterWillBePossibleToConsume(bandwidths, tokensToConsume, currentTimeNanos);

            if (nanosToCloseDeficit == INFINITY_DURATION) {
                return nanosToCloseDeficit;
            }
            state.consume(bandwidths, tokensToConsume);
            return nanosToCloseDeficit;
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected VerboseResult consumeAsMuchAsPossibleVerboseImpl(long limit) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableToConsume = state.getAvailableTokens(bandwidths);
            long toConsume = Math.min(limit, availableToConsume);
            if (toConsume == 0) {
                return new VerboseResult<>(currentTimeNanos, 0L, configuration, state.copy());
            }
            state.consume(bandwidths, toConsume);
            return new VerboseResult<>(currentTimeNanos, toConsume, configuration, state.copy());
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected VerboseResult tryConsumeVerboseImpl(long tokensToConsume) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableToConsume = state.getAvailableTokens(bandwidths);
            if (tokensToConsume > availableToConsume) {
                return new VerboseResult<>(currentTimeNanos, false, configuration, state.copy());
            }
            state.consume(bandwidths, tokensToConsume);
            return new VerboseResult<>(currentTimeNanos, true, configuration, state.copy());
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected VerboseResult tryConsumeAndReturnRemainingTokensVerboseImpl(long tokensToConsume) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableToConsume = state.getAvailableTokens(bandwidths);
            if (tokensToConsume > availableToConsume) {
                long nanosToWaitForRefill = state.calculateDelayNanosAfterWillBePossibleToConsume(bandwidths, tokensToConsume, currentTimeNanos);
                ConsumptionProbe probe = ConsumptionProbe.rejected(availableToConsume, nanosToWaitForRefill);
                return new VerboseResult<>(currentTimeNanos, probe, configuration, state.copy());
            }
            state.consume(bandwidths, tokensToConsume);
            ConsumptionProbe probe = ConsumptionProbe.consumed(availableToConsume - tokensToConsume);
            return new VerboseResult<>(currentTimeNanos, probe, configuration, state.copy());
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected VerboseResult estimateAbilityToConsumeVerboseImpl(long tokensToEstimate) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableToConsume = state.getAvailableTokens(bandwidths);
            if (tokensToEstimate > availableToConsume) {
                long nanosToWaitForRefill = state.calculateDelayNanosAfterWillBePossibleToConsume(bandwidths, tokensToEstimate, currentTimeNanos);
                EstimationProbe estimationProbe = EstimationProbe.canNotBeConsumed(availableToConsume, nanosToWaitForRefill);
                return new VerboseResult<>(currentTimeNanos, estimationProbe, configuration, state.copy());
            }
            EstimationProbe estimationProbe = EstimationProbe.canBeConsumed(availableToConsume);
            return new VerboseResult<>(currentTimeNanos, estimationProbe, configuration, state.copy());
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected VerboseResult getAvailableTokensVerboseImpl() {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long availableTokens = state.getAvailableTokens(bandwidths);
            return new VerboseResult<>(currentTimeNanos, availableTokens, configuration, state.copy());
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected VerboseResult addTokensVerboseImpl(long tokensToAdd) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            state.addTokens(bandwidths, tokensToAdd);
            return new VerboseResult<>(currentTimeNanos, Nothing.INSTANCE, configuration, state.copy());
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected VerboseResult replaceConfigurationVerboseImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            this.state.refillAllBandwidth(bandwidths, currentTimeNanos);
            this.state = this.state.replaceConfiguration(this.configuration, newConfiguration, tokensInheritanceStrategy, currentTimeNanos);
            this.bandwidths = newConfiguration.getBandwidths();
            this.configuration = newConfiguration;
            return new VerboseResult<>(currentTimeNanos, null, configuration, state.copy());
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected VerboseResult consumeIgnoringRateLimitsVerboseImpl(long tokensToConsume) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            long nanosToCloseDeficit = state.calculateDelayNanosAfterWillBePossibleToConsume(bandwidths, tokensToConsume, currentTimeNanos);

            if (nanosToCloseDeficit == INFINITY_DURATION) {
                return new VerboseResult<>(currentTimeNanos, nanosToCloseDeficit, configuration, state.copy());
            }
            state.consume(bandwidths, tokensToConsume);
            return new VerboseResult<>(currentTimeNanos, nanosToCloseDeficit, configuration, state.copy());
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected void addTokensImpl(long tokensToAdd) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            state.addTokens(bandwidths, tokensToAdd);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public long getAvailableTokens() {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            state.refillAllBandwidth(bandwidths, currentTimeNanos);
            return state.getAvailableTokens(bandwidths);
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected void replaceConfigurationImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) {
        long currentTimeNanos = timeMeter.currentTimeNanos();
        lock.lock();
        try {
            this.state.refillAllBandwidth(bandwidths, currentTimeNanos);
            this.state = this.state.replaceConfiguration(this.configuration, newConfiguration, tokensInheritanceStrategy, currentTimeNanos);
            this.configuration = newConfiguration;
            this.bandwidths = newConfiguration.getBandwidths();
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected CompletableFuture tryConsumeAsyncImpl(long tokensToConsume) {
        boolean result = tryConsumeImpl(tokensToConsume);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture addTokensAsyncImpl(long tokensToAdd) {
        addTokensImpl(tokensToAdd);
        return CompletableFuture.completedFuture(null);
    }

    @Override
    protected CompletableFuture tryConsumeAndReturnRemainingTokensAsyncImpl(long tokensToConsume) {
        ConsumptionProbe result = tryConsumeAndReturnRemainingTokensImpl(tokensToConsume);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture estimateAbilityToConsumeAsyncImpl(long tokensToEstimate) {
        EstimationProbe result = estimateAbilityToConsumeImpl(tokensToEstimate);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture tryConsumeAsMuchAsPossibleAsyncImpl(long limit) {
        long result = consumeAsMuchAsPossibleImpl(limit);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture reserveAndCalculateTimeToSleepAsyncImpl(long tokensToConsume, long maxWaitTimeNanos) {
        long result = reserveAndCalculateTimeToSleepImpl(tokensToConsume, maxWaitTimeNanos);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture replaceConfigurationAsyncImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) {
        replaceConfigurationImpl(newConfiguration, tokensInheritanceStrategy);
        return CompletableFuture.completedFuture(Nothing.INSTANCE);
    }

    @Override
    protected CompletableFuture consumeIgnoringRateLimitsAsyncImpl(long tokensToConsume) {
        long result = consumeIgnoringRateLimitsImpl(tokensToConsume);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture> tryConsumeAsMuchAsPossibleVerboseAsyncImpl(long limit) {
        VerboseResult result = consumeAsMuchAsPossibleVerboseImpl(limit);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture> tryConsumeVerboseAsyncImpl(long tokensToConsume) {
        VerboseResult result = tryConsumeVerboseImpl(tokensToConsume);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture> tryConsumeAndReturnRemainingTokensVerboseAsyncImpl(long tokensToConsume) {
        VerboseResult result = tryConsumeAndReturnRemainingTokensVerboseImpl(tokensToConsume);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture> estimateAbilityToConsumeVerboseAsyncImpl(long tokensToEstimate) {
        VerboseResult result = estimateAbilityToConsumeVerboseImpl(tokensToEstimate);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture> addTokensVerboseAsyncImpl(long tokensToAdd) {
        VerboseResult result = addTokensVerboseImpl(tokensToAdd);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture> replaceConfigurationVerboseAsyncImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) {
        VerboseResult result = replaceConfigurationVerboseImpl(newConfiguration, tokensInheritanceStrategy);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    protected CompletableFuture> consumeIgnoringRateLimitsVerboseAsyncImpl(long tokensToConsume) {
        VerboseResult result = consumeIgnoringRateLimitsVerboseImpl(tokensToConsume);
        return CompletableFuture.completedFuture(result);
    }

    @Override
    public BucketState createSnapshot() {
        lock.lock();
        try {
            return state.copy();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public BucketConfiguration getConfiguration() {
        return configuration;
    }

    @Override
    public String toString() {
        synchronized (this) {
            return "SynchronizedBucket{" +
                "state=" + state +
                ", configuration=" + getConfiguration() +
                '}';
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy