* [PATCH] persist_data: Retry database setup
@ 2018-12-07 21:50 Joshua Watt
2018-12-07 21:56 ` Joshua Watt
2018-12-07 22:03 ` ✗ patchtest: failure for " Patchwork
0 siblings, 2 replies; 3+ messages in thread
From: Joshua Watt @ 2018-12-07 21:50 UTC (permalink / raw)
To: openembedded-core
The configuration of the sqlite database can timeout due to locking
under heavy load and should be subject to the same retry logic as the
other statements.
[YOCTO #13069]
Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
---
bitbake/lib/bb/persist_data.py | 85 ++++++++++++++++++----------------
1 file changed, 45 insertions(+), 40 deletions(-)
diff --git a/bitbake/lib/bb/persist_data.py b/bitbake/lib/bb/persist_data.py
index 29feb78203b..4b37227cfd1 100644
--- a/bitbake/lib/bb/persist_data.py
+++ b/bitbake/lib/bb/persist_data.py
@@ -66,27 +66,31 @@ def _remove_table_weakref(ref):
class SQLTable(collections.MutableMapping):
class _Decorators(object):
@staticmethod
- def retry(f):
+ def retry(*, reconnect=True):
"""
Decorator that restarts a function if a database locked sqlite
- exception occurs.
+ exception occurs. If reconnect is True, the database connection
+ will be closed and reopened each time a failure occurs
"""
- def wrap_func(self, *args, **kwargs):
- # Reconnect if necessary
- if self.connection is None:
- self.reconnect()
-
- count = 0
- while True:
- try:
- return f(self, *args, **kwargs)
- except sqlite3.OperationalError as exc:
- if 'is locked' in str(exc) and count < 500:
- count = count + 1
- self.reconnect()
- continue
- raise
- return wrap_func
+ def retry_wrapper(f):
+ def wrap_func(self, *args, **kwargs):
+ # Reconnect if necessary
+ if self.connection is None and reconnect:
+ self.reconnect()
+
+ count = 0
+ while True:
+ try:
+ return f(self, *args, **kwargs)
+ except sqlite3.OperationalError as exc:
+ if 'is locked' in str(exc) and count < 500:
+ count = count + 1
+ if reconnect:
+ self.reconnect()
+ continue
+ raise
+ return wrap_func
+ return retry_wrapper
@staticmethod
def transaction(f):
@@ -113,16 +117,28 @@ class SQLTable(collections.MutableMapping):
def __init__(self, cachefile, table):
self.cachefile = cachefile
self.table = table
- self.connection = connect(self.cachefile)
-
+ self.connection = None
self._execute_single("CREATE TABLE IF NOT EXISTS %s(key TEXT PRIMARY KEY NOT NULL, value TEXT);" % table)
+
+ @_Decorators.retry(reconnect=False)
+ @_Decorators.transaction
+ def _setup_database(self, cursor):
+ cursor.execute("pragma synchronous = off;")
+ # Enable WAL and keep the autocheckpoint length small (the default is
+ # usually 1000). Persistent caches are usually read-mostly, so keeping
+ # this short will keep readers running quickly
+ cursor.execute("pragma journal_mode = WAL;")
+ cursor.execute("pragma wal_autocheckpoint = 100;")
+
def reconnect(self):
if self.connection is not None:
self.connection.close()
- self.connection = connect(self.cachefile)
+ self.connection = sqlite3.connect(self.cachefile, timeout=5)
+ self.connection.text_factory = str
+ self._setup_database()
- @_Decorators.retry
+ @_Decorators.retry()
@_Decorators.transaction
def _execute_single(self, cursor, *query):
"""
@@ -131,7 +147,7 @@ class SQLTable(collections.MutableMapping):
"""
cursor.execute(*query)
- @_Decorators.retry
+ @_Decorators.retry()
def _row_iter(self, f, *query):
"""
Helper function that returns a row iterator. Each time __next__ is
@@ -173,7 +189,7 @@ class SQLTable(collections.MutableMapping):
def __exit__(self, *excinfo):
self.connection.__exit__(*excinfo)
- @_Decorators.retry
+ @_Decorators.retry()
@_Decorators.transaction
def __getitem__(self, cursor, key):
cursor.execute("SELECT * from %s where key=?;" % self.table, [key])
@@ -182,14 +198,14 @@ class SQLTable(collections.MutableMapping):
return row[1]
raise KeyError(key)
- @_Decorators.retry
+ @_Decorators.retry()
@_Decorators.transaction
def __delitem__(self, cursor, key):
if key not in self:
raise KeyError(key)
cursor.execute("DELETE from %s where key=?;" % self.table, [key])
- @_Decorators.retry
+ @_Decorators.retry()
@_Decorators.transaction
def __setitem__(self, cursor, key, value):
if not isinstance(key, str):
@@ -204,13 +220,13 @@ class SQLTable(collections.MutableMapping):
else:
cursor.execute("INSERT into %s(key, value) values (?, ?);" % self.table, [key, value])
- @_Decorators.retry
+ @_Decorators.retry()
@_Decorators.transaction
def __contains__(self, cursor, key):
cursor.execute('SELECT * from %s where key=?;' % self.table, [key])
return cursor.fetchone() is not None
- @_Decorators.retry
+ @_Decorators.retry()
@_Decorators.transaction
def __len__(self, cursor):
cursor.execute("SELECT COUNT(key) FROM %s;" % self.table)
@@ -245,7 +261,7 @@ class SQLTable(collections.MutableMapping):
return self._row_iter(lambda row: (row[0], row[1]), "SELECT * FROM %s;" %
self.table)
- @_Decorators.retry
+ @_Decorators.retry()
@_Decorators.transaction
def clear(self, cursor):
cursor.execute("DELETE FROM %s;" % self.table)
@@ -302,17 +318,6 @@ class PersistData(object):
"""
del self.data[domain][key]
-def connect(database):
- connection = sqlite3.connect(database, timeout=5)
- connection.execute("pragma synchronous = off;")
- # Enable WAL and keep the autocheckpoint length small (the default is
- # usually 1000). Persistent caches are usually read-mostly, so keeping
- # this short will keep readers running quickly
- connection.execute("pragma journal_mode = WAL;")
- connection.execute("pragma wal_autocheckpoint = 100;")
- connection.text_factory = str
- return connection
-
def persist(domain, d):
"""Convenience factory for SQLTable objects based upon metadata"""
import bb.utils
--
2.19.2
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH] persist_data: Retry database setup
2018-12-07 21:50 [PATCH] persist_data: Retry database setup Joshua Watt
@ 2018-12-07 21:56 ` Joshua Watt
2018-12-07 22:03 ` ✗ patchtest: failure for " Patchwork
1 sibling, 0 replies; 3+ messages in thread
From: Joshua Watt @ 2018-12-07 21:56 UTC (permalink / raw)
To: openembedded-core
Oops, wrong list. I will resend to the bitbake list.
On Fri, 2018-12-07 at 15:50 -0600, Joshua Watt wrote:
> The configuration of the sqlite database can timeout due to locking
> under heavy load and should be subject to the same retry logic as the
> other statements.
>
> [YOCTO #13069]
>
> Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
> ---
> bitbake/lib/bb/persist_data.py | 85 ++++++++++++++++++------------
> ----
> 1 file changed, 45 insertions(+), 40 deletions(-)
>
> diff --git a/bitbake/lib/bb/persist_data.py
> b/bitbake/lib/bb/persist_data.py
> index 29feb78203b..4b37227cfd1 100644
> --- a/bitbake/lib/bb/persist_data.py
> +++ b/bitbake/lib/bb/persist_data.py
> @@ -66,27 +66,31 @@ def _remove_table_weakref(ref):
> class SQLTable(collections.MutableMapping):
> class _Decorators(object):
> @staticmethod
> - def retry(f):
> + def retry(*, reconnect=True):
> """
> Decorator that restarts a function if a database locked
> sqlite
> - exception occurs.
> + exception occurs. If reconnect is True, the database
> connection
> + will be closed and reopened each time a failure occurs
> """
> - def wrap_func(self, *args, **kwargs):
> - # Reconnect if necessary
> - if self.connection is None:
> - self.reconnect()
> -
> - count = 0
> - while True:
> - try:
> - return f(self, *args, **kwargs)
> - except sqlite3.OperationalError as exc:
> - if 'is locked' in str(exc) and count < 500:
> - count = count + 1
> - self.reconnect()
> - continue
> - raise
> - return wrap_func
> + def retry_wrapper(f):
> + def wrap_func(self, *args, **kwargs):
> + # Reconnect if necessary
> + if self.connection is None and reconnect:
> + self.reconnect()
> +
> + count = 0
> + while True:
> + try:
> + return f(self, *args, **kwargs)
> + except sqlite3.OperationalError as exc:
> + if 'is locked' in str(exc) and count <
> 500:
> + count = count + 1
> + if reconnect:
> + self.reconnect()
> + continue
> + raise
> + return wrap_func
> + return retry_wrapper
>
> @staticmethod
> def transaction(f):
> @@ -113,16 +117,28 @@ class SQLTable(collections.MutableMapping):
> def __init__(self, cachefile, table):
> self.cachefile = cachefile
> self.table = table
> - self.connection = connect(self.cachefile)
> -
> + self.connection = None
> self._execute_single("CREATE TABLE IF NOT EXISTS %s(key TEXT
> PRIMARY KEY NOT NULL, value TEXT);" % table)
>
> +
> + @_Decorators.retry(reconnect=False)
> + @_Decorators.transaction
> + def _setup_database(self, cursor):
> + cursor.execute("pragma synchronous = off;")
> + # Enable WAL and keep the autocheckpoint length small (the
> default is
> + # usually 1000). Persistent caches are usually read-mostly,
> so keeping
> + # this short will keep readers running quickly
> + cursor.execute("pragma journal_mode = WAL;")
> + cursor.execute("pragma wal_autocheckpoint = 100;")
> +
> def reconnect(self):
> if self.connection is not None:
> self.connection.close()
> - self.connection = connect(self.cachefile)
> + self.connection = sqlite3.connect(self.cachefile, timeout=5)
> + self.connection.text_factory = str
> + self._setup_database()
>
> - @_Decorators.retry
> + @_Decorators.retry()
> @_Decorators.transaction
> def _execute_single(self, cursor, *query):
> """
> @@ -131,7 +147,7 @@ class SQLTable(collections.MutableMapping):
> """
> cursor.execute(*query)
>
> - @_Decorators.retry
> + @_Decorators.retry()
> def _row_iter(self, f, *query):
> """
> Helper function that returns a row iterator. Each time
> __next__ is
> @@ -173,7 +189,7 @@ class SQLTable(collections.MutableMapping):
> def __exit__(self, *excinfo):
> self.connection.__exit__(*excinfo)
>
> - @_Decorators.retry
> + @_Decorators.retry()
> @_Decorators.transaction
> def __getitem__(self, cursor, key):
> cursor.execute("SELECT * from %s where key=?;" % self.table,
> [key])
> @@ -182,14 +198,14 @@ class SQLTable(collections.MutableMapping):
> return row[1]
> raise KeyError(key)
>
> - @_Decorators.retry
> + @_Decorators.retry()
> @_Decorators.transaction
> def __delitem__(self, cursor, key):
> if key not in self:
> raise KeyError(key)
> cursor.execute("DELETE from %s where key=?;" % self.table,
> [key])
>
> - @_Decorators.retry
> + @_Decorators.retry()
> @_Decorators.transaction
> def __setitem__(self, cursor, key, value):
> if not isinstance(key, str):
> @@ -204,13 +220,13 @@ class SQLTable(collections.MutableMapping):
> else:
> cursor.execute("INSERT into %s(key, value) values (?,
> ?);" % self.table, [key, value])
>
> - @_Decorators.retry
> + @_Decorators.retry()
> @_Decorators.transaction
> def __contains__(self, cursor, key):
> cursor.execute('SELECT * from %s where key=?;' % self.table,
> [key])
> return cursor.fetchone() is not None
>
> - @_Decorators.retry
> + @_Decorators.retry()
> @_Decorators.transaction
> def __len__(self, cursor):
> cursor.execute("SELECT COUNT(key) FROM %s;" % self.table)
> @@ -245,7 +261,7 @@ class SQLTable(collections.MutableMapping):
> return self._row_iter(lambda row: (row[0], row[1]), "SELECT
> * FROM %s;" %
> self.table)
>
> - @_Decorators.retry
> + @_Decorators.retry()
> @_Decorators.transaction
> def clear(self, cursor):
> cursor.execute("DELETE FROM %s;" % self.table)
> @@ -302,17 +318,6 @@ class PersistData(object):
> """
> del self.data[domain][key]
>
> -def connect(database):
> - connection = sqlite3.connect(database, timeout=5)
> - connection.execute("pragma synchronous = off;")
> - # Enable WAL and keep the autocheckpoint length small (the
> default is
> - # usually 1000). Persistent caches are usually read-mostly, so
> keeping
> - # this short will keep readers running quickly
> - connection.execute("pragma journal_mode = WAL;")
> - connection.execute("pragma wal_autocheckpoint = 100;")
> - connection.text_factory = str
> - return connection
> -
> def persist(domain, d):
> """Convenience factory for SQLTable objects based upon
> metadata"""
> import bb.utils
--
Joshua Watt <JPEWhacker@gmail.com>
^ permalink raw reply [flat|nested] 3+ messages in thread* ✗ patchtest: failure for persist_data: Retry database setup
2018-12-07 21:50 [PATCH] persist_data: Retry database setup Joshua Watt
2018-12-07 21:56 ` Joshua Watt
@ 2018-12-07 22:03 ` Patchwork
1 sibling, 0 replies; 3+ messages in thread
From: Patchwork @ 2018-12-07 22:03 UTC (permalink / raw)
To: Joshua Watt; +Cc: openembedded-core
== Series Details ==
Series: persist_data: Retry database setup
Revision: 1
URL : https://patchwork.openembedded.org/series/15264/
State : failure
== Summary ==
Thank you for submitting this patch series to OpenEmbedded Core. This is
an automated response. Several tests have been executed on the proposed
series by patchtest resulting in the following failures:
* Issue Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists [test_target_mailing_list]
Suggested fix Send the series again to the correct mailing list (ML)
Suggested ML bitbake-devel@lists.openembedded.org [http://git.openembedded.org/bitbake/]
Patch's path: bitbake/lib/bb/persist_data.py
* Issue Series does not apply on top of target branch [test_series_merge_on_head]
Suggested fix Rebase your series on top of targeted branch
Targeted branch master (currently at c6de27c2f6)
If you believe any of these test results are incorrect, please reply to the
mailing list (openembedded-core@lists.openembedded.org) raising your concerns.
Otherwise we would appreciate you correcting the issues and submitting a new
version of the patchset if applicable. Please ensure you add/increment the
version number when sending the new version (i.e. [PATCH] -> [PATCH v2] ->
[PATCH v3] -> ...).
---
Guidelines: https://www.openembedded.org/wiki/Commit_Patch_Message_Guidelines
Test framework: http://git.yoctoproject.org/cgit/cgit.cgi/patchtest
Test suite: http://git.yoctoproject.org/cgit/cgit.cgi/patchtest-oe
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2018-12-07 22:03 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2018-12-07 21:50 [PATCH] persist_data: Retry database setup Joshua Watt
2018-12-07 21:56 ` Joshua Watt
2018-12-07 22:03 ` ✗ patchtest: failure for " Patchwork
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox