[OE-core] [PATCH] persist_data: Retry database setup
Joshua Watt
jpewhacker at gmail.com
Fri Dec 7 21:56:36 UTC 2018
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 at 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 at gmail.com>
More information about the Openembedded-core
mailing list