Skip to content

Get id of Bandwidth which has zero tokens #185

Get id of Bandwidth which has zero tokens #185
Oct 20, 2021 · 3 answers · 5 replies

Hello.

I have a bucket with multiple limits.
Is it possible to get identifier of the Bandwidth which consumed all tokens?
f.ex.

Bucket bucket = Bucket4j.builder()
                                .addLimit(Bandwidth.simple(10, Duration.ofSeconds(1)).withId("technical-limit"))
                                .addLimit(Bandwidth.simple(10000, Duration.ofHours(1)).withId("business-limit"))
                                .build();

if (bucket.tryConsume(100)) {
            // the limit is not exceeded
            ...
} else {
            // limit is exceeded
            String bandwidthId = ???;
            if(bandwidthId == "technical-limit") {
                 // do something
            } else {
                // do something else
            }
}

Thank you.

@dlakhtyuk

Yes, it is possible. There is Verbose API for doing stuff like this. It is not well documented on the Github Readme Pages, but I believe that javadocs is good enough.

According to your particular task Verbose API can be used in following way:

        Bucket bucket = Bucket4j.builder()
                .addLimit(Bandwidth.simple(10, Duration.ofSeconds(1)).withId("technical-limit"))
                .addLimit(Bandwidth.simple(10000, Duration.ofHours(1)).withId("business-limit"))
                .build();
        ...
        int tokensToConsume = 100;
        VerboseResult<Boolean> verboseResult = bucket.asVerbose().tryConsume(tokensToConsume);
        boolean wasConsumed = verboseR…

Replies

3 suggested answers
·
5 replies

@dlakhtyuk

Yes, it is possible. There is Verbose API for doing stuff like this. It is not well documented on the Github Readme Pages, but I believe that javadocs is good enough.

According to your particular task Verbose API can be used in following way:

        Bucket bucket = Bucket4j.builder()
                .addLimit(Bandwidth.simple(10, Duration.ofSeconds(1)).withId("technical-limit"))
                .addLimit(Bandwidth.simple(10000, Duration.ofHours(1)).withId("business-limit"))
                .build();
        ...
        int tokensToConsume = 100;
        VerboseResult<Boolean> verboseResult = bucket.asVerbose().tryConsume(tokensToConsume);
        boolean wasConsumed = verboseResult.getValue();
        if (wasConsumed) {
            // the limit is not exceeded
            ...
        } else {
            // limit is exceeded
            Set<String> bandwidthIds = getBandwidthsThatHavingLessTokensThatRequired(verboseResult, tokensToConsume);

            String bandwidthId = ???;
            if( bandwidthIds.contains("technical-limit")) {
                // do something
            } else {
                // do something else
            }
        }
    }

    private static Set<String> getBandwidthsThatHavingLessTokensThatRequired(VerboseResult<Boolean> verboseResult, int requiredTokens) {
        BucketConfiguration configuration = verboseResult.getConfiguration();
        Bandwidth[] bandwidths = configuration.getBandwidths();
        BucketState state = verboseResult.getState();

        Set<String> bandwidthsWithTokensLessThanRequired = new HashSet<>();
        for (int i = 0; i < bandwidths.length; i++) {
            if (state.getCurrentSize(i) < requiredTokens) {
                bandwidthsWithTokensLessThanRequired.add(bandwidths[i].getId());
            }
        }

        return bandwidthsWithTokensLessThanRequired;
    }

As you can see VerboseResult is a full snapshot of the bucket that is taken immediately after your operation. Then taken snapshot can be investigated in way that you need.

1 reply
@dlakhtyuk

Hello @vladimir-bukhtoyarov

I've tried latest stable version 6.3.0, but did not find BucketState.getCurrentSize there,
seems this is new feature added in 7.0.0-beta.

Anyway, your suggestion looks like solution! Thank you.

Answer selected by dlakhtyuk

I've tried latest stable version 6.3.0, but did not find BucketState.getCurrentSize

It is there https://github.com/vladimir-bukhtoyarov/bucket4j/blob/6.3/bucket4j-core/src/main/java/io/github/bucket4j/BucketState.java#L481

Unfortunately, it has package visibility. When I was writing example in previous post I placed it in the io.github.bucket4j package. That it is why I did not detect the visibility problem. But it is not a big problem. You can move getBandwidthsThatHavingLessTokensThatRequired to the some utility class and put this class to the io.github.bucket4j package, it will be full enough to solve visibility problem. I will mark this method as public in the next release.

3 replies
@dlakhtyuk

That explains everything. Thanks!

@dlakhtyuk

Just a suggestion as improvement in the future.
The original issue is that tryConsume or tryConsumeAndReturnRemaining do not provide a reason why bucket failed to consume tokens.
Cast to asVerbose plus state.getCurrentSize may work, but on other hand it may lead to inconsistent behavior
f.ex.

        if (wasConsumed) {
            // the limit is not exceeded
            ...
        } else {
            // limit is exceeded
            
            // some delay for any reason
            Thread.sleep(100500 years);  
            ....
            Set<String> bandwidthIds = getBandwidthsThatHavingLessTokensThatRequired(verboseResult, tokensToConsume);
            ...
        }

In this case wasConsumed can be false while getBandwidthsThatHavingLessTokensThatRequired can return empty list because Bandwidth was refilled when current Thread was busy.

Thus, may be it make sense to add Bandwidth.id as part of tryConsumeAndReturnRemaining(or whatever method) as a reason of failed consumption.
btw, ConsumptionProbe.nanosToWaitForRefill is related to particular Bandwidth, right? May be ConsumptionProbe.bandwidthId is appropriate candidate.

@vladimir-bukhtoyarov

I will mark this method as public in the next release.

Done.

In this case wasConsumed can be false while getBandwidthsThatHavingLessTokensThatRequired can return empty list because Bandwidth was refilled when current Thread was busy.

Definitely no. If getCurrentSize behaved in the way that you described, snapshoting(as well as Verbose API) would not have sense.

Firstly, snapshot is decoupled from original bucket, any change in the bucket after acquiring of snapshot does not affects the snapshot, and wise versa.
Secondly, snapshot stays unchanged independently of time that past after snapshot acquiring, until you explicitly change snapshot through BucketState API. That means that refill is switched off for snapshot until you explicitly call a correspondent method on BucketState.
In third: getCurrentSize is read-only method that does not refill tokens before returning the current amount.

1 reply
@dlakhtyuk

I see, that's good I was wrong! :)
Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
2 participants