Skip to main content

Concurrency Model

The GooseFS Rust SDK is fully async (tokio) and designed for high-concurrency workloads. This page covers the key concurrency concepts every user should know.

FileSystemContext: One Per Process

FileSystemContext owns the Master/Worker connection pools, config refresher, and metrics tasks. Build it once and share via Arc:

use std::sync::Arc;
use goosefs_sdk::context::FileSystemContext;
use goosefs_sdk::config::GoosefsConfig;
use goosefs_sdk::fs::{BaseFileSystem, FileSystem};

#[tokio::main]
async fn main() -> goosefs_sdk::error::Result<()> {
let ctx: Arc<FileSystemContext> = FileSystemContext::connect(GoosefsConfig::new("127.0.0.1:9200")).await?;
let fs = BaseFileSystem::from_context(ctx.clone());

// Spawn many concurrent operations sharing the same context.
let mut handles = Vec::new();
for i in 0..100 {
let fs = fs.clone();
handles.push(tokio::spawn(async move {
fs.exists(&format!("/data/file-{}", i)).await
}));
}
for h in handles {
let _ = h.await;
}

ctx.close().await?;
Ok(())
}
note

FileSystemContext is Send + Sync and can be freely cloned as Arc<FileSystemContext>. All internal pools are thread-safe.

Connection Pools

Master connection pool

The master pool maintains multiple HTTP/2 channels to the master. Default size is 1 (single channel). Raise to 4-8 for high-concurrency remote scenarios:

use goosefs_sdk::config::{GoosefsConfig, MasterPoolSchedule};

let mut config = GoosefsConfig::new("127.0.0.1:9200");
config.master_connection_pool_size = 8;
config.master_connection_pool_schedule = MasterPoolSchedule::P2C;

See Configuration for env var / properties alternatives.

Worker connection pool

The worker pool is per-worker-address. Default is min(cores, 4). Each WorkerClient::acquire() returns an owned WorkerClient; dropping it returns the channel to the pool.

ConfigRefresher

A background task reloads transparent-acceleration switches from properties/env. ConfigRefresher uses a 30s expiry duration, while FileSystemContext::connect() starts an eager load followed by refresh checks every 60s.

Async I/O with tokio

All SDK methods are async fn and run on the tokio runtime. They never block the calling thread — I/O is driven by tokio's reactor.

// Concurrent reads with tokio::join!
let (a, b) = tokio::join!(
GoosefsFileReader::read_file_with_context(ctx.clone(), "/data/a"),
GoosefsFileReader::read_file_with_context(ctx.clone(), "/data/b"),
);

GoosefsFileInStream is not Sync

GoosefsFileInStream holds internal cursor state and requires &mut self for read / seek. It is Send but not Sync — use one stream per task:

// ✅ One stream per task — safe.
let mut tasks = Vec::new();
for path in paths {
let ctx = ctx.clone();
tasks.push(tokio::spawn(async move {
let mut s = GoosefsFileInStream::open_with_context(
ctx,
&path,
goosefs_sdk::fs::options::OpenFileOptions::new(),
).await?;
s.read_all().await
}));
}

GoosefsFileWriter must be closed or cancelled

Dropping a GoosefsFileWriter without close() or cancel() triggers best-effort cleanup, but in-flight blocks may leak if no tokio runtime is available. Always call one or the other:

// ✅ Explicit close on success path.
writer.close().await?;

// ✅ Cancel on error path.
if let Err(e) = writer.write(data).await {
writer.cancel().await.ok();
return Err(e);
}

Multi-Master (HA)

use goosefs_sdk::config::GoosefsConfig;
use goosefs_sdk::context::FileSystemContext;

let addrs = vec!["10.0.0.1:9200".to_string(), "10.0.0.2:9200".to_string(), "10.0.0.3:9200".to_string()];
let config = GoosefsConfig::from_addresses(addrs);
let ctx = FileSystemContext::connect(config).await?;

Two or more addresses → multi-master mode. The client polls to discover the Primary automatically. If the Primary fails, the client retries on the next replica.

Graceful Shutdown

// Close the context to stop its background tasks and mark it as closed.
ctx.close().await?;
assert!(ctx.is_closed());

close() is idempotent. After close, is_closed() returns true and the context's background refresh and metrics tasks have stopped.