diff --git a/sdk/python/tests/test_pg0.py b/sdk/python/tests/test_pg0.py index 5589938..fa405c8 100644 --- a/sdk/python/tests/test_pg0.py +++ b/sdk/python/tests/test_pg0.py @@ -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 @@ -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.""" diff --git a/src/main.rs b/src/main.rs index 7742598..35e4a7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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)?; } @@ -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() };