Skip to content

Commit

Permalink
Resolve 'database is locked' problem when opening a new connection
Browse files Browse the repository at this point in the history
The schema and static tables should only be initialized on the first
connection; ensuring the database is in WAL mode and setting up a
connection busy handler also should have been done immediately upon
connection rather than at the end of the connection process
  • Loading branch information
djp952 committed Mar 5, 2017
1 parent a412f3d commit 9bec22f
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 74 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ git clone https://github.com/djp952/external-sqlite -b sqlite-3.17.0 --depth=1
git clone https://github.com/djp952/prebuilt-libcurl -b libcurl-7.52.1 --depth=1
git clone https://github.com/djp952/prebuilt-libssl -b libssl-1.0.2k --depth=1
git clone https://github.com/djp952/prebuilt-libuuid -b libuuid-1.0.3 --depth=1
git clone https://github.com/djp952/prebuilt-libz -b libz-1.2.8 --depth=1
git clone https://github.com/djp952/pvr.hdhomerundvr -b Jarvis
cd pvr.hdhomerundvr
msbuild msbuild.proj
Expand Down
5 changes: 5 additions & 0 deletions pvr.hdhomerundvr/changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
v1.0.3
- Add "SD Channels" channel group
- Use IPv4 specific backend discovery URLs (ipv4.my.hdhomerun.com)
- Resolve issue with SQLite database being locked during initialization of a new connection

v1.0.2
- Add option to enable discovery of extended (8 hour) Electronic Program Guide data
- Update SQLite database engine to v3.17.0
Expand Down
162 changes: 92 additions & 70 deletions src/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@ connectionpool::connectionpool(char const* connstring, int flags) : m_connstr((c

if(connstring == nullptr) throw std::invalid_argument("connstring");

// Create and pool an initial connection now to give the caller an opportunity
// to catch any exceptions during construction of the connection pool
handle = open_database(m_connstr.c_str(), m_flags);
// Create and pool the initial connection now to give the caller an opportunity
// to catch any exceptions during initialization of the database
handle = open_database(m_connstr.c_str(), m_flags, true);
m_connections.push_back(handle);
m_queue.push(handle);
}
Expand Down Expand Up @@ -205,9 +205,8 @@ sqlite3* connectionpool::acquire(void)

if(m_queue.empty()) {

// No connections are available, open a new one using the same connection data
// and add it to the vector<> of all active connection objects
handle = open_database(m_connstr.c_str(), m_flags);
// No connections are available, open a new one using the same flags
handle = open_database(m_connstr.c_str(), m_flags, false);
m_connections.push_back(handle);
}

Expand Down Expand Up @@ -2523,18 +2522,43 @@ void modify_recordingrule(sqlite3* instance, struct recordingrule const& recordi
// flags - Database open flags (see sqlite3_open_v2)

sqlite3* open_database(char const* connstring, int flags)
{
return open_database(connstring, flags, false);
}

//---------------------------------------------------------------------------
// open_database
//
// Opens the SQLite database instance
//
// Arguments:
//
// connstring - Database connection string
// flags - Database open flags (see sqlite3_open_v2)
// initialize - Flag indicating database schema should be (re)initialized

sqlite3* open_database(char const* connstring, int flags, bool initialize)
{
sqlite3* instance = nullptr; // SQLite database instance

// Create the SQLite database using the provided connection string
int result = sqlite3_open_v2(connstring, &instance, flags, nullptr);
if(result != SQLITE_OK) throw sqlite_exception(result);

// Set the connection to report extended error codes
// set the connection to report extended error codes
//
sqlite3_extended_result_codes(instance, -1);

// set a busy_timeout handler for this connection
//
sqlite3_busy_timeout(instance, 30000);

try {

// switch the database to write-ahead logging
//
execute_non_query(instance, "pragma journal_mode=wal");

// scalar function: decode_channel_id
//
result = sqlite3_create_function_v2(instance, "decode_channel_id", 1, SQLITE_UTF8, nullptr, decode_channel_id, nullptr, nullptr, nullptr);
Expand Down Expand Up @@ -2580,70 +2604,68 @@ sqlite3* open_database(char const* connstring, int flags)
result = sqlite3_create_function_v2(instance, "url_encode", 1, SQLITE_UTF8, nullptr, url_encode, nullptr, nullptr, nullptr);
if(result != SQLITE_OK) throw sqlite_exception(result);

// table: client
// Only execute schema creation steps if the database is being initialized; the caller needs
// to ensure that this is set for only one connection otherwise locking issues can occur
//
// clientid(pk)
execute_non_query(instance, "create table if not exists client(clientid text primary key not null)");

// table: device
//
// deviceid(pk) | type | data
execute_non_query(instance, "create table if not exists device(deviceid text primary key not null, type text, data text)");

// table: lineup
//
// deviceid(pk) | data
execute_non_query(instance, "create table if not exists lineup(deviceid text primary key not null, data text)");

// table: recording
//
// deviceid(pk) | data
execute_non_query(instance, "create table if not exists recording(deviceid text primary key not null, data text)");

// table: guide
//
// channelid(pk) | channelname | iconurl | data
execute_non_query(instance, "create table if not exists guide(channelid integer primary key not null, channelname text, iconurl text, data text)");

// table: recordingrule
//
// recordingruleid(pk) | data
execute_non_query(instance, "create table if not exists recordingrule(recordingruleid text primary key not null, seriesid text not null, data text)");

// table: episode
//
// seriesid(pk) | data
execute_non_query(instance, "create table if not exists episode(seriesid text primary key not null, data text)");

// table: genremap
//
// filter(pk) | genretype
execute_non_query(instance, "create table if not exists genremap(filter text primary key not null, genretype integer)");

// (re)generate the clientid
//
execute_non_query(instance, "delete from client");
execute_non_query(instance, "insert into client values(generate_uuid())");

// (re)build the genremap table
//
sqlite3_exec(instance, "replace into genremap values('Movies', 0x10)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_MOVIEDRAMA
sqlite3_exec(instance, "replace into genremap values('News', 0x20)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_NEWSCURRENTAFFAIRS
sqlite3_exec(instance, "replace into genremap values('Comedy', 0x30)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SHOW
sqlite3_exec(instance, "replace into genremap values('Drama', 0x30)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SHOW
sqlite3_exec(instance, "replace into genremap values('Game Show', 0x30)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SHOW
sqlite3_exec(instance, "replace into genremap values('Talk Show', 0x30)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SHOW
sqlite3_exec(instance, "replace into genremap values('Sports', 0x40)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SPORTS
sqlite3_exec(instance, "replace into genremap values('Kids', 0x50)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_CHILDRENYOUTH
sqlite3_exec(instance, "replace into genremap values('Food', 0xA0)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_LEISUREHOBBIES

// set a busy_timeout handler for this connection
//
sqlite3_busy_timeout(instance, 30000);

// switch the database to write-ahead logging
//
execute_non_query(instance, "pragma journal_mode=wal");
if(initialize) {

// table: client
//
// clientid(pk)
execute_non_query(instance, "create table if not exists client(clientid text primary key not null)");

// table: device
//
// deviceid(pk) | type | data
execute_non_query(instance, "create table if not exists device(deviceid text primary key not null, type text, data text)");

// table: lineup
//
// deviceid(pk) | data
execute_non_query(instance, "create table if not exists lineup(deviceid text primary key not null, data text)");

// table: recording
//
// deviceid(pk) | data
execute_non_query(instance, "create table if not exists recording(deviceid text primary key not null, data text)");

// table: guide
//
// channelid(pk) | channelname | iconurl | data
execute_non_query(instance, "create table if not exists guide(channelid integer primary key not null, channelname text, iconurl text, data text)");

// table: recordingrule
//
// recordingruleid(pk) | data
execute_non_query(instance, "create table if not exists recordingrule(recordingruleid text primary key not null, seriesid text not null, data text)");

// table: episode
//
// seriesid(pk) | data
execute_non_query(instance, "create table if not exists episode(seriesid text primary key not null, data text)");

// table: genremap
//
// filter(pk) | genretype
execute_non_query(instance, "create table if not exists genremap(filter text primary key not null, genretype integer)");

// (re)generate the clientid
//
execute_non_query(instance, "delete from client");
execute_non_query(instance, "insert into client values(generate_uuid())");

// (re)build the genremap table
//
sqlite3_exec(instance, "replace into genremap values('Movies', 0x10)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_MOVIEDRAMA
sqlite3_exec(instance, "replace into genremap values('News', 0x20)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_NEWSCURRENTAFFAIRS
sqlite3_exec(instance, "replace into genremap values('Comedy', 0x30)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SHOW
sqlite3_exec(instance, "replace into genremap values('Drama', 0x30)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SHOW
sqlite3_exec(instance, "replace into genremap values('Game Show', 0x30)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SHOW
sqlite3_exec(instance, "replace into genremap values('Talk Show', 0x30)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SHOW
sqlite3_exec(instance, "replace into genremap values('Sports', 0x40)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_SPORTS
sqlite3_exec(instance, "replace into genremap values('Kids', 0x50)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_CHILDRENYOUTH
sqlite3_exec(instance, "replace into genremap values('Food', 0xA0)", nullptr, nullptr, nullptr); // EPG_EVENT_CONTENTMASK_LEISUREHOBBIES
}
}

// Close the database instance on any thrown exceptions
Expand Down
1 change: 1 addition & 0 deletions src/database.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ void modify_recordingrule(sqlite3* instance, struct recordingrule const& recordi
//
// Opens a handle to the backend SQLite database
sqlite3* open_database(char const* connstring, int flags);
sqlite3* open_database(char const* connstring, int flags, bool initialize);

// try_execute_non_query
//
Expand Down
10 changes: 7 additions & 3 deletions src/sqlite_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@
//
// code - SQLite error code

sqlite_exception::sqlite_exception(int code) : m_what(sqlite3_errstr(code))
sqlite_exception::sqlite_exception(int code)
{
char* what = sqlite3_mprintf("(%d) %s", code, sqlite3_errstr(code));
m_what.assign(what);
sqlite3_free(reinterpret_cast<void*>(what));
}

//-----------------------------------------------------------------------------
Expand All @@ -46,8 +49,9 @@ sqlite_exception::sqlite_exception(int code) : m_what(sqlite3_errstr(code))

sqlite_exception::sqlite_exception(int code, char const* message) : m_what(sqlite3_errstr(code))
{
// If a message was provided, append it to the generated error message
if(message) m_what += std::string(": ") + message;
char* what = sqlite3_mprintf("%s: (%d) %s", message, code, sqlite3_errstr(code));
m_what.assign(what);
sqlite3_free(reinterpret_cast<void*>(what));
}

//-----------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/version.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
Company=Michael G. Brehm
Copyright=
Product=zuki.pvr.hdhomerundvr
Version=1.0.2
Version=1.0.3

0 comments on commit 9bec22f

Please sign in to comment.