Coverage Report

Created: 2026-07-31 19:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
common/cpp/token_bucket_rate_limiter.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
#include <bvar/bvar.h>
20
21
#include <atomic>
22
#include <functional>
23
#include <memory>
24
#include <shared_mutex>
25
#include <string>
26
#include <string_view>
27
28
namespace doris {
29
enum class S3RateLimitType : int {
30
    GET = 0,
31
    PUT,
32
    UNKNOWN,
33
};
34
35
extern std::string to_string(S3RateLimitType type);
36
extern S3RateLimitType string_to_s3_rate_limit_type(std::string_view value);
37
38
inline auto metric_func_factory(bvar::Adder<int64_t>& sleep_ns_bvar,
39
                                bvar::Adder<int64_t>& sleep_count_bvar,
40
0
                                bvar::Adder<int64_t>* rejected_count_bvar = nullptr) {
41
0
    return [&, rejected_count_bvar](int64_t ns) {
42
0
        if (ns > 0) {
43
0
            sleep_ns_bvar << ns;
44
0
            sleep_count_bvar << 1;
45
0
        } else if (ns < 0 && rejected_count_bvar != nullptr) {
46
0
            *rejected_count_bvar << 1;
47
0
        }
48
0
    };
49
0
}
50
51
class TokenBucketRateLimiter {
52
public:
53
    static constexpr size_t default_burst_seconds = 1;
54
55
    TokenBucketRateLimiter(size_t max_speed, size_t max_burst, size_t limit);
56
    ~TokenBucketRateLimiter();
57
58
    // Use `amount` remain_tokens and count, sleeping when rate tokens are insufficient.
59
    // Returns the sleep duration in nanoseconds, or -1 when the count limit rejects the add.
60
    int64_t add(size_t amount);
61
62
    // Return `amount` tokens to the bucket (capped at max_burst) and roll back the
63
    // cumulative counter. Used to reconcile a reservation with the actually consumed
64
    // amount, e.g. a short read at EOF.
65
    void refund(size_t amount);
66
67
907
    size_t get_max_speed() const { return _max_speed; }
68
69
688
    size_t get_max_burst() const { return _max_burst; }
70
71
680
    size_t get_limit() const { return _limit; }
72
73
private:
74
    std::pair<size_t, double> _update_remain_token(long now, size_t amount);
75
    size_t _count {0};
76
    const size_t _max_speed {0}; // in tokens per second. which indicates the QPS
77
    const size_t _max_burst {0}; // in tokens. which indicates the token bucket size
78
    const uint64_t _limit {0};   // 0 - not limited.
79
    class SimpleSpinLock;
80
    std::unique_ptr<SimpleSpinLock> _mutex;
81
    // Amount of remain_tokens available in token bucket. Updated in `add` method.
82
    double _remain_tokens {0};
83
    long _prev_ns_count {0}; // Previous `add` call time (in nanoseconds).
84
};
85
86
struct TokenBucketRateLimiterResult {
87
    int64_t sleep_duration;
88
    size_t max_speed;
89
    size_t max_burst;
90
    size_t limit;
91
};
92
93
class TokenBucketRateLimiterHolder {
94
public:
95
    TokenBucketRateLimiterHolder(size_t max_speed, size_t max_burst, size_t limit,
96
                                 std::function<void(int64_t)> metric_func);
97
    ~TokenBucketRateLimiterHolder();
98
99
    int64_t add(size_t amount);
100
    TokenBucketRateLimiterResult add_with_config(size_t amount);
101
102
    // Charge `amount` like add(), but return the limiter generation the tokens were
103
    // taken from, or nullptr when the count limit rejects the charge. Callers that later
104
    // refund a reservation must refund on the returned object, so that a concurrent
105
    // reset() cannot make the refund pollute a fresh bucket that never saw the charge.
106
    std::shared_ptr<TokenBucketRateLimiter> charge(size_t amount);
107
108
    int reset(size_t max_speed, size_t max_burst, size_t limit);
109
110
    // Whether the currently published limiter can throttle or reject at all
111
    // (max_speed > 0 or limit > 0). Lock-free fast path for callers that want to
112
    // skip disabled limiters.
113
0
    bool is_enabled() const { return _enabled.load(std::memory_order_acquire); }
114
115
    size_t get_max_speed() const;
116
    size_t get_max_burst() const;
117
    size_t get_limit() const;
118
119
private:
120
    mutable std::shared_mutex rate_limiter_rw_lock;
121
    std::shared_ptr<TokenBucketRateLimiter> rate_limiter;
122
    std::atomic<bool> _enabled;
123
    // Record the correspoding sleeping time(unit is ms)
124
    std::function<void(int64_t)> metric_func;
125
};
126
127
using S3RateLimiter = TokenBucketRateLimiter;
128
using S3RateLimiterHolder = TokenBucketRateLimiterHolder;
129
130
std::function<void(int64_t)> s3_rate_limiter_metric_func(S3RateLimitType type);
131
int64_t apply_s3_rate_limit(S3RateLimitType type, S3RateLimiterHolder* rate_limiter,
132
                            int64_t log_interval);
133
} // namespace doris