Database connection pooling

Reusing connections is a win with asynchronous transactions.  More
fine-tuning in the future of cache pages and such will help improve
Geary's database performance.

Also fixed a naming and namespace problem with Geary.DatabaseError
and a hole where, if a connection could not be opened (for resource
problems, most likely), the asynchronous transaction would never
complete.
This commit is contained in:
Jim Nelson 2012-07-12 19:17:12 -07:00
parent a047ceb698
commit af9a2c7f63
4 changed files with 46 additions and 12 deletions

View file

@ -45,11 +45,11 @@ engine/api/geary-special-folder-type.vala
engine/common/common-message-data.vala
engine/db/database-error.vala
engine/db/db.vala
engine/db/db-connection.vala
engine/db/db-context.vala
engine/db/db-database.vala
engine/db/db-database-error.vala
engine/db/db-result.vala
engine/db/db-statement.vala
engine/db/db-synchronous-mode.vala

View file

@ -4,7 +4,7 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public errordomain DatabaseError {
public errordomain Geary.DatabaseError {
GENERAL,
OPEN_REQUIRED,
BUSY,

View file

@ -40,6 +40,7 @@ public class Geary.Db.Database : Geary.Db.Context {
private Connection? master_connection = null;
private int outstanding_async_jobs = 0;
private ThreadPool<TransactionAsyncJob>? thread_pool = null;
private Gee.LinkedList<Connection>? cx_pool = null;
private unowned PrepareConnection? prepare_cb = null;
public Database(File db_file) {
@ -76,9 +77,13 @@ public class Geary.Db.Database : Geary.Db.Context {
}
if (threadsafe()) {
assert(thread_pool == null);
thread_pool = new ThreadPool<TransactionAsyncJob>.with_owned_data(on_async_job,
DEFAULT_MAX_CONCURRENCY, true);
if (thread_pool == null) {
thread_pool = new ThreadPool<TransactionAsyncJob>.with_owned_data(on_async_job,
DEFAULT_MAX_CONCURRENCY, true);
}
if (cx_pool == null)
cx_pool = new Gee.LinkedList<Connection>();
} else {
warning("SQLite not thread-safe: asynchronous queries will not be available");
}
@ -101,6 +106,9 @@ public class Geary.Db.Database : Geary.Db.Context {
// drop the master connection, which holds a ref back to this object
master_connection = null;
// As per the contract above, can't simply drop the thread and connection pools; that would
// be bad.
is_open = false;
}
@ -220,23 +228,37 @@ public class Geary.Db.Database : Geary.Db.Context {
// This method must be thread-safe.
private void on_async_job(owned TransactionAsyncJob job) {
// create connection for this thread
// TODO: Use connection pool -- *never* use master connection
// go to connection pool before creating a connection -- *never* use master connection for
// threaded operations
Connection? cx = null;
try {
cx = open_connection();
} catch (Error err) {
debug("Warning: unable to open database connection to %s, cancelling AsyncJob: %s",
db_file.get_path(), err.message);
lock (cx_pool) {
cx = cx_pool.poll();
}
Error? open_err = null;
if (cx == null) {
try {
cx = open_connection();
} catch (Error err) {
open_err = err;
debug("Warning: unable to open database connection to %s, cancelling AsyncJob: %s",
db_file.get_path(), err.message);
}
}
if (cx != null)
job.execute(cx);
else
job.failed(open_err);
lock (outstanding_async_jobs) {
assert(outstanding_async_jobs > 0);
--outstanding_async_jobs;
}
lock (cx_pool) {
cx_pool.offer(cx);
}
}
public override Database? get_database() {

View file

@ -42,6 +42,18 @@ private class Geary.Db.TransactionAsyncJob : Object {
caught_err = err;
}
schedule_completion();
}
// Called in background thread context
internal void failed(Error err) {
// store as a caught thread to report to original caller
caught_err = err;
schedule_completion();
}
private void schedule_completion() {
// notify foreground thread of completion
// because Idle doesn't hold a ref, manually keep this object alive
ref();