Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement cancel() and terminate() methods #4

Open
gajus opened this issue Feb 26, 2019 · 3 comments
Open

Implement cancel() and terminate() methods #4

gajus opened this issue Feb 26, 2019 · 3 comments

Comments

@gajus
Copy link
Owner

gajus commented Feb 26, 2019

These methods would abstract the following patterns:

await pool.connect(async (connection0) => {
  await pool.connect(async (connection1) => {
    const backendProcessId = await connection1.oneFirst(sql`SELECT pg_backend_pid()`);

    setTimeout(() => {
      connection0.query(sql`SELECT pg_terminate_backend(${backendProcessId})`)
    }, 2000);

    try {
      await connection1.query(sql`SELECT pg_sleep(30)`);
    } catch (error) {

    }

    // This will never be executed.
    await connection1.query(sql`SELECT now()`)
  });
});

and

await pool.connect(async (connection0) => {
  await pool.connect(async (connection1) => {
    const backendProcessId = await connection1.oneFirst(sql`SELECT pg_backend_pid()`);

    setTimeout(() => {
      connection0.query(sql`SELECT pg_cancel_backend(${backendProcessId})`)
    }, 2000);

    try {
      await connection1.query(sql`SELECT pg_sleep(30)`);
    } catch (error) {

    }

    // This will be executed.
    await connection1.query(sql`SELECT now()`)
  });
});

The design challenge:

  • Should the supervisor connection (the one thats responsible for cancelling the query) be created before the query executed?
  • If yes, then this would double the number of connections.
  • We could use a dedicated method pool.cancellableConnect((connection) => {}).
  • If not, then how do we guarantee that there are enough connection slots to start the new connection when required?
@gajus
Copy link
Owner Author

gajus commented Feb 26, 2019

  • QueryCancelledError can be used to capture pg_cancel_backend.
  • BackendTerminatedError can be used to capture pg_terminate_backend.

gajus/slonik#39

@gajus
Copy link
Owner Author

gajus commented Feb 26, 2019

Example implementation:

const cancellableConnection = async (pool: DatabasePoolType, handler: (connection: DatabaseConnectionType, cancel: () => Promise<void>) => Promise<*>) => {
  return pool.connect(async (connection0) => {
    let done = false;

    const backendProcessId = await connection0.oneFirst(sql`SELECT pg_backend_pid()`);

    await pool.connect(async (connection1) => {
      await handler(connection1, async () => {
        if (done) {
          return;
        }

        await connection1.query(sql`
          SELECT pg_terminate_backend(${backendProcessId})
        `);
      });

      done = true;
    });
  });
};

Used as:

await cancellableConnection(target, (connection, cancel) => {
  setTimeout(() => {
    cancel();
  }, 1000);

  return connection.query(sql`SELECT pg_sleep(10)`);
});

@gajus
Copy link
Owner Author

gajus commented Feb 27, 2019

A more correct implementation:

const cancellableConnection = async (pool: DatabasePoolType, cancellableConnectionRoutine: CancellableConnectionRoutineType) => {
  let done = false;

  return pool.connect(async (connection0) => {
    return pool.connect(async (connection1) => {
      const backendProcessId = await connection1.oneFirst(sql`SELECT pg_backend_pid()`);

      const cancel = () => {
        if (done) {
          return;
        }

        done = true;

        connection0.query(sql`
          SELECT pg_terminate_backend(${backendProcessId})
        `)
          .catch((error) => {
            if (!(error instanceof BackendTerminatedError)) {
              throw error;
            }
          });
      };

      let result;

      try {
        result = await cancellableConnectionRoutine(connection1, cancel);

        done = true;
      } catch (error) {
        done = true;

        throw error;
      }

      return result;
    });
  });
};

@gajus gajus transferred this issue from gajus/slonik Nov 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant