persist_data: Retry database setup
PATCH 1/1 persist_data: Retry database setup
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]
--- bitbake/lib/bb/persist_data.py | 85 ++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 40 deletions(-)
bitbake/lib/bb/persist_data.py +45 -40
@@ -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
Signature · 1 line
2.19.2
Oops, wrong list. I will resend to the bitbake list.
Quoted 193 lines · Joshua Watt
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 <[email protected]>--- bitbake/lib/bb/persist_data.py | 85 ++++++++++++++++++------------ ---- 1 file changed, 45 insertions(+), 40 deletions(-)bitbake/lib/bb/persist_data.py +1
-1@@ -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 lockedsqlite
bitbake/lib/bb/persist_data.py +1
-1- exception occurs. + exception occurs. If reconnect is True, the databaseconnection
bitbake/lib/bb/persist_data.py +13
-16+ 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:
bitbake/lib/bb/persist_data.py +14
-2+ 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 (thedefault is
+ # usually 1000). Persistent caches are usually read-mostly,
so keepingbitbake/lib/bb/persist_data.py +9
-3+ # 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__isbitbake/lib/bb/persist_data.py +1
-1@@ -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])
bitbake/lib/bb/persist_data.py +2
-2@@ -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):bitbake/lib/bb/persist_data.py +2
-2@@ -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.transactiondef
__len__(self, cursor):
cursor.execute("SELECT COUNT(key) FROM %s;" % self.table)bitbake/lib/bb/persist_data.py +1
-1@@ -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)bitbake/lib/bb/persist_data.py +0
-4@@ -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 (thedefault is
- # usually 1000). Persistent caches are usually read-mostly, so
keepingbitbake/lib/bb/persist_data.py +0
-6- # 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 uponmetadata"""
import bb.utils
Signature · 1 line
Joshua Watt <[email protected]>
✗ patchtest: failure for persist_data: Retry database setup
In reply to Joshua Watt== 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 [email protected] [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 ([email protected]) 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