1

I would like to write a SwingWorker that, when the user cancels the task, it: 1) cancels the PreparedStatement and 2) aborts the Connection.

Here is some code to illustrate:

class Task extends SwingWorker<Void, Void> {

    Connection conn;
    PreparedStatement p_stmt;
    @Override
    protected Void doInBackground() throws Exception {
        ResultSet results = p_stmt.executeQuery();
        while(results.next()) {
            if(isCancelled())
                return;
        }
        return null;
    }

    void cancell() {
        cancel(true);
        try {
            p_stmt.cancel();
            conn.abort(/* How do I properly implement Executor? */);
        } catch (SQLException e) {
            handleError(e);
        }
    }

    @Override
    protected void done() {
        try {
            get();
        } catch (ExecutionException | InterruptedException e) {
            handleError(e);
        }
    }
}

static void handleError(Exception e) {
    // ...
}

This line:

conn.abort(/* How do I properly implement Executor? */);

Is the line I'm interested in. What should I pass to Connection::abort?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
ryvantage
  • 13,064
  • 15
  • 63
  • 112
  • Why are you calling `abort(...)` and not `.close()`? – Hovercraft Full Of Eels Aug 08 '18 at 17:31
  • Perhaps I should ask another SO question, but what's the difference and which is preferable? – ryvantage Aug 08 '18 at 17:34
  • https://stackoverflow.com/questions/51752617/jdbc-connection-close-vs-abort – ryvantage Aug 08 '18 at 17:39
  • 2
    I've honestly never seen `.abort(...)` used and have only seen `.close()`. The abort method is a fairly recent addition, Java 7, I believe. – Hovercraft Full Of Eels Aug 08 '18 at 17:42
  • I'm asking because my current `SwingWorker` implementations aren't cancelling fast enough (i.e., the queries run for several seconds after the user cancels) so I am looking to get very deep in the cancellation/abort process to minimize unnecessary activity. – ryvantage Aug 08 '18 at 17:44
  • You're doing all database access in the SwingWorker, a single thread, and so use `.close()`. – Hovercraft Full Of Eels Aug 08 '18 at 18:37
  • @HovercraftFullOfEels It has been a while since I have done anything with Swing and `SwingWorker`, but iirc, `cancel` is to be called from the event thread to asynchronously cancel/end the `SwingWorker`, so `abort` is probably suitable here. – Mark Rotteveel Aug 08 '18 at 20:57
  • @MarkRotteveel: I don't even think that `.abort(...)` is possible in this situation since I don't see how an Executor is worked into this code. – Hovercraft Full Of Eels Aug 08 '18 at 21:00
  • @MarkRotteveel: Swing uses an ExecutorService internally to manage the SwingWorkers, but as far as I understand it, this service is well hidden, and I know of no way of extracting it. I think that this difficulty is the main reason that the question was asked in the first place -- the OP was having difficulty trying to figure out what parameter to pass into the abort method. – Hovercraft Full Of Eels Aug 08 '18 at 21:08
  • @HovercraftFullOfEels Executor services used by Swing are a separate concern from the Executor that needs to be passed to `Connection.abort`; that just needs to be any implementation of `Executor`. – Mark Rotteveel Aug 08 '18 at 21:20
  • Many of the drivers don't support `abort()` (H2, for example). – Dávid Horváth Jun 15 '20 at 17:36

1 Answers1

3

You need to provide any implementation of Executor here. This could for example be an ExecutorService created by one of the methods of Executors, for example Executors.newSingleThreadPool(). You could also use a ForkJoinPool, for example the common pool returned by ForkJoinPool.commonPool(), but it could also be as basic as using a lambda that creates a new thread for each runnable passed:

connection.abort(runnable -> new Thread(runnable).start())

You could even just keep everything blocked in the current thread until the cleanup is done:

connection.abort(Runnable::run)

Each have their pros and cons, and unfortunately it is hard to provide a good advice because the JDBC specification is unclear on how the Executor is to be used. For example, should the driver add all cleanup tasks to the executor before returning from abort (the wording in the Javadoc implies this, but it is open to interpretation), or is it free to add tasks asynchronously during the cleanup? How well-behaved should the cleanup task(s) be in terms of execution time, etc.

If you create an Executor specifically for calling abort using Executors.newSingleThreadPool(), you will need to shut it down eventually otherwise you are leaking threads, but if the abort clean up tasks asynchronously add new tasks, there is no clearly defined moment that you can shutdown the executor service (or you may have shut it down too soon). This may be an indication that you should use a fixed thread pool with maybe more than one thread for the lifetime of your application, instead of creating it specifically for calling the abort.

On the other hand if you use the common fork/join pool, you may impact other parts of your application that also use it if a lot of tasks are added, or if the tasks are long running.

The first lambda I provided can result in the creation of a lot of threads if the driver creates a lot of tasks, and the second lambda I provided could result in a long wait or maybe even incorrect behaviour if the driver expects it to be run asynchronously.

In other words, it is a bit of a headache.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • Ok sounds good. Good to know there's a lot at stake in getting this answer right. My specific driver is Oracle OJDBC6 – ryvantage Aug 09 '18 at 00:25
  • I implemented the solution (`.conn.abort(Runnable::run);`) and get an error every time I cancel (`java.lang.AbstractMethodError: oracle.jdbc.driver.T4CConnection.abort(Ljava/util/concurrent/Executor;)V`). Last line of the stack trace is my code (i.e., the stack trace does not go into any libraries). – ryvantage Aug 09 '18 at 00:33
  • `runnable -> new Thread(runnable).start()` yields the same error – ryvantage Aug 09 '18 at 00:35
  • 2
    @ryvantage The `AbstractMethodError` means the method is not present in the implementation. The `abort` method was introduced in JDBC 4.1 (Java 7), in the naming convention used by Oracle, the `6` in ojdbc6.jar means that the driver is for Java 6 (JDBC 4), so the `abort` method is not present. You need to update your driver to a ojdbc7 or ojdbc8 version (at least, I'm assuming that Oracle implemented it in ojdbc7.jar and ojdbc8.jar). – Mark Rotteveel Aug 09 '18 at 07:53