Unit Test Gets Stuck in Rust

I have a repository implementation as below:

#[async_trait]
impl FileIDRepository for SqliteFileIDRepository {
    // ... other method implementations ...

    async fn upsert(&self, file_id: FileID) -> Result<(), OperationError> {
        debug!("Upserting the file id into the file id cache database...");
        trace!("file id: {:?}", file_id);

        let conn = self.conn.lock().await;

        debug!("Creating the hash...");
        let hash_pk: usize = match conn.execute(
            "INSERT INTO hashes (hash) VALUES (?1)",
            params![file_id.hash],
        ) {
            Ok(_) => {
                match conn.prepare("SELECT pk, hash FROM hashes WHERE hash=:hash") {
                    Ok(mut stmt) => {
                        match stmt.query(&[(":hash", &file_id.hash)]) {
                            Ok(mut rows) => {
                                debug!("Reading the row in hashes table...");
                                match rows.next() {
                            Ok(row_o) => match row_o {
                                Some(row) => match row.get(0) {
                                    Ok(v) => v,
                                    Err(e) => return handle_fidc_error!(Some(e)),
                                },
                                None => return handle_fidc_error!(Some("Expected newly inserted hash to exist but it does not.")),
                            },
                            Err(e) => return handle_fidc_error!(Some(e)),
                        }
                            }
                            Err(e) => return handle_fidc_error!(Some(e)),
                        }
                    }
                    Err(e) => return handle_fidc_error!(Some(e)),
                }
            }
            Err(e) => return handle_fidc_error!(Some(e)),
        };

        debug!("Creating the file id...");
        match conn.execute(
            "INSERT INTO fids (fid, hash_pk) VALUES (?1, ?2)",
            params![file_id.file_id, hash_pk],
        ) {
            Ok(_) => Ok(()),
            Err(e) => return handle_fidc_error!(Some(e)),
        }
    }

    // ... other method implementations ...
}

And I have a test like below:

    #[rstest]
    #[tokio::test]
    #[serial]
    async fn upsert_present(#[future] repo_future: SqliteFileIDRepository) {
        // set up
        let repo = repo_future.await;
        let conn = repo.conn.lock().await;
        {
            conn.execute("INSERT INTO hashes (hash) VALUES ('foo')", params![])
                .expect("Could not insert into hashes.");
            conn.execute(
                "INSERT INTO fids (fid, hash_pk) VALUES ('bar', 1)",
                params![],
            )
            .expect("Could not insert into fids.");
        }

        let file_id = FileID::new("foo", "bar");

        // test
        repo.upsert(file_id)
            .await
            .expect("Could not upsert file id.");

        let hash_exists = conn
            .prepare("SELECT hash FROM hashes WHERE hash = 'foo'")
            .expect("Could not prepare the connection for db.")
            .exists(params![])
            .expect("Could not check if hash exists.");

        let fid_exists = conn
            .prepare("SELECT fid FROM fids WHERE fid = 'bar'")
            .expect("Could not prepare the connection for db.")
            .exists(params![])
            .expect("Could not check if file id exists.");

        assert!(hash_exists, "Expected hash to exist.");
        assert!(fid_exists, "Expected file id to exist.");
    }

I want to test out if the related method upserts the given value if exists. The weird thing is I have upsert_absent test which runs just okay, but this one fails.

I'd like to also give the Drop implementation in the test module so that it clears the database after the test.

    impl Drop for SqliteFileIDRepository {
        fn drop(&mut self) {
            use std::fs;
            let db_path = get_db_path().expect("Could not get db path.");
            fs::remove_file(db_path).expect("Could not remove the db.");
        }
    }

And also the fixture I use with rstest, which deletes the SQLite database if exists before providing an instance of SqliteFileIDRepository:

    #[fixture]
    async fn repo_future() -> SqliteFileIDRepository {
        let _ = fs::remove_file(get_db_path().expect("Could not get db path.")).await;
        SqliteFileIDRepository::default()
            .await
            .expect("Could not initialize SqliteFileIDRepository.")
    }

The test just hangs when it comes upsert_present test. I can confirm this by doing RUST_TEST_THREADS=1 cargo test upsert_present --lib.

I'd like to get more information as to why it hangs. Debugging in VSCode does not work for me for some reason. If you also have no clue why this happens, do you know how I can debug this further?

Thanks in advance.

Further Troubleshooting

I have set up debugging in VSCode so I can see where it gets stuck.

The test upsert_present gets stuck at this line:

        repo.upsert(file_id)
            .await
            .expect("Could not upsert file id.");

The debugger led me an assembly (?) line where it got stuck:

; id = {0x000007e7}, range = [0x0000000000111f50-0x0000000000111f87), name="syscall"
; Source location: unknown
7FFFF79C5F50: F3 0F 1E FA                endbr64 
7FFFF79C5F54: 48 89 F8                   movq   %rdi, %rax
7FFFF79C5F57: 48 89 F7                   movq   %rsi, %rdi
7FFFF79C5F5A: 48 89 D6                   movq   %rdx, %rsi
7FFFF79C5F5D: 48 89 CA                   movq   %rcx, %rdx
7FFFF79C5F60: 4D 89 C2                   movq   %r8, %r10
7FFFF79C5F63: 4D 89 C8                   movq   %r9, %r8
7FFFF79C5F66: 4C 8B 4C 24 08             movq   0x8(%rsp), %r9
7FFFF79C5F6B: 0F 05                      syscall 
7FFFF79C5F6D: 48 3D 01 F0 FF FF          cmpq   $-0xfff, %rax  ; imm = 0xF001 ; this is where it gets stuck
7FFFF79C5F73: 73 01                      jae    0x7ffff79c5f76  ; <+38>
7FFFF79C5F75: C3                         retq   
7FFFF79C5F76: 48 8B 0D CB DE 0C 00       movq   0xcdecb(%rip), %rcx
7FFFF79C5F7D: F7 D8                      negl   %eax
7FFFF79C5F7F: 64 89 01                   movl   %eax, %fs:(%rcx)
7FFFF79C5F82: 48 83 C8 FF                orq    $-0x1, %rax
7FFFF79C5F86: C3                         retq   

I don't know any assembly so I am not sure what this is.


Environment

  • Rust 1.57.0 (2021)
  • Kubuntu 21.04 (if relevant)

The idea was not complicated actually. The problem was this:

I get the Mutex SQLite connection inside the repo, thus locking it until the end of the scope.

The upsert method also requests for SQLite connection, which is currently blocked on the current scope.

So I have moved all the setup logic to an inner scope, which will result in dropping the MutexGuard, thus freeing the connection. My final test is like this (with commentary):

    #[rstest]
    #[tokio::test]
    #[serial]
    async fn upsert_present(#[future] repo_future: SqliteFileIDRepository) {
        // set up
        let repo = repo_future.await;
        {
            let conn = repo.conn.lock().await; // get the connection, which locks it.
            conn.execute("INSERT INTO hashes (hash) VALUES ('foo')", params![])
                .expect("Could not insert into hashes.");
            conn.execute(
                "INSERT INTO fids (fid, hash_pk) VALUES ('bar', 1)",
                params![],
            )
            .expect("Could not insert into fids.");
        } // this is the end of setup scope, which will free the MutexGuard on SQLite connection

        let file_id = FileID::new("foo", "bar");

        // test
        // upsert method also tries to get the connection from Mutex. since it is free now, it will not block the thread, waiting for it to unlock.
        repo.upsert(file_id)
            .await
            .expect("Could not upsert file id.");
        // at the end of upsert, another scope ends, thus, freeing the connection once again.

        let conn = repo.conn.lock().await; // lock the connection again and do the checks.

        let hash_exists = conn
            .prepare("SELECT hash FROM hashes WHERE hash = 'foo'")
            .expect("Could not prepare the connection for db.")
            .exists(params![])
            .expect("Could not check if hash exists.");

        let fid_exists = conn
            .prepare("SELECT fid FROM fids WHERE fid = 'bar'")
            .expect("Could not prepare the connection for db.")
            .exists(params![])
            .expect("Could not check if file id exists.");

        assert!(hash_exists, "Expected hash to exist.");
        assert!(fid_exists, "Expected file id to exist.");
    }

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.