Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ jobs:
sudo apt-get install clang-format-19

- name: clang-format
# skip contrib dir, find all files named *.c/h/cpp/hpp,
# Skip contrib and historical Rust C-header snapshots, find *.c/h/cpp/hpp,
# run clang-format 19.x on each file, in-place modifications
run: |
find . -name contrib -prune -or -type f \
find . '(' -name contrib -or -path ./bindings/rust/foundationdb-sys/include ')' -prune -or -type f \
'(' -name \*.c -or -name \*.cpp -or -name \*.h -or -name \*.hpp ')' \
-exec clang-format-19 -i '{}' \;

Expand Down
119 changes: 119 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: Rust bindings

on:
pull_request:
branches:
- main
- release-*
paths:
- bindings/rust/**
- bindings/c/**
- bindings/bindingtester/**
- contrib/local_cluster/binding_test.py
- cmake/AddFdbTest.cmake
- cmake/FDBComponents.cmake
- fdbclient/vexillographer/**
- flow/ApiVersions.cmake
- .github/workflows/rust.yml
push:
branches:
- main
- release-*
paths:
- bindings/rust/**
- bindings/c/**
- bindings/bindingtester/**
- contrib/local_cluster/binding_test.py
- cmake/AddFdbTest.cmake
- cmake/FDBComponents.cmake
- fdbclient/vexillographer/**
- flow/ApiVersions.cmake
- .github/workflows/rust.yml
workflow_dispatch:

permissions:
contents: read

defaults:
run:
working-directory: bindings/rust

env:
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
check:
name: Check and unit tests (${{ matrix.toolchain }})
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
toolchain: ["1.85.1", stable]
env:
RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }}
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

- name: Install Rust and libclang
run: |
rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal
sudo apt-get update
sudo apt-get install --yes libclang-dev

- name: Generate current C headers and options
run: cmake -DOUTPUT_DIR="$RUNNER_TEMP/rust-fdb-include" -P tests/current_headers.cmake

- name: Check current headers and options
env:
FDB_INCLUDE_DIR: ${{ runner.temp }}/rust-fdb-include
run: |
cargo check --locked -p foundationdb --lib --features fdb-7_4

- name: Check client targets against current headers
# API version features are mutually exclusive; do not use --all-features.
env:
FDB_INCLUDE_DIR: ${{ runner.temp }}/rust-fdb-include
run: >-
cargo check --locked --workspace --exclude foundationdb-simulation --all-targets
--features foundationdb/fdb-7_4,foundationdb/num-bigint,foundationdb/trace,foundationdb-gen/fdb-7_4

- name: Check simulator C ABI and examples
env:
FDB_INCLUDE_DIR: ${{ runner.temp }}/rust-fdb-include
run: >-
cargo check --locked --package foundationdb-simulation --all-targets
--features fdb-7_4

- name: Check supported historical APIs
# Some examples require newer APIs; historical compatibility covers the library.
run: |
for version in 6_1 6_2 6_3 7_0 7_1 7_3 7_4; do
cargo check --locked -p foundationdb --lib --no-default-features \
--features "embedded-fdb-include,fdb-${version},uuid"
done

- name: Test tuple encoding and macros
# These tests do not require libfdb_c or a running FoundationDB cluster.
run: |
cargo test --locked -p foundationdb-tuple --all-features
cargo test --locked -p foundationdb-macros

format:
name: Rustfmt
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
RUSTUP_TOOLCHAIN: "1.85.1"
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

- name: Install Rustfmt
run: rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --component rustfmt

- name: Check formatting
run: cargo fmt --all -- --check
3 changes: 3 additions & 0 deletions .github/workflows/tidy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ jobs:
flow/include/flow/sse2neon.h|flow/include/flow/xxhash.h)
continue
;;
bindings/rust/foundationdb-sys/include/*)
continue # historical C API snapshots for compatibility builds
;;
esac
# These inputs are not parseable as standalone clang-tidy translation units in this workflow:
# the public C headers require C consumer setup and intentionally use C typedefs, AWS headers are
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ The language bindings that CMake supports will have a corresponding `README.md`

Generally, CMake will build all language bindings for which it can find all necessary dependencies. After each successful CMake run, CMake will tell you which language bindings it is going to build.

The [Rust bindings](bindings/rust/README.md) are opt-in with `-DBUILD_RUST_BINDING=ON`. They retain the imported Rust API and currently select FoundationDB API 740; see their README for build requirements and compatibility limits.


### Generating `compile_commands.json`

Expand Down
3 changes: 3 additions & 0 deletions bindings/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ endif()
if(WITH_GO_BINDING AND NOT OPEN_FOR_IDE)
add_subdirectory(go)
endif()
if(WITH_RUST_BINDING)
add_subdirectory(rust)
endif()
if(WITH_RUBY_BINDING)
add_subdirectory(ruby)
endif()
Expand Down
8 changes: 6 additions & 2 deletions bindings/bindingtester/bindingtester.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ def __init__(self, args):

min_api_version = max([tester.min_api_version for tester in self.testers])
max_api_version = min([tester.max_api_version for tester in self.testers])
# Scripted cases exercise the newest common API; randomized suites also
# sample older versions. A tester's ceiling need not match the harness's.
if self.args.test_name == "scripted" and self.args.api_version is None:
self.args.api_version = min(max_api_version, self.test.max_api_version)
self.args.api_version = choose_api_version(
self.args.api_version,
min_api_version,
Expand Down Expand Up @@ -646,8 +650,8 @@ def parse_args(argv):
"--api-version",
default=None,
type=int,
help="The API version that the testers should use. Not supported in scripted mode. (default = random version supported by "
"all testers)",
help="The API version that the testers should use. (default = newest common version in scripted mode, "
"random version supported by all testers otherwise)",
)
parser.add_argument(
"--cluster-file",
Expand Down
9 changes: 9 additions & 0 deletions bindings/bindingtester/known_testers.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ def _absolute_path(path):
MAX_API_VERSION,
types=ALL_TYPES,
),
"rust": Tester(
"rust",
os.environ.get("FDB_RUST_BINDINGTESTER", _absolute_path("rust/bin/bindingtester")),
2040,
610,
740,
types=ALL_TYPES,
directory_snapshot_ops_enabled=False,
),
"flow": Tester(
"flow",
_absolute_path("flow/bin/fdb_flow_tester"),
Expand Down
20 changes: 12 additions & 8 deletions bindings/bindingtester/run_binding_tester.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ BREAKONERROR="${BREAKONERROR:-0}"
RUNSCRIPTS="${RUNSCRIPTS:-1}"
RUNTESTS="${RUNTESTS:-1}"
RANDOMTEST="${RANDOMTEST:-0}"
# BINDINGTESTS="${BINDINGTESTS:-python python3 java java_async ruby go flow}"
BINDINGTESTS="${BINDINGTESTS:-python python3 java java_async go flow swift}"
DEFAULT_BINDINGTESTS="python java java_async go flow swift"
if [[ -x "${FDB_RUST_BINDINGTESTER:-${SCRIPTDIR}/../rust/bin/bindingtester}" ]]; then
DEFAULT_BINDINGTESTS+=" rust"
fi
BINDINGTESTS="${BINDINGTESTS:-${DEFAULT_BINDINGTESTS}}"
LOGLEVEL="${LOGLEVEL:-INFO}"
_BINDINGTESTS=(${BINDINGTESTS})
DISABLEDTESTS=()
Expand All @@ -41,7 +44,7 @@ VERSION="1.6"
# Display syntax
if [ "${#}" -lt 2 ]
then
echo 'run_binding_tester.sh <number of cycles> <error file>'
echo 'run_binding_tester.sh <number of cycles> <error file> [bindingtester options...]'
echo ' cycles: number of cycles to run test (0 => unlimitted)'
echo ''
echo ' Modifiable Environment Variables:'
Expand All @@ -61,6 +64,7 @@ fi
# Read arguments
MAXCYCLES="${1}"
ERRORFILE="${2}"
BINDINGTESTER_OPTIONS=("${@:3}")

function logError()
{
Expand Down Expand Up @@ -187,7 +191,7 @@ function runScriptedTest()
else
local test="${1}"

if ! runCommand "Scripting ${test} ..." 'python3' '-u' "${TESTFILE}" "${test}" --test-name scripted --logging-level "${LOGLEVEL}"
if ! runCommand "Scripting ${test} ..." 'python3' '-u' "${TESTFILE}" "${test}" --test-name scripted --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}"
then
let status="${status} + 1"
fi
Expand All @@ -212,25 +216,25 @@ function runTest()
fi

# API
if ([[ "${TESTINDEX}" -eq 0 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[0]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name api --compare --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}"
if ([[ "${TESTINDEX}" -eq 0 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[0]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name api --compare --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}"
then
let status="${status} + 1"
fi

# Concurrent API
if ([[ "${TESTINDEX}" -eq 1 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[1]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name api --concurrency "${CONCURRENCY}" --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}"
if ([[ "${TESTINDEX}" -eq 1 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[1]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name api --concurrency "${CONCURRENCY}" --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}"
then
let status="${status} + 1"
fi

# Directory
if ([[ "${TESTINDEX}" -eq 2 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[2]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name directory --compare --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}"
if ([[ "${TESTINDEX}" -eq 2 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[2]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name directory --compare --num-ops "${OPERATIONS}" --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}"
then
let status="${status} + 1"
fi

# Directory HCA
if ([[ "${TESTINDEX}" -eq 3 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[3]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name directory_hca --concurrency "${CONCURRENCY}" --num-ops "${HCAOPERATIONS}" --logging-level "${LOGLEVEL}"
if ([[ "${TESTINDEX}" -eq 3 ]] || [[ "${TESTINDEX}" -eq "${TESTTOTAL}" ]]) && ([[ "${BREAKONERROR}" -eq 0 ]] || [[ "${status}" -eq 0 ]]) && ! runCommand " ${TESTTYPES[3]}" 'python3' '-u' "${TESTFILE}" "${test}" --test-name directory_hca --concurrency "${CONCURRENCY}" --num-ops "${HCAOPERATIONS}" --logging-level "${LOGLEVEL}" "${BINDINGTESTER_OPTIONS[@]}"
then
let status="${status} + 1"
fi
Expand Down
2 changes: 1 addition & 1 deletion bindings/bindingtester/tests/scripted.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

class ScriptedTest(Test):
def __init__(self, subspace):
super(ScriptedTest, self).__init__(subspace, FDB_API_VERSION, FDB_API_VERSION)
super(ScriptedTest, self).__init__(subspace, 510, FDB_API_VERSION)
self.workspace = self.subspace["workspace"]
self.results_subspace = self.subspace["results"]
# self.thread_subspace = self.subspace['threads'] # TODO: update START_THREAD so that we can create threads in subspaces
Expand Down
18 changes: 18 additions & 0 deletions bindings/c/fdb_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@

#include "fdbclient/FDBTypes.h"
#include "flow/ProtocolVersion.h"
#include <atomic>
#include <cstdint>
#include <mutex>
#define FDB_USE_LATEST_API_VERSION
#define FDB_INCLUDE_LEGACY_TYPES

Expand All @@ -32,6 +34,7 @@
#include "foundationdb/fdb_c_internal.h"

int g_api_version = 0;
static std::atomic<int> g_header_api_version{ 0 };

/*
* Our clients might share these ThreadSafe types between threads. It is therefore
Expand Down Expand Up @@ -1108,6 +1111,13 @@ extern "C" DLLEXPORT FDBFuture* fdb_transaction_get_range_split_points_with_limi
fdb_api_ptr_##func = (void*)&fdb_api_ptr_removed;

extern "C" DLLEXPORT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version) {
std::unique_lock<std::mutex> selectionLock;
RETURN_ON_ERROR(
// Shared by every caller of this C library, including independently loaded language bindings.
// Retain the mutex through process exit so late calls cannot encounter a destroyed lock.
static auto* const selectionMutex = new std::mutex;
selectionLock = std::unique_lock<std::mutex>(*selectionMutex););

/* Can only call this once */
if (g_api_version != 0)
return error_code_api_version_already_set;
Expand Down Expand Up @@ -1157,9 +1167,17 @@ extern "C" DLLEXPORT fdb_error_t fdb_select_api_version_impl(int runtime_version
FDB_API_CHANGED(fdb_transaction_set_option, 14);
/* End versioned API changes */

// Publish only after the runtime version and versioned entry points are final.
g_header_api_version.store(header_version, std::memory_order_release);
return error_code_success;
}

extern "C" DLLEXPORT void fdb_get_selected_api_versions(int* runtime_version, int* header_version) {
const int headerVersion = g_header_api_version.load(std::memory_order_acquire);
*runtime_version = headerVersion == 0 ? 0 : g_api_version;
*header_version = headerVersion;
}

extern "C" DLLEXPORT int fdb_get_max_api_version() {
return FDB_API_VERSION;
}
Expand Down
9 changes: 9 additions & 0 deletions bindings/c/foundationdb/fdb_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -669,8 +669,17 @@ DLLEXPORT WARN_UNUSED_RESULT FDBFuture* fdb_transaction_get_range_split_points_w
#define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1
#define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1

/* Concurrent selection calls are serialized; calls after successful selection return api_version_already_set. */
DLLEXPORT WARN_UNUSED_RESULT fdb_error_t fdb_select_api_version_impl(int runtime_version, int header_version);

/*
* Returns the runtime and header versions selected by fdb_select_api_version_impl,
* or zero for both until selection completes successfully. Both output pointers
* must be non-null. This query is thread-safe; API selection must still complete
* before other threads use the FoundationDB API.
*/
DLLEXPORT void fdb_get_selected_api_versions(int* runtime_version, int* header_version);

DLLEXPORT int fdb_get_max_api_version(void);
DLLEXPORT const char* fdb_get_client_version(void);

Expand Down
13 changes: 13 additions & 0 deletions bindings/rust/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[alias]
# Common development aliases for contributors

# Build/test/check with latest FDB version (7.4) using embedded headers
# Linking requires an installed FoundationDB client library
build-fdb-latest = "build -p foundationdb --features embedded-fdb-include,fdb-7_4"
test-fdb-latest = "test -p foundationdb --features embedded-fdb-include,fdb-7_4"
check-fdb-latest = "check -p foundationdb --features embedded-fdb-include,fdb-7_4"
clippy-fdb-latest = "clippy -p foundationdb --features embedded-fdb-include,fdb-7_4"
doc-fdb-latest = "doc -p foundationdb --features embedded-fdb-include,fdb-7_4"

# Lint retained crates and examples with the current supported API.
clippy-all = "clippy --workspace --all-targets --features foundationdb/num-bigint,foundationdb/embedded-fdb-include,foundationdb/fdb-7_4,foundationdb/trace,foundationdb-gen/fdb-7_4,foundationdb-gen/embedded-fdb-include,foundationdb-simulation/fdb-7_4 -- -D warnings"
11 changes: 11 additions & 0 deletions bindings/rust/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/target
**/*.rs.bk
*.swp

.vscode
.direnv
.idea

# Files generated by the simulation
simfdb
trace.*.json
Loading
Loading