Canceling Database Operations

Mulgara now provides preliminary support for canceling database operations, including the ability to cancel an operation that is executing on a remote server in response to a client RMI request. For the time being, this feature is only exposed programatically in the Connection API via the cancel() method, which will terminate whatever operation is executing against the underlying session. When a command is canceled, Mulgara will attempt to terminate processing of the corresponding database operation and release any associated resources in a timely fashion, but no guarantees can be made as to how quickly this will happen.

Background

The main difficulty in canceling database operations on a Mulgara server lies in the way in which Java implements the handling of RMI calls. In the application code, neither the client or the server is aware of the presence of RMI; in general, the transport mechanism is hidden from the application. The client invokes a method on a remote object, and the RMI client stub will send a request to the server over a TCP socket and block waiting for a response; the RMI server stub dispatches the call to the appropriate method on the server object, which is generally not aware that it is executing in the context of an RMI call.

In order to work around this limitation, Mulgara uses a third-party library called Interruptible RMI to hook into the RMI transport layer. This library allows the client to cancel a call by closing the underlying TCP socket, and provides the server with hooks to check whether the socket is still alive. This does impose some additional overhead to the RMI communications, so this feature is disabled by default. See the following section for instructions on configuring Mulgara to enable canceling RMI operations.

When running an embedded Mulgara server (i.e. one with a local: URI) there is no such overhead in setting up RMI communications. Cancellation of database operations is implemented directly via the Thread.interrupt() method, and there is no need to do any additional configuration to enable this.

RMI Configuration

As mentioned in the previous section, cancellation of RMI operations is disabled by default in Mulgara. In order to enable this feature, both the Mulgara server and the ConnectionFactory client must be properly configured. The means of configuration for each component are listed below, in order from highest to lowest precedence:

Client Configuration

  • Use the ConnectionFactory(boolean useInterruptibleRmi) constructor to create a factory with interruptible RMI operations enabled or disabled based on the value of the flag passed to the constructor.
  • Set the application-wide default for all new ConnectionFactory instances in code via the setDefaultInterrupt(boolean) method of the org.mulgara.util.Rmi class.
  • Set the application-wide default for all new ConnectionFactory instances from the command line by setting the mulgara.rmi.interrupt system property to true or false.
  • If the default has not been set by any of these means, then interrupting RMI operations is disabled.

Server Configuration

  • If the mulgara.rmi.interrupt system property is set, then it is parsed as a boolean value to determine whether to enable interruptible RMI operations in the server.
  • If the server XML configuration file contains the <RMIInterrupt> element, then its boolean value is used to determine whether to enable interruptible RMI operations.
  • If neither of the above configuration options are set, then interruptible RMI operations are disabled in the server.

Note that Mulgara ships with a default server XML configuration file with a value of false for the <RMIInterrupt> element.

Example

When interruptible RMI operations are enabled as described in the previous section, the Connection.cancel() method can be used to cancel a command currently executing on the connection from another client thread. The following sample code illustrates a method that will start a query in a new thread, and wait up to 5 seconds for it to complete before canceling it:

 1   public Answer doQuery(String queryStr) throws [[QueryException]] {
 2     // Create the connection.
 3     [[ConnectionFactory]] factory = new [[ConnectionFactory]](true);
 4     final Connection conn = factory.newConnection(URI.create("rmi://localhost/server1"));
 5 
 6     // Parse the query.
 7     final Query query = new [[SparqlInterpreter]]().parseQuery(queryStr);
 8 
 9     // Use an array to hold the Answer from the anonymous inner class
10     final Answer[] answer = new Answer[] { null };
11     final [[QueryException]][] ex = new [[QueryException]][] { null };
12 
13     // Create the thread
14     Thread t = new Thread(new Runnable() {
15       public void run() {
16         try {
17           answerr0 = conn.execute(query);
18         } catch (QueryException qe) {
19           exr0 = qe;
20         }
21       }
22     });
23 
24     // Start the thread
25     t.start();
26 
27     // Wait for the thread to complete.
28     try {
29       t.join(5000);
30 
31       // Cancel the query running on the connection if the thread hasn't finished.
32       // This will cause a [[QueryException]] to be thrown by the Connection.execute(Query) method.
33       if (t.isAlive()) conn.cancel();
34 
35       // Even if we canceled the query, we still need to wait for the thread to die
36       // so the connection can finish cleaning up and throwing the canceled exception.
37       while (t.isAlive) t.join();
38     } catch (InterruptedException ie) {
39       // Shouldn't happen.
40       throw new [[QueryException]]("Interrupted waiting for query to finish", ie);
41     }
42 
43     // If we recorded an exception, throw it.
44     if (exr0 != null) throw exr0;
45 
46     // Otherwise, return the answer.
47     return answerr0;
48   }

For Developers

The Interruptible RMI library and the Connection.cancel() method provide a framework for canceling a Mulgara database server operation from an RMI client. However, in order to terminate processing and release resources in a timely manner, the server must periodically check whether it has been canceled and act accordingly. It is the responsibility of Mulgara developers to identify critical sections of the code for expensive server operations and perform this check. The org.mulgara.util.ThreadUtil class provides a checkForInterrupt() method to facilitate this process. It checks whether the current thread has been interrupted, either locally via the Thread.interrupt() method or remotely via the Interruptible RMI framework, and throws an exception of the requested exception type if the thread has been interrupted. In most cases, this thrown exception should be sufficient to quickly unwind the call stack and roll back the transaction. In this manner, adding a single line of code in strategic locations in the codebase can greatly improve the responsiveness of the Mulgara server to cancellation requests.