SERVER-104168 Vendor in folly token bucket util (#35481)

GitOrigin-RevId: 17e9908a92ffdecd26b671ce46bddbb2dc4d64bd
This commit is contained in:
Erin McNulty 2025-04-28 18:32:21 -04:00 committed by MongoDB Bot
parent 256b5a47e3
commit 27de69298a
9 changed files with 1322 additions and 0 deletions

1
.github/CODEOWNERS vendored
View File

@ -2813,6 +2813,7 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot
/src/third_party/**/cares @10gen/server-networking-and-observability @svc-auto-approve-bot /src/third_party/**/cares @10gen/server-networking-and-observability @svc-auto-approve-bot
/src/third_party/**/croaring @10gen/query-execution @svc-auto-approve-bot /src/third_party/**/croaring @10gen/query-execution @svc-auto-approve-bot
/src/third_party/**/fmt @10gen/server-programmability @svc-auto-approve-bot /src/third_party/**/fmt @10gen/server-programmability @svc-auto-approve-bot
/src/third_party/**/folly @10gen/server-workload-scheduling @svc-auto-approve-bot
/src/third_party/**/gperftools @10gen/server-workload-scheduling @svc-auto-approve-bot /src/third_party/**/gperftools @10gen/server-workload-scheduling @svc-auto-approve-bot
/src/third_party/**/grpc @10gen/server-networking-and-observability @svc-auto-approve-bot /src/third_party/**/grpc @10gen/server-networking-and-observability @svc-auto-approve-bot
/src/third_party/**/icu4c* @10gen/query-execution @svc-auto-approve-bot /src/third_party/**/icu4c* @10gen/query-execution @svc-auto-approve-bot

View File

@ -542,6 +542,49 @@
}, },
"scope": "required" "scope": "required"
}, },
{
"supplier": {
"name": "Organization: github"
},
"name": "folly",
"version": "v2025.04.21.00",
"licenses": [
{
"license": {
"id": "Apache-2.0"
}
}
],
"purl": "pkg:github/folly/folly@v2025.04.21.00",
"properties": [
{
"name": "internal:team_responsible",
"value": "Workload Scheduling"
},
{
"name": "emits_persisted_data",
"value": "false"
},
{
"name": "info_link",
"value": "https://github.com/facebook/folly"
},
{
"name": "import_script_path",
"value": "src/third_party/folly/scripts/import.sh"
}
],
"type": "library",
"bom-ref": "b2fa8868-e134-4fcb-9641-6344ecfef39e",
"evidence": {
"occurrences": [
{
"location": "src/third_party/folly"
}
]
},
"scope": "required"
},
{ {
"supplier": { "supplier": {
"name": "Organization: github" "name": "Organization: github"

View File

@ -24,6 +24,9 @@ filters:
- "fmt": - "fmt":
approvers: approvers:
- 10gen/server-programmability - 10gen/server-programmability
- "folly":
approvers:
- 10gen/server-workload-scheduling
- "gperftools": - "gperftools":
approvers: approvers:
- 10gen/server-workload-scheduling - 10gen/server-workload-scheduling

26
src/third_party/folly/BUILD.bazel vendored Normal file
View File

@ -0,0 +1,26 @@
load("//bazel:mongo_src_rules.bzl", "mongo_cc_library", "mongo_cc_unit_test")
package(default_visibility = ["//visibility:public"])
mongo_cc_library(
name = "folly_token_bucket",
hdrs = [
"dist/folly/TokenBucket.h",
],
includes = ["./dist"],
)
mongo_cc_unit_test(
name = "token_bucket_test",
srcs = [
"test/token_bucket_test.cpp",
],
tags = [
"mongo_unittest_fifth_group",
"server-workload-scheduling",
],
deps = [
":folly_token_bucket",
"//src/mongo:base",
],
)

200
src/third_party/folly/dist/LICENSE vendored Normal file
View File

@ -0,0 +1,200 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Files in folly/external/farmhash licensed as follows
Copyright (c) 2014 Google, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

259
src/third_party/folly/dist/README.md vendored Normal file
View File

@ -0,0 +1,259 @@
Folly: Facebook Open-source Library
===================================
<a href="https://opensource.facebook.com/support-ukraine">
<img src="https://img.shields.io/badge/Support-Ukraine-FFD500?style=flat&labelColor=005BBB" alt="Support Ukraine - Help Provide Humanitarian Aid to Ukraine." />
</a>
# What is `folly`?
<img src="static/logo.svg" alt="Logo Folly" width="15%" align="right" />
Folly (acronymed loosely after Facebook Open Source Library) is a
library of C++17 components designed with practicality and efficiency
in mind. **Folly contains a variety of core library components used extensively
at Facebook**. In particular, it's often a dependency of Facebook's other
open source C++ efforts and place where those projects can share code.
It complements (as opposed to competing against) offerings
such as Boost and of course `std`. In fact, we embark on defining our
own component only when something we need is either not available, or
does not meet the needed performance profile. We endeavor to remove
things from folly if or when `std` or Boost obsoletes them.
Performance concerns permeate much of Folly, sometimes leading to
designs that are more idiosyncratic than they would otherwise be (see
e.g. `PackedSyncPtr.h`, `SmallLocks.h`). Good performance at large
scale is a unifying theme in all of Folly.
## Check it out in the intro video
[![Explain Like Im 5: Folly](https://img.youtube.com/vi/Wr_IfOICYSs/0.jpg)](https://www.youtube.com/watch?v=Wr_IfOICYSs)
# Logical Design
Folly is a collection of relatively independent components, some as
simple as a few symbols. There is no restriction on internal
dependencies, meaning that a given folly module may use any other
folly components.
All symbols are defined in the top-level namespace `folly`, except of
course macros. Macro names are ALL_UPPERCASE and should be prefixed
with `FOLLY_`. Namespace `folly` defines other internal namespaces
such as `internal` or `detail`. User code should not depend on symbols
in those namespaces.
# Physical Design
At the top level Folly uses the classic "stuttering" scheme
`folly/folly` used by Boost and others. The first directory serves as
an installation root of the library (with possible versioning a la
`folly-1.0/`), and the second is to distinguish the library when
including files, e.g. `#include <folly/FBString.h>`.
The directory structure is flat (mimicking the namespace structure),
i.e. we don't have an elaborate directory hierarchy (it is possible
this will change in future versions). The subdirectory `experimental`
contains files that are used inside folly and possibly at Facebook but
not considered stable enough for client use. Your code should not use
files in `folly/experimental` lest it may break when you update Folly.
The `folly/folly/test` subdirectory includes the unittests for all
components, usually named `ComponentXyzTest.cpp` for each
`ComponentXyz.*`. The `folly/folly/docs` directory contains
documentation.
# What's in it?
Because of folly's fairly flat structure, the best way to see what's in it
is to look at the headers in [top level `folly/` directory](https://github.com/facebook/folly/tree/main/folly). You can also
check the [`docs` folder](folly/docs) for documentation, starting with the
[overview](folly/docs/Overview.md).
Folly is published on GitHub at https://github.com/facebook/folly.
# Build Notes
Because folly does not provide any ABI compatibility guarantees from commit to
commit, we generally recommend building folly as a static library.
folly supports gcc (5.1+), clang, or MSVC. It should run on Linux (x86-32,
x86-64, and ARM), iOS, macOS, and Windows (x86-64). The CMake build is only
tested on some of these platforms; at a minimum, we aim to support macOS and
Linux (on the latest Ubuntu LTS release or newer.)
## `getdeps.py`
This script is used by many of Meta's OSS tools. It will download and build all of the necessary dependencies first, and will then invoke cmake etc to build folly. This will help ensure that you build with relevant versions of all of the dependent libraries, taking into account what versions are installed locally on your system.
It's written in python so you'll need python3.6 or later on your PATH. It works on Linux, macOS and Windows.
The settings for folly's cmake build are held in its getdeps manifest `build/fbcode_builder/manifests/folly`, which you can edit locally if desired.
### Dependencies
If on Linux or MacOS (with homebrew installed) you can install system dependencies to save building them:
# Clone the repo
git clone https://github.com/facebook/folly
# Install dependencies
cd folly
sudo ./build/fbcode_builder/getdeps.py install-system-deps --recursive
If you'd like to see the packages before installing them:
./build/fbcode_builder/getdeps.py install-system-deps --dry-run --recursive
On other platforms or if on Linux and without system dependencies `getdeps.py` will mostly download and build them for you during the build step.
Some of the dependencies `getdeps.py` uses and installs are:
* a version of boost compiled with C++14 support.
* googletest is required to build and run folly's tests.
### Build
This script will download and build all of the necessary dependencies first,
and will then invoke cmake etc to build folly. This will help ensure that you build with relevant versions of all of the dependent libraries, taking into account what versions are installed locally on your system.
`getdeps.py` currently requires python 3.6+ to be on your path.
`getdeps.py` will invoke cmake etc.
# Clone the repo
git clone https://github.com/facebook/folly
cd folly
# Build, using system dependencies if available
python3 ./build/fbcode_builder/getdeps.py --allow-system-packages build
It puts output in its scratch area:
* `installed/folly/lib/libfolly.a`: Library
You can also specify a `--scratch-path` argument to control
the location of the scratch directory used for the build. You can find the default scratch install location from logs or with `python3 ./build/fbcode_builder/getdeps.py show-inst-dir`.
There are also
`--install-dir` and `--install-prefix` arguments to provide some more
fine-grained control of the installation directories. However, given that
folly provides no compatibility guarantees between commits we generally
recommend building and installing the libraries to a temporary location, and
then pointing your project's build at this temporary location, rather than
installing folly in the traditional system installation directories. e.g., if you are building with CMake you can use the `CMAKE_PREFIX_PATH` variable to allow CMake to find folly in this temporary installation directory when
building your project.
If you want to invoke `cmake` again to iterate, there is a helpful `run_cmake.py` script output in the scratch build directory. You can find the scratch build directory from logs or with `python3 ./build/fbcode_builder/getdeps.py show-build-dir`.
### Run tests
By default `getdeps.py` will build the tests for folly. To run them:
cd folly
python3 ./build/fbcode_builder/getdeps.py --allow-system-packages test
### `build.sh`/`build.bat` wrapper
`build.sh` can be used on Linux and MacOS, on Windows use
the `build.bat` script instead. Its a wrapper around `getdeps.py`.
## Build with cmake directly
If you don't want to let getdeps invoke cmake for you then by default, building the tests is disabled as part of the CMake `all` target.
To build the tests, specify `-DBUILD_TESTS=ON` to CMake at configure time.
NB if you want to invoke `cmake` again to iterate on a `getdeps.py` build, there is a helpful `run_cmake.py` script output in the scratch-path build directory. You can find the scratch build directory from logs or with `python3 ./build/fbcode_builder/getdeps.py show-build-dir`.
Running tests with ctests also works if you cd to the build dir, e.g.
`(cd $(python3 ./build/fbcode_builder/getdeps.py show-build-dir) && ctest)`
### Finding dependencies in non-default locations
If you have boost, gtest, or other dependencies installed in a non-default
location, you can use the `CMAKE_INCLUDE_PATH` and `CMAKE_LIBRARY_PATH`
variables to make CMAKE look also look for header files and libraries in
non-standard locations. For example, to also search the directories
`/alt/include/path1` and `/alt/include/path2` for header files and the
directories `/alt/lib/path1` and `/alt/lib/path2` for libraries, you can invoke
`cmake` as follows:
```
cmake \
-DCMAKE_INCLUDE_PATH=/alt/include/path1:/alt/include/path2 \
-DCMAKE_LIBRARY_PATH=/alt/lib/path1:/alt/lib/path2 ...
```
## Ubuntu LTS, CentOS Stream, Fedora
Use the `getdeps.py` approach above. We test in CI on Ubuntu LTS, and occasionally on other distros.
If you find the set of system packages is not quite right for your chosen distro, you can specify distro version specific overrides in the dependency manifests (e.g. https://github.com/facebook/folly/blob/main/build/fbcode_builder/manifests/boost ). You could probably make it work on most recent Ubuntu/Debian or Fedora/Redhat derived distributions.
At time of writing (Dec 2021) there is a build break on GCC 11.x based systems in lang_badge_test. If you don't need badge functionality you can work around by commenting it out from CMakeLists.txt (unfortunately fbthrift does need it)
## Windows (Vcpkg)
Note that many tests are disabled for folly Windows builds, you can see them in the log from the cmake configure step, or by looking for WINDOWS_DISABLED in `CMakeLists.txt`
That said, `getdeps.py` builds work on Windows and are tested in CI.
If you prefer, you can try Vcpkg. folly is available in [Vcpkg](https://github.com/Microsoft/vcpkg#vcpkg) and releases may be built via `vcpkg install folly:x64-windows`.
You may also use `vcpkg install folly:x64-windows --head` to build against `main`.
## macOS
`getdeps.py` builds work on macOS and are tested in CI, however if you prefer, you can try one of the macOS package managers
### Homebrew
folly is available as a Formula and releases may be built via `brew install folly`.
You may also use `folly/build/bootstrap-osx-homebrew.sh` to build against `main`:
```
./folly/build/bootstrap-osx-homebrew.sh
```
This will create a build directory `_build` in the top-level.
### MacPorts
Install the required packages from MacPorts:
```
sudo port install \
boost \
cmake \
gflags \
git \
google-glog \
libevent \
libtool \
lz4 \
lzma \
openssl \
snappy \
xz \
zlib
```
Download and install double-conversion:
```
git clone https://github.com/google/double-conversion.git
cd double-conversion
cmake -DBUILD_SHARED_LIBS=ON .
make
sudo make install
```
Download and install folly with the parameters listed below:
```
git clone https://github.com/facebook/folly.git
cd folly
mkdir _build
cd _build
cmake ..
make
sudo make install
```

View File

@ -0,0 +1,705 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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.
*/
/**
* Notice: this code has been modified from the folly source code to be compatible with MongoDB utilities.
*/
#pragma once
#include <algorithm>
#include <atomic>
#include <chrono>
#include <thread>
#include <boost/optional.hpp>
#include "mongo/platform/compiler.h"
#include "mongo/stdx/new.h"
#include "mongo/util/assert_util.h"
namespace folly {
/**
* constexpr_min and constexpr_max are pasted here from folly/ConstexprMath.h to avoid adding more
* folly dependencies.
*/
// When a and b are equivalent objects, we return a to make sorting stable.
template <typename T, typename... Ts>
constexpr T constexpr_min(T a, Ts... ts) {
T list[] = {ts..., a}; // 0-length arrays are illegal
for (auto i = 0u; i < sizeof...(Ts); ++i) {
a = list[i] < a ? list[i] : a;
}
return a;
}
// TLDR: Prefer using operator< for ordering. And when a and b are equivalent objects, we return b
// to make sorting stable.
// See http://stepanovpapers.com/notes.pdf for details.
template <typename T, typename... Ts>
constexpr T constexpr_max(T a, Ts... ts) {
T list[] = {ts..., a}; // 0-length arrays are illegal
for (auto i = 0u; i < sizeof...(Ts); ++i) {
a = list[i] < a ? a : list[i];
}
return a;
}
struct TokenBucketPolicyDefault {
using align =
std::integral_constant<size_t, mongo::stdx::hardware_destructive_interference_size>;
template <typename T>
using atom = std::atomic<T>;
using clock = std::chrono::steady_clock;
using concurrent = std::true_type;
};
/**
* Thread-safe (atomic) token bucket primitive.
*
* This primitive can be used to implement a token bucket
* (http://en.wikipedia.org/wiki/Token_bucket). It handles
* the storage of the state in an atomic way, and presents
* an interface dealing with tokens, rate, burstSize and time.
*
* This primitive records the last time it was updated. This allows the
* token bucket to add tokens "just in time" when tokens are requested.
*
* @tparam Policy A policy.
*/
template <typename Policy = TokenBucketPolicyDefault>
class TokenBucketStorage {
template <typename T>
using Atom = typename Policy::template atom<T>;
using Align = typename Policy::align;
using Clock = typename Policy::clock; // do we need clock here?
using Concurrent = typename Policy::concurrent;
static_assert(Clock::is_steady, "clock must be steady"); // do we need clock?
public:
/**
* Constructor.
*
* @param zeroTime Initial time at which to consider the token bucket
* starting to fill. Defaults to 0, so by default token
* buckets are "empty" after construction.
*/
explicit TokenBucketStorage(double zeroTime = 0) noexcept
: zeroTime_(zeroTime) {}
/**
* Copy constructor.
*
* Thread-safe. (Copy constructors of derived classes may not be thread-safe
* however.)
*/
TokenBucketStorage(const TokenBucketStorage& other) noexcept
: zeroTime_(other.zeroTime_.load(std::memory_order_relaxed)) {}
/**
* Copy-assignment operator.
*
* Warning: not thread safe for the object being assigned to (including
* self-assignment). Thread-safe for the other object.
*/
TokenBucketStorage& operator=(const TokenBucketStorage& other) noexcept {
zeroTime_.store(other.zeroTime(), std::memory_order_relaxed);
return *this;
}
/**
* Re-initialize token bucket.
*
* Thread-safe.
*
* @param zeroTime Initial time at which to consider the token bucket
* starting to fill. Defaults to 0, so by default token
* bucket is reset to "empty".
*/
void reset(double zeroTime = 0) noexcept {
zeroTime_.store(zeroTime, std::memory_order_relaxed);
}
/**
* Returns the token balance at specified time (negative if bucket in debt).
*
* Thread-safe (but returned value may immediately be outdated).
*/
double balance(
double rate, double burstSize, double nowInSeconds) const noexcept {
invariant(rate > 0);
invariant(burstSize > 0);
double zt = this->zeroTime_.load(std::memory_order_relaxed);
return std::min((nowInSeconds - zt) * rate, burstSize);
}
/**
* Consume tokens at the given rate/burst/time.
*
* Consumption is actually done by the callback function: it's given a
* reference with the number of available tokens and returns the number
* consumed. Typically the return value would be between 0.0 and available,
* but there are no restrictions.
*
* Note: the callback may be called multiple times, so please no side-effects
*/
template <typename Callback>
double consume(
double rate,
double burstSize,
double nowInSeconds,
const Callback& callback) {
invariant(rate > 0);
invariant(burstSize > 0);
double zeroTimeOld;
double zeroTimeNew;
double consumed;
do {
zeroTimeOld = zeroTime();
double tokens = std::min((nowInSeconds - zeroTimeOld) * rate, burstSize);
consumed = callback(tokens);
double tokensNew = tokens - consumed;
if (consumed == 0.0) {
return consumed;
}
zeroTimeNew = nowInSeconds - tokensNew / rate;
} while (MONGO_unlikely(
!compare_exchange_weak_relaxed(zeroTime_, zeroTimeOld, zeroTimeNew)));
return consumed;
}
/**
* returns the time at which the bucket will have `target` tokens available.
*
* Caution: it doesn't make sense to ask about target > burstSize
*
* Eg.
* // time debt repaid
* bucket.timeWhenBucket(rate, 0);
*
* // time bucket is full
* bucket.timeWhenBucket(rate, burstSize);
*/
double timeWhenBucket(double rate, double target) {
return zeroTime() + target / rate;
}
/**
* Return extra tokens back to the bucket.
*
* Thread-safe.
*/
void returnTokens(double tokensToReturn, double rate) {
invariant(rate > 0);
returnTokensImpl(tokensToReturn, rate);
}
private:
/**
* Adjust zeroTime based on rate and tokenCount and return the new value of
* zeroTime_. Note: Token count can be negative to move the zeroTime_
* into the future.
*/
double returnTokensImpl(double tokenCount, double rate) {
auto zeroTimeOld = zeroTime_.load(std::memory_order_relaxed);
double zeroTimeNew;
do {
zeroTimeNew = zeroTimeOld - tokenCount / rate;
} while (MONGO_unlikely(
!compare_exchange_weak_relaxed(zeroTime_, zeroTimeOld, zeroTimeNew)));
return zeroTimeNew;
}
static bool compare_exchange_weak_relaxed(
Atom<double>& atom, double& expected, double zeroTime) {
if (Concurrent::value) {
return atom.compare_exchange_weak(
expected, zeroTime, std::memory_order_relaxed);
} else {
return atom.store(zeroTime, std::memory_order_relaxed), true;
}
}
double zeroTime() const {
return this->zeroTime_.load(std::memory_order_relaxed);
}
static constexpr size_t AlignZeroTime =
constexpr_max(Align::value, alignof(Atom<double>));
alignas(AlignZeroTime) Atom<double> zeroTime_;
};
/**
* Thread-safe (atomic) token bucket implementation.
*
* A token bucket (http://en.wikipedia.org/wiki/Token_bucket) models a stream
* of events with an average rate and some amount of burstiness. The canonical
* example is a packet switched network: the network can accept some number of
* bytes per second and the bytes come in finite packets (bursts). A token
* bucket stores up to a fixed number of tokens (the burst size). Some number
* of tokens are removed when an event occurs. The tokens are replenished at a
* fixed rate. Failure to allocate tokens implies resource is unavailable and
* caller needs to implement its own retry mechanism. For simple cases where
* caller is okay with a FIFO starvation-free scheduling behavior, there are
* also APIs to 'borrow' from the future effectively assigning a start time to
* the caller when it should proceed with using the resource. It is also
* possible to 'return' previously allocated tokens to make them available to
* other users. Returns in excess of burstSize are considered expired and
* will not be available to later callers.
*
* This implementation records the last time it was updated. This allows the
* token bucket to add tokens "just in time" when tokens are requested.
*
* The "dynamic" base variant allows the token generation rate and maximum
* burst size to change with every token consumption.
*
* @tparam Policy A policy.
*/
template <typename Policy = TokenBucketPolicyDefault>
class BasicDynamicTokenBucket {
template <typename T>
using Atom = typename Policy::template atom<T>;
using Align = typename Policy::align;
using Clock = typename Policy::clock;
using Concurrent = typename Policy::concurrent;
static_assert(Clock::is_steady, "clock must be steady");
public:
/**
* Constructor.
*
* @param zeroTime Initial time at which to consider the token bucket
* starting to fill. Defaults to 0, so by default token
* buckets are "empty" after construction.
*/
explicit BasicDynamicTokenBucket(double zeroTime = 0) noexcept
: bucket_(zeroTime) {}
/**
* Copy constructor and copy assignment operator.
*
* Thread-safe. (Copy constructors of derived classes may not be thread-safe
* however.)
*/
BasicDynamicTokenBucket(const BasicDynamicTokenBucket& other) noexcept =
default;
BasicDynamicTokenBucket& operator=(
const BasicDynamicTokenBucket& other) noexcept = default;
/**
* Re-initialize token bucket.
*
* Thread-safe.
*
* @param zeroTime Initial time at which to consider the token bucket
* starting to fill. Defaults to 0, so by default token
* bucket is reset to "empty".
*/
void reset(double zeroTime = 0) noexcept { bucket_.reset(zeroTime); }
/**
* Returns the current time in seconds since Epoch.
*/
static double defaultClockNow() noexcept {
auto const now = Clock::now().time_since_epoch();
return std::chrono::duration<double>(now).count();
}
/**
* Attempts to consume some number of tokens. Tokens are first added to the
* bucket based on the time elapsed since the last attempt to consume tokens.
* Note: Attempts to consume more tokens than the burst size will always
* fail.
*
* Thread-safe.
*
* @param toConsume The number of tokens to consume.
* @param rate Number of tokens to generate per second.
* @param burstSize Maximum burst size. Must be greater than 0.
* @param nowInSeconds Current time in seconds. Should be monotonically
* increasing from the nowInSeconds specified in
* this token bucket's constructor.
* @return True if the rate limit check passed, false otherwise.
*/
bool consume(
double toConsume,
double rate,
double burstSize,
double nowInSeconds = defaultClockNow()) {
invariant(rate > 0);
invariant(burstSize > 0);
if (bucket_.balance(rate, burstSize, nowInSeconds) < 0.0) {
return 0;
}
double consumed = bucket_.consume(
rate, burstSize, nowInSeconds, [toConsume](double available) {
return available < toConsume ? 0.0 : toConsume;
});
invariant(consumed == toConsume || consumed == 0.0);
return consumed == toConsume;
}
/**
* Similar to consume, but always consumes some number of tokens. If the
* bucket contains enough tokens - consumes toConsume tokens. Otherwise the
* bucket is drained.
*
* Thread-safe.
*
* @param toConsume The number of tokens to consume.
* @param rate Number of tokens to generate per second.
* @param burstSize Maximum burst size. Must be greater than 0.
* @param nowInSeconds Current time in seconds. Should be monotonically
* increasing from the nowInSeconds specified in
* this token bucket's constructor.
* @return number of tokens that were consumed.
*/
double consumeOrDrain(
double toConsume,
double rate,
double burstSize,
double nowInSeconds = defaultClockNow()) {
invariant(rate > 0);
invariant(burstSize > 0);
if (bucket_.balance(rate, burstSize, nowInSeconds) <= 0.0) {
return 0;
}
double consumed = bucket_.consume(
rate, burstSize, nowInSeconds, [toConsume](double available) {
return constexpr_min(available, toConsume);
});
return consumed;
}
/**
* Return extra tokens back to the bucket.
*
* Thread-safe.
*/
void returnTokens(double tokensToReturn, double rate) {
invariant(rate > 0);
invariant(tokensToReturn > 0);
bucket_.returnTokens(tokensToReturn, rate);
}
/**
* Like consumeOrDrain but the call will always satisfy the asked for count.
* It does so by borrowing tokens from the future if the currently available
* count isn't sufficient.
*
* Returns a folly::Optional<double>. The optional wont be set if the request
* cannot be satisfied: only case is when it is larger than burstSize. The
* value of the optional is a double indicating the time in seconds that the
* caller needs to wait at which the reservation becomes valid. The caller
* could simply sleep for the returned duration to smooth out the allocation
* to match the rate limiter or do some other computation in the meantime. In
* any case, any regular consume or consumeOrDrain calls will fail to allocate
* any tokens until the future time is reached.
*
* Note: It is assumed the caller will not ask for a very large count nor use
* it immediately (if not waiting inline) as that would break the burst
* prevention the limiter is meant to be used for.
*
* Thread-safe.
*/
boost::optional<double> consumeWithBorrowNonBlocking(
double toConsume,
double rate,
double burstSize,
double nowInSeconds = defaultClockNow()) {
invariant(rate > 0);
invariant(burstSize > 0);
if (burstSize < toConsume) {
return boost::none;
}
while (toConsume > 0) {
double consumed =
consumeOrDrain(toConsume, rate, burstSize, nowInSeconds);
if (consumed > 0) {
toConsume -= consumed;
} else {
bucket_.returnTokens(-toConsume, rate);
double debtPaid = bucket_.timeWhenBucket(rate, 0);
double napTime = std::max(0.0, debtPaid - nowInSeconds);
return napTime;
}
}
return 0;
}
/**
* Convenience wrapper around non-blocking borrow to sleep inline until
* reservation is valid.
*/
bool consumeWithBorrowAndWait(
double toConsume,
double rate,
double burstSize,
double nowInSeconds = defaultClockNow()) {
auto res =
consumeWithBorrowNonBlocking(toConsume, rate, burstSize, nowInSeconds);
if (res.value_or(0) > 0) {
const auto napUSec = static_cast<int64_t>(res.value() * 1000000);
std::this_thread::sleep_for(std::chrono::microseconds(napUSec));
}
return res.has_value();
}
/**
* Returns the tokens available at specified time (zero if in debt).
*
* Use balance() to get the balance of tokens.
*
* Thread-safe (but returned value may immediately be outdated).
*/
double available(
double rate,
double burstSize,
double nowInSeconds = defaultClockNow()) const noexcept {
return std::max(0.0, balance(rate, burstSize, nowInSeconds));
}
/**
* Returns the token balance at specified time (negative if bucket in debt).
*
* Thread-safe (but returned value may immediately be outdated).
*/
double balance(
double rate,
double burstSize,
double nowInSeconds = defaultClockNow()) const noexcept {
return bucket_.balance(rate, burstSize, nowInSeconds);
}
private:
TokenBucketStorage<Policy> bucket_;
};
/**
* Specialization of BasicDynamicTokenBucket with a fixed token
* generation rate and a fixed maximum burst size.
*/
template <typename Policy = TokenBucketPolicyDefault>
class BasicTokenBucket {
private:
using Impl = BasicDynamicTokenBucket<Policy>;
public:
/**
* Construct a token bucket with a specific maximum rate and burst size.
*
* @param genRate Number of tokens to generate per second.
* @param burstSize Maximum burst size. Must be greater than 0.
* @param zeroTime Initial time at which to consider the token bucket
* starting to fill. Defaults to 0, so by default token
* bucket is "empty" after construction.
*/
BasicTokenBucket(
double genRate, double burstSize, double zeroTime = 0) noexcept
: tokenBucket_(zeroTime), rate_(genRate), burstSize_(burstSize) {
invariant(rate_ > 0);
invariant(burstSize_ > 0);
}
/**
* Copy constructor.
*
* Warning: not thread safe!
*/
BasicTokenBucket(const BasicTokenBucket& other) noexcept = default;
/**
* Copy-assignment operator.
*
* Warning: not thread safe!
*/
BasicTokenBucket& operator=(const BasicTokenBucket& other) noexcept = default;
/**
* Returns the current time in seconds since Epoch.
*/
static double defaultClockNow() noexcept(noexcept(Impl::defaultClockNow())) {
return Impl::defaultClockNow();
}
/**
* Change rate and burst size.
*
* Warning: not thread safe!
*
* @param genRate Number of tokens to generate per second.
* @param burstSize Maximum burst size. Must be greater than 0.
* @param nowInSeconds Current time in seconds. Should be monotonically
* increasing from the nowInSeconds specified in
* this token bucket's constructor.
*/
void reset(
double genRate,
double burstSize,
double nowInSeconds = defaultClockNow()) noexcept {
invariant(genRate > 0);
invariant(burstSize > 0);
const double availTokens = available(nowInSeconds);
rate_ = genRate;
burstSize_ = burstSize;
setCapacity(availTokens, nowInSeconds);
}
/**
* Change number of tokens in bucket.
*
* Warning: not thread safe!
*
* @param tokens Desired number of tokens in bucket after the call.
* @param nowInSeconds Current time in seconds. Should be monotonically
* increasing from the nowInSeconds specified in
* this token bucket's constructor.
*/
void setCapacity(double tokens, double nowInSeconds) noexcept {
tokenBucket_.reset(nowInSeconds - tokens / rate_);
}
/**
* Attempts to consume some number of tokens. Tokens are first added to the
* bucket based on the time elapsed since the last attempt to consume tokens.
* Note: Attempts to consume more tokens than the burst size will always
* fail.
*
* Thread-safe.
*
* @param toConsume The number of tokens to consume.
* @param nowInSeconds Current time in seconds. Should be monotonically
* increasing from the nowInSeconds specified in
* this token bucket's constructor.
* @return True if the rate limit check passed, false otherwise.
*/
bool consume(double toConsume, double nowInSeconds = defaultClockNow()) {
return tokenBucket_.consume(toConsume, rate_, burstSize_, nowInSeconds);
}
/**
* Similar to consume, but always consumes some number of tokens. If the
* bucket contains enough tokens - consumes toConsume tokens. Otherwise the
* bucket is drained.
*
* Thread-safe.
*
* @param toConsume The number of tokens to consume.
* @param nowInSeconds Current time in seconds. Should be monotonically
* increasing from the nowInSeconds specified in
* this token bucket's constructor.
* @return number of tokens that were consumed.
*/
double consumeOrDrain(
double toConsume, double nowInSeconds = defaultClockNow()) {
return tokenBucket_.consumeOrDrain(
toConsume, rate_, burstSize_, nowInSeconds);
}
/**
* Returns extra token back to the bucket. Cannot be negative.
* For negative tokens, setCapacity() can be used
*/
void returnTokens(double tokensToReturn) {
return tokenBucket_.returnTokens(tokensToReturn, rate_);
}
/**
* Reserve tokens and return time to wait for in order for the reservation to
* be compatible with the bucket configuration.
*/
boost::optional<double> consumeWithBorrowNonBlocking(
double toConsume, double nowInSeconds = defaultClockNow()) {
return tokenBucket_.consumeWithBorrowNonBlocking(
toConsume, rate_, burstSize_, nowInSeconds);
}
/**
* Reserve tokens. Blocks if need be until reservation is satisfied.
*/
bool consumeWithBorrowAndWait(
double toConsume, double nowInSeconds = defaultClockNow()) {
return tokenBucket_.consumeWithBorrowAndWait(
toConsume, rate_, burstSize_, nowInSeconds);
}
/**
* Returns the tokens available at specified time (zero if in debt).
*
* Use balance() to get the balance of tokens.
*
* Thread-safe (but returned value may immediately be outdated).
*/
double available(double nowInSeconds = defaultClockNow()) const noexcept {
return std::max(0.0, balance(nowInSeconds));
}
/**
* Returns the token balance at specified time (negative if bucket in debt).
*
* Thread-safe (but returned value may immediately be outdated).
*/
double balance(double nowInSeconds = defaultClockNow()) const noexcept {
return tokenBucket_.balance(rate_, burstSize_, nowInSeconds);
}
/**
* Returns the number of tokens generated per second.
*
* Thread-safe (but returned value may immediately be outdated).
*/
double rate() const noexcept { return rate_; }
/**
* Returns the maximum burst size.
*
* Thread-safe (but returned value may immediately be outdated).
*/
double burst() const noexcept { return burstSize_; }
private:
Impl tokenBucket_;
double rate_;
double burstSize_;
};
using TokenBucket = BasicTokenBucket<>;
using DynamicTokenBucket = BasicDynamicTokenBucket<>;
} // namespace folly

39
src/third_party/folly/scripts/import.sh vendored Normal file
View File

@ -0,0 +1,39 @@
#!/bin/bash
# This script clones the specified version of folly and strips out everything we do not need.
# Right now, we only use the TokenBucket utility from folly, and so nearly everthing else is removed.
set -o verbose
set -o errexit
set -euo pipefail
IFS=$'\n\t'
set -vx
NAME=folly
VERSION="v2025.04.21.00"
REVISION="${VERSION}-mongo"
# get the source
DEST_DIR=$(git rev-parse --show-toplevel)/src/third_party/folly
if [[ -d $DEST_DIR/dist ]]; then
echo "You must remove '$DEST_DIR/dist' before running $0" >&2
exit 1
fi
git clone --depth 1 --branch $REVISION git@github.com:mongodb-forks/folly.git $DEST_DIR/dist
pushd $DEST_DIR/dist
find . -mindepth 1 -maxdepth 1 -name ".*" -exec rm -rf {} \;
find . -mindepth 1 -maxdepth 1 -name "*build*" -exec rm -rf {} \;
find . -mindepth 1 -maxdepth 1 -name "*CMake*" -exec rm -rf {} \;
find . -mindepth 1 -maxdepth 1 -iname "buck*" -exec rm -rf {} \;
rm "CODE_OF_CONDUCT.md"
rm "CONTRIBUTING.md"
rm PACKAGE
rm -rf static
popd
pushd $DEST_DIR/dist/folly
rm -R -- */
find . ! -name 'TokenBucket.h' -type f -exec rm -rf {} +
popd

View File

@ -0,0 +1,46 @@
/**
* Copyright (C) 2025-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <folly/TokenBucket.h>
#include "mongo/unittest/unittest.h"
namespace mongo {
namespace {
/**
* Simple test to ensure the folly::TokenBucket library has been vendored in properly.
*/
TEST(TokenBucketTest, CanConsume) {
folly::DynamicTokenBucket tokenBucket;
ASSERT_TRUE(tokenBucket.consume(1.0, 1.0, 20.0));
}
} // namespace
} // namespace mongo