Avoiding Statement.cancel() races - Mailing list pgsql-jdbc

From Oliver Jowett
Subject Avoiding Statement.cancel() races
Date
Msg-id 20031121021413.GA19307@opencloud.com
Whole thread Raw
Responses Re: Avoiding Statement.cancel() races
List pgsql-jdbc
I'm trying to work out how to avoid race conditions when using cancel() to
do graceful shutdown of a DB access thread, but it seems like there's a race
condition in the JDBC API itself.

I have a worker thread doing DB work. Another thread decides that it wants
to stop this worker thread (in our case, due to cluster partition). So it
does the usual set-flag-and-notify trick to inform the DB thread.

However that worker thread might be currently running a query -- and in turn
that query may block for an extended period of time (for example, if it's
waiting on a backend lock that's held by a client connection from the other
side of the partition, then it'll only continue when that other client
connection finally dies which could take up to the TCP abort interval).

I'd like to write code that looks something like this:

  private volatile boolean stopping = false;
  private Statement currentStatement = null;

  public void run() {
     // Worker thread
     while (!stopping) {
       // ... wait() for more work ...

       synchronized (this) {
         if (stopping) throw new SQLException("db thread cancelled");
         currentStatement = /* ... */;
       }
       results = currentStatement.executeUpdate();
       synchronized (this) {
         currentStatement = null;
       }
       // ...
    }
  }

  public void stopWorkerThread() {
     synchronized (this) {
       stopping = true;
       if (currentStatement != null) currentStatement.cancel();
       this.notify();
     }
  }

However there's an obvious race condition here between starting query
execution and cancelling the statement -- it's possible for
stopWorkerThread() to run at just the wrong time and "cancel" the statement
before it starts execution, having no effect and leaving the worker thread
still running a query (at least as I understand cancel() .. that's how it's
implemented in the postgresql driver anyway).

Extending the synchronized block to enclose 'someStatement.execute()'
doesn't help since then stopWorkerThread() can't enter that monitor while
the query is executing, which defeats the purpose of doing this in the first
place.

This seems like a design flaw in the JDBC API. Is there a way to solve this
problem that I'm missing?

Perhaps cancel() should be "permanent" i.e. once cancelled, a Statement
cannot be reused: all subsequent attempts at query execution are immediately
cancelled. This seems simpler, but I'd have expected any behaviour like this
to be explicitly spelled out in the cancel() javadoc if it was intended to
work that way.

Alternatively, we could add a cancelPermanently() method to PGstatement that
has this behaviour as a postgresql extension.

Any thoughts?

-O

pgsql-jdbc by date:

Previous
From: "Kirby, Stephen (Civ,ARL/CISD)"
Date:
Subject: unsubscribe
Next
From: Kris Jurka
Date:
Subject: Re: Avoiding Statement.cancel() races