Skip to content

Unhandled Exceptions causes PgConnection.close to hang forever (v3) #119

Closed
@osaxma

Description

@osaxma

Add the following code to a file in the test folder and run it to reproduce the issue (or read the comments in the code):

import 'package:postgres/postgres_v3_experimental.dart';
import 'package:test/test.dart';

import 'docker.dart';

void main() {
  // NOTE: The Docker Container will not close after stopping this test so that needs to be done manually. 
  usePostgresDocker();

  // ignore: unused_local_variable
  late final PgConnection conn1;

  late final PgConnection conn2;

  group('description', () {
    setUpAll(() async {
      conn1 = await PgConnection.open(
        PgEndpoint(
          host: 'localhost',
          database: 'dart_test',
          username: 'dart',
          password: 'dart',
        ),
        sessionSettings: PgSessionSettings(
          onBadSslCertificate: (cert) => true,
        ),
      );

      conn2 = await PgConnection.open(
        PgEndpoint(
          host: 'localhost',
          database: 'dart_test',
          username: 'postgres',
          password: 'postgres',
        ),
        sessionSettings: PgSessionSettings(
          onBadSslCertificate: (cert) => true,
        ),
      );
    });

    test('produce error', () async {
      // get conn1 PID
      final res = await conn2
          .execute("SELECT pid FROM pg_stat_activity where usename = 'dart';");
      final conn1PID = res.first.first as int;

      // Simulate issue by terminating a connection during a query
      // ignore: unawaited_futures
      conn1.execute('select * from pg_stat_activity;'); // comment this out and a different error will appear 

      // Terminate the conn1 while the query is running
      await conn2.execute(
          'select pg_terminate_backend($conn1PID) from pg_stat_activity;');
      // this will cause the following exception:
      // PostgreSQLException (PostgreSQLSeverity.fatal 57P01: terminating connection due to administrator command )

      expect(true, true);
    });

    tearDownAll(() async {
      print('closing conn1');
      await conn1.close(); // this will never close & execution will hang here
      print('closing conn2');
      await conn2.close();
    });
  });
}

I think currently there's no direct and easy way for the user to handle such Exceptions (maybe a callback would help?). I also think PgConnection.close should check if the connection was closed/terminated/errored-out before trying a graceful termination process.


Additional Context

I initially encountered a simple issue where await conn.close(); would hang forever due to a connection blocking query used in logical replication (i.e. START_REPLICATION ...... ). I tried to force close the connection but there was no way around it. So I opened a separate connection to kill the replication process in the database in the testing teardown code as a workaround. That's when I came across the issue which is presented in the code snippet above.

While this issue appeared with me in an edge case, I still think there should be a way to handle such severe/fatal PostgreSQL errors because they may appear in other cases. Also, as mentioned earlier, the PgConnection.close should account for such scenarios and maybe have a flag for ungraceful termination (e.g. PgConnection.close(force: true)) in addition to a timeout logic (honestly i didn't try to add timeout settings yet).

cc: @isoos @simolus3

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