Skip to content
Merged
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
36 changes: 36 additions & 0 deletions sdk/python/tests/test_pg0.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Tests for pg0 Python client."""

import os
import signal
import time

import pytest
import pg0
from pg0 import Pg0, InstanceInfo, Pg0AlreadyRunningError, Pg0Error
Expand Down Expand Up @@ -147,6 +151,38 @@ def test_port_conflict_error(self, clean_instance):
pg2.stop()
pg0.drop(f"{TEST_NAME}-2")

def test_data_survives_crash(self, clean_instance):
"""Test that data is preserved after an unclean shutdown (SIGKILL).

Regression test for https://github.com/vectorize-io/pg0/issues/6
Simulates a crash by sending SIGKILL to the PostgreSQL process,
which leaves a stale postmaster.pid. On restart, data must still exist.
"""
pg = Pg0(name=TEST_NAME, port=TEST_PORT)
info = pg.start()

# Create a table and insert data
pg.execute("CREATE TABLE crash_test (id serial PRIMARY KEY, value text);")
pg.execute("INSERT INTO crash_test (value) VALUES ('survive_crash');")
result = pg.execute("SELECT value FROM crash_test;")
assert "survive_crash" in result

# Simulate a crash: SIGKILL the postgres process (leaves stale postmaster.pid)
pid = info.pid
assert pid is not None
os.kill(pid, signal.SIGKILL)
time.sleep(1) # Wait for process to die

# Restart — this must NOT lose data
info = pg.start()
assert info.running is True

# Verify data survived the crash
result = pg.execute("SELECT value FROM crash_test;")
assert "survive_crash" in result

pg.stop()


class TestConvenienceFunctions:
"""Tests for module-level convenience functions."""
Expand Down
9 changes: 8 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,13 @@ fn start(
if is_process_running(info.pid) {
return Err(CliError::AlreadyRunning(info.pid));
}
// Stale instance, clean up
// Stale instance: clean up instance metadata but preserve data directory.
// Remove stale postmaster.pid so PostgreSQL can start with existing data.
let pid_file = info.data_dir.join("postmaster.pid");
if pid_file.exists() {
println!("Removing stale postmaster.pid (process {} no longer running)...", info.pid);
fs::remove_file(&pid_file)?;
}
remove_instance(&name)?;
}

Expand Down Expand Up @@ -611,6 +617,7 @@ fn start(
installation_dir: version_install_dir,
configuration,
trust_installation_dir: true, // Use our extracted files
temporary: false, // Never delete data directory on drop - pg0 manages data lifecycle explicitly
timeout: Some(std::time::Duration::from_secs(600)), // 10 minute timeout for slow systems (ARM64 emulation under QEMU)
..Default::default()
};
Expand Down
Loading