sqlite-jdbc
sqlite-jdbc copied to clipboard
Timeout handling is incompatible with Hibernate
(While Hibernate does not officially support SQLite, there are SQLiteDialect files for Hibernate floating around.)
I found that for some reason, whenever Hibernate closes a Statement, the last thing it does before calling close() on the Statement is that it calls setQueryTimeout(0) on it. Unfortunately, SQLite-JDBC interprets this as a setBusyTimeout(0) on the entire Connection. As a result, later requests on the same connection do not handle concurrency at all. (Hibernate treats the "database is locked" exception as basically a fatal error.) While Hibernate's behavior is strange, I think it is compliant to the JDBC spec, and SQLite-JDBC is doing the wrong thing.
The following patch works for me:
diff -ur sqlite-jdbc-3.8.11.2/org/sqlite/jdbc3/JDBC3Statement.java sqlite-jdbc-3.8.11.2-fix-timeout-handling/org/sqlite/jdbc3/JDBC3Statement.java
--- sqlite-jdbc-3.8.11.2/org/sqlite/jdbc3/JDBC3Statement.java 2015-09-14 13:04:26.000000000 +0200
+++ sqlite-jdbc-3.8.11.2-fix-timeout-handling/org/sqlite/jdbc3/JDBC3Statement.java 2016-02-03 19:12:05.359615675 +0100
@@ -15,10 +15,13 @@
import org.sqlite.core.DB.ProgressObserver;
public abstract class JDBC3Statement extends CoreStatement {
+ private int busyTimeout;
+
// PUBLIC INTERFACE /////////////////////////////////////////////
protected JDBC3Statement(SQLiteConnection conn) {
super(conn);
+ busyTimeout = -1;
}
/**
@@ -48,6 +51,9 @@
public boolean execute(String sql) throws SQLException {
internalClose();
+ if (busyTimeout >= 0)
+ conn.setBusyTimeout(busyTimeout);
+
SQLExtension ext = ExtendedCommand.parse(sql);
if (ext != null) {
ext.execute(db);
@@ -78,6 +84,9 @@
internalClose();
this.sql = sql;
+ if (busyTimeout >= 0)
+ conn.setBusyTimeout(busyTimeout);
+
db.prepare(this);
if (!exec()) {
@@ -102,6 +111,9 @@
internalClose();
this.sql = sql;
+ if (busyTimeout >= 0)
+ conn.setBusyTimeout(busyTimeout);
+
int changes = 0;
SQLExtension ext = ExtendedCommand.parse(sql);
if (ext != null) {
@@ -257,7 +269,7 @@
* @see java.sql.Statement#getQueryTimeout()
*/
public int getQueryTimeout() throws SQLException {
- return conn.getBusyTimeout();
+ return (busyTimeout >= 0 ? busyTimeout : conn.getBusyTimeout()) / 1000;
}
/**
@@ -266,7 +278,7 @@
public void setQueryTimeout(int seconds) throws SQLException {
if (seconds < 0)
throw new SQLException("query timeout must be >= 0");
- conn.setBusyTimeout(1000 * seconds);
+ busyTimeout = 1000 * seconds;
}
// TODO: write test
It delays calling setBusyTimeout until actually required, so that a setQueryTimeout(0) on a Statement about to be closed will have no effect. (It also fixes the return value of getQueryTimeout, which should be in seconds according to the spec.) This is still not perfect because setting the timeout on a Statement can still affect the whole Connection, but at least what Hibernate does does not break things anymore. (In my testing so far, I don't see the connection-wide timeout being messed with at all anymore. It looks like Hibernate does not normally set a query timeout except before closing the statement.)
There is actually a second issue here, which is that mapping setQueryTimeout(0) to conn.setBusyTimeout(0) is never correct to begin with. According to the javadoc for setQueryTimeout, "zero means there is no limit" (i.e., wait forever), whereas for SQLite, a busy_timeout of 0 means "do not wait at all", i.e., the exact opposite.
I'm submitting a better (more complete) fix as a pull request.
A complex query on a database used by only one process (no concurrent access, so no busy error) can time out/take a long time to execute. You should take a look at sqlite3_progress_handler.
Huh? Sorry, but I do not understand your reply. Have you read my detailed analysis of the issue? The issue is that the connection timeout is reset to 0 when the query happens, due to a prior call to setQueryTimeout(0) on a Statement. I verified this with a debugger. I also verified that my fix (both the original fix and the improved one I submitted as a pull request) makes the lock timeouts go away in my application, and I also verified in a debugger that it makes the root cause go away (the busy timeout is now the one that was set initially, not 0). I know what I am talking about, I spent a couple hours tracking this down in a debugger.
Now if what you are saying is that setting the busy timeout is not sufficient to comply with the specification for the query timeout, I agree that you are probably right. But what is sure is that my fix makes the code much closer to being compliant than what is currently there.
Hi, I think this can make the follow sql statement works well. I'll try it later " insert into table1(col1, col2) select col1, col2 from table2." .
I failed with got an error "java.sql.SQLException: database is locked". Don't know why.
I bumped into this problem too. Is there any particular reason why the pull request was never merged?
My commit is just a backport (cherry-pick) from pre-3.9.0 to 3.8.11.2 to document what we have been using in production (manually applied with patch) for years. You will probably want to merge @MartinHaeusler's updated patch instead.