Skip to content

Sporadic failures/hangs using pgtemp across many tests #28

@hoxxep

Description

@hoxxep

I am using pgtemp across many unit tests and I'm a big fan. I am also using cargo nextest, spinning up many of these in parallel. I occasionally run into two issues which I believe may be related to each other and to issue #9

  1. Database still starting error: This is the following diesel error that I occasionally get from tests, where pgtemp believes it's initialised slightly sooner than it really has.
    PoolError(Backend(ConnectionError(CouldntSetupConfiguration(DatabaseError(Unknown, "the database system is starting up")))))
  2. Database hangs on shutdown: I also occasionally experience 60s hangs at the end of tests while dropping tempdb, potentially the same as issue Shutdown (without persist) hangs in Github actions #9.

These happen locally and on Github CI, and infrequently enough that it's hard to figure out why. I am using postgres 14 and initialising via let _tempdb = pgtemp::PgTempDBBuilder::new().start_async().await, relying on the _tempdb variable to be dropped at the end of the test to shut down the temporary postgres instance.

My working hypothesis is that either pgtemp or postgres has a race condition when starting up multiple databases at the same time, perhaps picking up the wrong process ID or file somewhere? However, I can't artificially reproduce it with the test attached below. I'll add more information if some extra traces I've collected help.

The hang often happens to the same test (on 1 in every 10 test runs), and occasionally happens to other tests that start around the same time (on 1 in every 100 test runs). It's definitely the dropping of pgtemp that's hanging, which makes me think the order/timing that tests are started is having an impact.

This attempt to recreate it doesn't work though...
#[tokio::test]
async fn test_spinning_up_many_pgtemp_instances() {
    // function to create and destroy many tempdbs sequentially, to simulate a single test thread
    // running many tests sequentially
    async fn sequential_create_destroy() {
        for _ in 0..50 {
            let start = std::time::Instant::now();

            // create new tempdb
            let temp = pgtemp::PgTempDBBuilder::new().start_async().await;
            let mut pg_conn = sqlx::postgres::PgConnection::connect(
                temp.connection_uri().as_str()
            ).await.unwrap();

            // add some data
            pg_conn.execute("CREATE SCHEMA test").await.unwrap();
            pg_conn.execute("CREATE DATABASE test").await.unwrap();
            pg_conn.execute("CREATE TABLE test.test_table (id INT)").await.unwrap();
            pg_conn.execute("INSERT INTO test.test_table VALUES (1)").await.unwrap();
            pg_conn.execute("INSERT INTO test.test_table VALUES (2)").await.unwrap();

            // drop DB with an active connection
            drop(temp);
            let duration = start.elapsed();
            if duration.as_secs() > 10 {
                panic!("Creating and destroying a PgTempDB took too long: {:?}", duration);
            }
        }
    }

    // run many instances in parallel to simulate test runner threads
    let mut instances = vec![];
    for _ in 0..20 {
        instances.push(sequential_create_destroy());
    }

    futures::future::join_all(instances).await;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions