Does Postgres-Nio not report errors?

Note: Not sure if this is the correct venue (or category) for my questions.

It feels like I am missing something!! How can the following postgres-nio API calls claim to never fail? Notice the lack of error reporting mechanism: no failable init?(), no throws, no Result<>, etc.

let client = PostgresClient(configuration: …)
await withTaskGroup(of: Void.self) { taskGroup in
    defer {
        // To shutdown the client, cancel its run method, by cancelling the taskGroup.
        taskGroup.cancelAll()
    }
    taskGroup.addTask {
        await client.run()
        // ⚠️ This hangs — awaits forever — if ever we can't connect to DB for **ANY** reason.
        // I.e. host doesn't answer or wrong credentials
    }
    // … interact with DB here …
}
  1. Why doesn't it honour the PostgresClient.options.connectTimeout instead of waiting forever?
  2. PostgresClient.run() does not throw, nor does it return a value to indicate an error; How would you obtain any useful error information?

Even if someone were to claim "it's not postgres-nio's responsibility; it's yours!"… if I were to implement my own timeout detection… how would I ever report anything that's useful?! I wouldn't know if it's the database that's not reachable, or wrong credentials, etc.

This doesn't seem suitable for any production projects. I must be missing something, right?!

1 Like

Starting the client (by calling .run()) doesn't actually do anything that talks to the server - it starts the client, sets up the connection pool ready to hold connections etc. It isn't until you make an actual query with .query() etc that it will attempt to reach out to the server and where the timeout will come into effect.

And yes, it's most definitely used in production at very large scale

@0xTim If that is so, why must I specify a time limit to the following @Test() for it to end?!

    @Test("Postgres Connection!", .timeLimit(.minutes(5)))
    func postgresNio() async throws {
        let config = PostgresClient.Configuration( … )
        let client = PostgresClient(configuration: config, backgroundLogger: logger)
        await withThrowingTaskGroup(of: Void.self) { taskGroup in
            defer {
                taskGroup.cancelAll()
            }
            taskGroup.addTask {
                 await client.run()
            }

            try await client.query("Select nothing from nonExistingTable", logger: logger)
        }
    }

I would expect the test to throw an error and end once the PostgresClient.options.connectTimeout (default of 10s) has elapsed. Wouldn't try await client.query() throw once the 10s timeout is done and fire the defered block to cancelAll() tasks?!

My guess here without looking into it is that's a bug in the library because the query call is happening before the client is set up (because the client.run() is inside the addTask block whereas the query call is invoked immediately. That should probably throw an error because you're trying to use the client before it was started. I suspect if you ensure the client is set up first it should fail as expected

1 Like

Thank you @0xTim! Just wanted to close this with a bit of feedback…

Everything above seems to have spawned from my lack of understanding of ThrowingTaskGroup's next() which has, in my opinion, too little documentation — one has to assume it does the same as nextResult(isolation:) and we know where assumptions lead us — nor have I seen examples/articles of others using it.

I only now just recently managed to find Error-Handling withThrowingTaskGroup() so I have some reading and experimentation to do.