Skip to content

Commit af0e5ab

Browse files
Fix testcontainers leak: label containers and clean up orphans
Testcontainers postgres instances were never cleaned up on process exit (OnceLock statics don't run destructors) or abnormal termination, accumulating 115+ orphaned containers and 4.77 GB of volumes. - Label test containers with com.betweenrows.test for identification - Clean up orphaned labeled containers in pre-commit hook before tests - Store ContainerAsync in SharedPostgres struct instead of holding via pending future
1 parent 8e64ec7 commit af0e5ab

2 files changed

Lines changed: 18 additions & 2 deletions

File tree

.githooks/pre-commit

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4+
# Clean up orphaned test containers from previous interrupted runs
5+
orphans=$(docker ps -q --filter "label=com.betweenrows.test" 2>/dev/null || true)
6+
if [ -n "$orphans" ]; then
7+
echo "→ cleaning up orphaned test containers"
8+
docker rm -f $orphans >/dev/null 2>&1 || true
9+
fi
10+
411
echo "→ cargo fmt --check"
512
cargo fmt --check
613

proxy/tests/support/mod.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ pub struct SharedPostgres {
6565
pub url: String,
6666
pub host: String,
6767
pub port: u16,
68+
/// Keeps the container alive. Dropped on normal process exit → container stops.
69+
/// For abnormal exits (SIGKILL), the container label lets us find and clean orphans.
70+
_container: testcontainers::ContainerAsync<testcontainers_modules::postgres::Postgres>,
6871
}
6972

7073
static SHARED_PG: OnceLock<Option<SharedPostgres>> = OnceLock::new();
@@ -87,10 +90,15 @@ pub fn shared_postgres() -> Option<&'static SharedPostgres> {
8790
}
8891
};
8992
rt.block_on(async {
93+
use testcontainers::core::ImageExt;
9094
use testcontainers::runners::AsyncRunner;
9195
use testcontainers_modules::postgres::Postgres;
9296

93-
let container = match Postgres::default().start().await {
97+
let container = match Postgres::default()
98+
.with_label("com.betweenrows.test", "true")
99+
.start()
100+
.await
101+
{
94102
Ok(c) => c,
95103
Err(e) => {
96104
eprintln!("testcontainers: could not start Postgres: {e}");
@@ -110,8 +118,9 @@ pub fn shared_postgres() -> Option<&'static SharedPostgres> {
110118
url: format!("postgres://postgres:postgres@127.0.0.1:{port}/postgres"),
111119
host: "127.0.0.1".to_string(),
112120
port,
121+
_container: container,
113122
}));
114-
// Keep container alive until the test process exits.
123+
// Keep the runtime alive so the container's async drop works on process exit.
115124
std::future::pending::<()>().await;
116125
});
117126
});

0 commit comments

Comments
 (0)