Allow multiple FoundationDB instances in the same process

This commit is contained in:
Maurus Decimus
2026-05-04 18:05:08 +02:00
parent 7e21ff6f4b
commit 8632f9b43a
8 changed files with 77 additions and 28 deletions

View File

@@ -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

View File

@@ -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 && \

View File

@@ -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?;
}

View File

@@ -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<Option<NetworkAutoStop>> = Mutex::new(None);
impl FdbStore {
pub async fn open(config: structs::FoundationDbStore) -> Result<Store, String> {
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(),
})))

View File

@@ -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<ReadVersion>,
}

View File

@@ -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"]

View File

@@ -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

View File

@@ -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