Is there a way to cancel the copying process started by calling copyIn()
method in a separate thread?
Say, I have a list of csv-files which I need to co
PostgreSQL doesn't actually support in-band query cancels.
When you request a query cancel from the JDBC driver it makes a new connection to send the cancel message. (This means that if you're at max_connections
a cancel will fail, which is kind of perverse).
The upshot of this is that you can do the same thing yourself:
Use pg_backend_pid()
to get the process ID of the worker before starting the copy operation;
When you want to cancel a copy, open a new connection and issue pg_cancel_backend(?)
with the pid recorded earlier. If it doesn't stop, you can wait a bit then do a pg_terminate_backend(?)
.
These are ordinary SQL-level functions.
The only real issue is that the cancel and terminate requests are session-level not statement level. So they can race with statement completion and the start of a new statement, eg:
At this point, the second copy will be terminated, which may not be what you wanted. So you must make sure to use appropriate exclusion client-side to prevent this from happening, making sure any outstanding cancel requests are finished before starting a new statement.
IIRC the JDBC driver has the same issue internally anyway. It's one of the reasons the team really want a way to cancel a particular unique per-session statement sequence number, like a (currently non-existent) pg_cancel_backend(pid, statementnumber)
that aborts with an error if the statement has already terminated, instead of sending the cancel anyway.
Disclaimer: I haven't tried this and I just got the idea by looking at the source code
There is a CopyManager.copyIn(String sql)
method that returns an instance of the CopyIn
interface which in turn is a descendant of CopyOperation
. That interface has a cancelCopy()
method.
See the JavaDocs here: http://jdbc.postgresql.org/documentation/publicapi/org/postgresql/copy/CopyOperation.html#cancelCopy%28%29
But the methods that take a stream to copy the data only return a long value, so no way of using the instance of the CopyOperation used there.
However, when looking at the source code of the copyIn() method this seems quite easy to do on your own.
So instead of calling copyIn(String, Reader)
you basically use the code from that method in your code:
// your code
CopyManager copyManager =
new CopyManager((BaseConnection) new DataSource().connect());
FileReader from = ... // different name!
int bufferSize = 65536;
// here starts the copy of the driver's implementation of the copyIn() method.
char[] cbuf = new char[bufferSize];
int len;
// if you store the instance of the CopyIn interface in an instance variable you
// should be able to call cancelCopy() on it
CopyIn cp = copyManager.copyIn(sql);
try {
while ( (len = from.read(cbuf)) > 0) {
byte[] buf = encoding.encode(new String(cbuf, 0, len));
cp.writeToCopy(buf, 0, buf.length);
}
return cp.endCopy();
} finally { // see to it that we do not leave the connection locked
if(cp.isActive())
cp.cancelCopy();
}