1 // Copyright 2022 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <grpc/support/port_platform.h>
16 
17 #include "src/core/lib/resource_quota/periodic_update.h"
18 
19 #include <atomic>
20 
21 #include "src/core/lib/gpr/useful.h"
22 
23 namespace grpc_core {
24 
MaybeEndPeriod(absl::FunctionRef<void (Duration)> f)25 bool PeriodicUpdate::MaybeEndPeriod(absl::FunctionRef<void(Duration)> f) {
26   if (period_start_ == Timestamp::ProcessEpoch()) {
27     period_start_ = Timestamp::Now();
28     updates_remaining_.store(1, std::memory_order_release);
29     return false;
30   }
31   // updates_remaining_ just reached 0 and the thread calling this function was
32   // the decrementer that got us there.
33   // We can now safely mutate any non-atomic mutable variables (we've got a
34   // guarantee that no other thread will), and by the time this function returns
35   // we must store a postive number into updates_remaining_.
36   auto now = Timestamp::Now();
37   Duration time_so_far = now - period_start_;
38   if (time_so_far < period_) {
39     // At most double the number of updates remaining until the next period.
40     // At least try to estimate when we'll reach it.
41     int64_t better_guess;
42     if (time_so_far.millis() == 0) {
43       better_guess = expected_updates_per_period_ * 2;
44     } else {
45       // Determine a scaling factor that would have gotten us to the next
46       // period, but clamp between 1.01 (at least 1% increase in guesses)
47       // and 2.0 (at most doubling) - to avoid running completely out of
48       // control.
49       const double scale =
50           Clamp(period_.seconds() / time_so_far.seconds(), 1.01, 2.0);
51       better_guess = expected_updates_per_period_ * scale;
52       if (better_guess <= expected_updates_per_period_) {
53         better_guess = expected_updates_per_period_ + 1;
54       }
55     }
56     // Store the remainder left. Note that updates_remaining_ may have been
57     // decremented by another thread whilst we performed the above calculations:
58     // we simply discard those decrements.
59     updates_remaining_.store(better_guess - expected_updates_per_period_,
60                              std::memory_order_release);
61     // Not quite done, return, try for longer.
62     return false;
63   }
64   // Finished period, start a new one and return true.
65   // We try to predict how many update periods we'd need to cover the full time
66   // span, and we increase that by 1% to attempt to tend to not enter the above
67   // stanza.
68   expected_updates_per_period_ =
69       period_.seconds() * expected_updates_per_period_ / time_so_far.seconds();
70   if (expected_updates_per_period_ < 1) expected_updates_per_period_ = 1;
71   period_start_ = now;
72   f(time_so_far);
73   updates_remaining_.store(expected_updates_per_period_,
74                            std::memory_order_release);
75   return true;
76 }
77 
78 }  // namespace grpc_core
79