From 8632f9b43a5d51717e1c768a222a1d0d4e0c6b32 Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Mon, 4 May 2026 18:05:08 +0200 Subject: [PATCH] Allow multiple FoundationDB instances in the same process --- CHANGELOG.md | 1 + Dockerfile.fdb | 30 ++++++++++++++----- crates/jmap/src/registry/set.rs | 14 +++++++-- crates/store/src/backend/foundationdb/main.rs | 26 ++++++++++------ crates/store/src/backend/foundationdb/mod.rs | 4 +-- tests/Cargo.toml | 2 +- tests/docker/docker-compose.yml | 9 +++++- tests/docker/scripts/init-fdb.sh | 19 +++++++++--- 8 files changed, 77 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e559a6b2..916f165e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - Spam filter: Use original instead of rewritten `RCPT` on checks. - JMAP references in nested objects not resolved. - Import tool fails to restore registry entries. +- FDB: Allow multiple FoundationDB instances in the same process. ## [0.16.3] - 2026-04-30 diff --git a/Dockerfile.fdb b/Dockerfile.fdb index 66b93f51..ca266b6a 100644 --- a/Dockerfile.fdb +++ b/Dockerfile.fdb @@ -1,13 +1,18 @@ +# syntax=docker/dockerfile:1 + FROM debian:trixie-slim AS chef +ARG TARGETARCH +ARG FDB_VERSION_RANGE="7.4" RUN apt-get update && \ export DEBIAN_FRONTEND=noninteractive && \ - apt-get install -yq \ + apt-get install -yq --no-install-recommends \ build-essential \ + ca-certificates \ cmake \ clang \ curl \ - protobuf-compiler \ - adduser + jq \ + protobuf-compiler ENV RUSTUP_HOME=/opt/rust/rustup \ PATH=/home/root/.cargo/bin:/opt/rust/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin RUN curl https://sh.rustup.rs -sSf | \ @@ -15,8 +20,19 @@ RUN curl https://sh.rustup.rs -sSf | \ sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path && \ env CARGO_HOME=/opt/rust/cargo \ rustup component add rustfmt -RUN curl -LO https://github.com/apple/foundationdb/releases/download/7.3.69/foundationdb-clients_7.3.69-1_amd64.deb && \ - dpkg -i foundationdb-clients_7.3.69-1_amd64.deb +RUN \ + ARCH="${TARGETARCH:-$(dpkg --print-architecture)}" && \ + case "$ARCH" in \ + amd64) FDB_ARCH=amd64 ;; \ + arm64) FDB_ARCH=aarch64 ;; \ + *) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; \ + esac && \ + curl --retry 5 -fLso fdb-client.deb "$(curl --retry 5 -fLs 'https://api.github.com/repos/apple/foundationdb/releases?per_page=100' | jq --arg FDB_ARCH "$FDB_ARCH" --arg RANGE "${FDB_VERSION_RANGE}" -r '[.[] | select(.tag_name | startswith($RANGE + "."))] | sort_by(.tag_name | split(".") | map(tonumber)) | reverse | .[0].assets[] | select(.name | test("foundationdb-clients.*" + $FDB_ARCH + ".deb$")) | .browser_download_url')" && \ + mkdir -p /fdb && \ + dpkg -x fdb-client.deb /fdb && \ + mv /fdb/usr/include/foundationdb /usr/include && \ + mv /fdb/usr/lib/libfdb_c.so /usr/lib && \ + rm -rf fdb-client.deb /fdb RUN env CARGO_HOME=/opt/rust/cargo cargo install cargo-chef && \ rm -rf /opt/rust/cargo/registry/ WORKDIR /app @@ -42,12 +58,10 @@ RUN cargo build -p stalwart --no-default-features --features "foundationdb s3 re FROM debian:trixie-slim AS runtime COPY --from=builder --chmod=0755 /app/target/release/stalwart /usr/local/bin/stalwart +COPY --from=builder /usr/lib/libfdb_c.so /usr/lib/libfdb_c.so RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ apt-get install -yq --no-install-recommends ca-certificates curl libcap2-bin && \ - curl -LO https://github.com/apple/foundationdb/releases/download/7.3.69/foundationdb-clients_7.3.69-1_amd64.deb && \ - dpkg -i foundationdb-clients_7.3.69-1_amd64.deb && \ - rm -f foundationdb-clients_7.3.69-1_amd64.deb && \ rm -rf /var/lib/apt/lists/* && \ groupadd -r -g 2000 stalwart && \ useradd -r -u 2000 -g 2000 -s /usr/sbin/nologin -M stalwart && \ diff --git a/crates/jmap/src/registry/set.rs b/crates/jmap/src/registry/set.rs index ea76b145..0a4edb94 100644 --- a/crates/jmap/src/registry/set.rs +++ b/crates/jmap/src/registry/set.rs @@ -29,7 +29,7 @@ use crate::registry::{ }; use common::{ Server, auth::AccessToken, cache::invalidate::CacheInvalidationBuilder, - expr::if_block::BootstrapExprExt, + expr::if_block::BootstrapExprExt, ipc::CacheInvalidation, }; use http_proto::HttpSessionData; use jmap_proto::{ @@ -649,8 +649,18 @@ impl RegistrySet for Server { .await? { RegistryWriteResult::Success(_) => { - // Schedule account deletion if let ObjectInner::Account(account) = &object.inner { + for sharee_id in self + .store() + .acl_revoke_all(id.document_id()) + .await + .caused_by(trc::location!())? + { + cache_invalidator.invalidate( + CacheInvalidation::AccessToken(sharee_id), + ); + } + schedule_account_destruction(set.server, id, account).await?; } diff --git a/crates/store/src/backend/foundationdb/main.rs b/crates/store/src/backend/foundationdb/main.rs index 1c53d818..1a044fee 100644 --- a/crates/store/src/backend/foundationdb/main.rs +++ b/crates/store/src/backend/foundationdb/main.rs @@ -6,19 +6,28 @@ use super::FdbStore; use crate::Store; -use foundationdb::{Database, api, options::DatabaseOption}; +use foundationdb::{Database, api, api::NetworkAutoStop, options::DatabaseOption}; +use parking_lot::Mutex; use registry::schema::structs; use std::sync::Arc; +static FDB_NETWORK: Mutex> = Mutex::new(None); + impl FdbStore { pub async fn open(config: structs::FoundationDbStore) -> Result { - let guard = unsafe { - api::FdbApiBuilder::default() - .build() - .map_err(|err| format!("Failed to boot FoundationDB: {err:?}"))? - .boot() - .map_err(|err| format!("Failed to boot FoundationDB: {err:?}"))? - }; + { + let mut guard = FDB_NETWORK.lock(); + if guard.is_none() { + let network = unsafe { + api::FdbApiBuilder::default() + .build() + .map_err(|err| format!("Failed to boot FoundationDB: {err:?}"))? + .boot() + .map_err(|err| format!("Failed to boot FoundationDB: {err:?}"))? + }; + *guard = Some(network); + } + } let db = Database::new(config.cluster_file.as_deref()) .map_err(|err| format!("Failed to create FoundationDB database: {err:?}"))?; @@ -49,7 +58,6 @@ impl FdbStore { } Ok(Store::FoundationDb(Arc::new(Self { - guard, db, version: Default::default(), }))) diff --git a/crates/store/src/backend/foundationdb/mod.rs b/crates/store/src/backend/foundationdb/mod.rs index 735df209..27e11335 100644 --- a/crates/store/src/backend/foundationdb/mod.rs +++ b/crates/store/src/backend/foundationdb/mod.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use foundationdb::{Database, FdbError, api::NetworkAutoStop}; +use foundationdb::{Database, FdbError}; use std::time::{Duration, Instant}; pub mod blob; @@ -15,10 +15,8 @@ pub mod write; const MAX_VALUE_SIZE: usize = 100000; pub const TRANSACTION_EXPIRY: Duration = Duration::from_secs(1); -#[allow(dead_code)] pub struct FdbStore { db: Database, - guard: NetworkAutoStop, version: parking_lot::Mutex, } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index db5373b3..b69e8459 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [features] #default = ["sqlite", "postgres", "mysql", "rocks", "s3", "redis", "nats", "azure", "foundationdb"] #default = ["sqlite", "postgres", "mysql", "rocks", "s3", "redis", "foundationdb"] -default = ["rocks", "s3"] +default = ["rocks"] sqlite = ["store/sqlite", "directory/sqlite"] foundationdb = ["store/foundation", "common/foundation"] postgres = ["store/postgres", "directory/postgres"] diff --git a/tests/docker/docker-compose.yml b/tests/docker/docker-compose.yml index 9a622b3d..7b7a4e36 100644 --- a/tests/docker/docker-compose.yml +++ b/tests/docker/docker-compose.yml @@ -60,11 +60,18 @@ services: tmpfs: - /var/fdb/data - /var/fdb/logs + healthcheck: + test: [ "CMD-SHELL", "fdbcli --exec 'status' --timeout 3 >/dev/null 2>&1" ] + interval: 2s + timeout: 5s + retries: 30 + start_period: 5s fdb-init: image: foundationdb/foundationdb:7.4.6 depends_on: - - foundationdb + foundationdb: + condition: service_healthy volumes: - fdb-config:/var/fdb - ./scripts/init-fdb.sh:/init-fdb.sh:ro diff --git a/tests/docker/scripts/init-fdb.sh b/tests/docker/scripts/init-fdb.sh index 90ffd369..36396d80 100755 --- a/tests/docker/scripts/init-fdb.sh +++ b/tests/docker/scripts/init-fdb.sh @@ -1,7 +1,18 @@ #!/bin/bash -set -e +set -eu -fdbcli --exec "configure new single memory" -echo "FoundationDB configured." -exit 0 +for i in $(seq 1 30); do + if fdbcli --exec 'status minimal' --timeout 5 2>&1 | grep -q "The database is available"; then + echo "FoundationDB already configured." + exit 0 + fi + if fdbcli --exec 'configure new single memory' --timeout 5 2>&1 | grep -q "Database created"; then + echo "FoundationDB configured." + exit 0 + fi + echo "Waiting for FoundationDB to be ready (attempt $i)..." + sleep 2 +done +echo "ERROR: Failed to configure FoundationDB after retries" >&2 +exit 1