Skip to content

Commit 262b1f3

Browse files
authored
Merge pull request #3253 from vkarak/feat/sqlite_conn_timeout
[feat] Expose SQLite connection timeout as configuration parameter
2 parents 27944ed + 425fd67 commit 262b1f3

File tree

6 files changed

+59
-16
lines changed

6 files changed

+59
-16
lines changed

docs/config_reference.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1636,12 +1636,20 @@ Result storage configuration
16361636
Currently, only Sqlite can be used as a storage backend.
16371637

16381638

1639+
.. py:attribute:: storage.sqlite_conn_timeout
1640+
1641+
:required: No
1642+
:default: ``60``
1643+
1644+
Timeout in seconds for SQLite database connections.
1645+
1646+
16391647
.. py:attribute:: storage.sqlite_db_file
16401648
16411649
:required: No
16421650
:default: ``"${HOME}/.reframe/reports/results.db"``
16431651

1644-
The Sqlite database file to use.
1652+
The SQLite database file to use.
16451653

16461654

16471655
General Configuration

docs/manpage.rst

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2007,9 +2007,24 @@ Whenever an environment variable is associated with a configuration option, its
20072007
================================== ==================
20082008

20092009

2010+
.. envvar:: RFM_SQLITE_CONN_TIMEOUT
2011+
2012+
Timeout for SQLite database connections.
2013+
2014+
.. table::
2015+
:align: left
2016+
2017+
================================== ==================
2018+
Associated command line option N/A
2019+
Associated configuration parameter :attr:`~config.storage.sqlite_conn_timeout`
2020+
================================== ==================
2021+
2022+
.. versionadded:: 4.7
2023+
2024+
20102025
.. envvar:: RFM_SQLITE_DB_FILE
20112026

2012-
The SQlite database file for storing test results.
2027+
The SQLite database file for storing test results.
20132028

20142029
.. table::
20152030
:align: left

reframe/core/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# SPDX-License-Identifier: BSD-3-Clause
55

66
#
7-
# Generic fallback configuration
7+
# Builtin configuration
88
#
99

1010
site_configuration = {

reframe/frontend/cli.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -765,11 +765,17 @@ def main():
765765
action='store_true',
766766
help='Resolve module conflicts automatically'
767767
)
768+
argparser.add_argument(
769+
dest='sqlite_conn_timeout',
770+
envvar='RFM_SQLITE_CONN_TIMEOUT',
771+
configvar='storage/sqlite_conn_timeout',
772+
help='Timeout for DB connections (SQLite backend)'
773+
)
768774
argparser.add_argument(
769775
dest='sqlite_db_file',
770776
envvar='RFM_SQLITE_DB_FILE',
771777
configvar='storage/sqlite_db_file',
772-
help='DB file where the results database resides'
778+
help='DB file where the results database resides (SQLite backend)'
773779
)
774780
argparser.add_argument(
775781
dest='syslog_address',
@@ -1586,10 +1592,13 @@ def module_unuse(*paths):
15861592
data = reporting.performance_compare(
15871593
rt.get_option('general/0/perf_report_spec'), report
15881594
)
1589-
except errors.ReframeError as err:
1595+
except Exception as err:
15901596
printer.warning(
15911597
f'failed to generate performance report: {err}'
15921598
)
1599+
printer.verbose(
1600+
''.join(traceback.format_exception(*sys.exc_info()))
1601+
)
15931602
else:
15941603
printer.performance_report(data)
15951604

@@ -1631,6 +1640,9 @@ def module_unuse(*paths):
16311640
printer.warning(
16321641
f'failed to store results in the database: {e}'
16331642
)
1643+
printer.verbose(
1644+
''.join(traceback.format_exception(*sys.exc_info()))
1645+
)
16341646
else:
16351647
printer.info('Current session stored with UUID: '
16361648
f'{sess_uuid}')

reframe/frontend/reporting/storage.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,18 @@ def _db_matches(self, patt, item):
7272
regex = re.compile(patt)
7373
return regex.match(item) is not None
7474

75+
def _db_connect(self, *args, **kwargs):
76+
timeout = runtime().get_option('storage/0/sqlite_conn_timeout')
77+
kwargs.setdefault('timeout', timeout)
78+
with getprofiler().time_region('sqlite connect'):
79+
return sqlite3.connect(*args, **kwargs)
80+
7581
def _db_create(self):
7682
clsname = type(self).__name__
7783
getlogger().debug(
7884
f'{clsname}: creating results database in {self.__db_file}...'
7985
)
80-
with sqlite3.connect(self.__db_file) as conn:
86+
with self._db_connect(self.__db_file) as conn:
8187
conn.execute('CREATE TABLE IF NOT EXISTS sessions('
8288
'uuid TEXT PRIMARY KEY, '
8389
'session_start_unix REAL, '
@@ -100,13 +106,13 @@ def _db_create(self):
100106
'schema_version TEXT)')
101107

102108
def _db_schema_check(self):
103-
with sqlite3.connect(self.__db_file) as conn:
109+
with self._db_connect(self.__db_file) as conn:
104110
results = conn.execute(
105111
'SELECT schema_version FROM metadata').fetchall()
106112

107113
if not results:
108114
# DB is new, insert the schema version
109-
with sqlite3.connect(self.__db_file) as conn:
115+
with self._db_connect(self.__db_file) as conn:
110116
conn.execute('INSERT INTO metadata VALUES(:schema_version)',
111117
{'schema_version': self.SCHEMA_VERSION})
112118
else:
@@ -159,14 +165,14 @@ def _db_store_report(self, conn, report, report_file_path):
159165

160166
def store(self, report, report_file=None):
161167
prefix = os.path.dirname(self.__db_file)
162-
with sqlite3.connect(self._db_file()) as conn:
168+
with self._db_connect(self._db_file()) as conn:
163169
with FileLock(os.path.join(prefix, '.db.lock')):
164170
return self._db_store_report(conn, report, report_file)
165171

166172
@time_function
167173
def _fetch_testcases_raw(self, condition):
168174
getprofiler().enter_region('sqlite query')
169-
with sqlite3.connect(self._db_file()) as conn:
175+
with self._db_connect(self._db_file()) as conn:
170176
query = ('SELECT session_uuid, testcases.uuid as uuid, json_blob '
171177
'FROM testcases '
172178
'JOIN sessions ON session_uuid == sessions.uuid '
@@ -207,7 +213,7 @@ def _fetch_testcases_raw(self, condition):
207213

208214
@time_function
209215
def fetch_session_time_period(self, session_uuid):
210-
with sqlite3.connect(self._db_file()) as conn:
216+
with self._db_connect(self._db_file()) as conn:
211217
query = ('SELECT session_start_unix, session_end_unix '
212218
f'FROM sessions WHERE uuid == "{session_uuid}" '
213219
'LIMIT 1')
@@ -231,7 +237,7 @@ def fetch_testcases_time_period(self, ts_start, ts_end, name_pattern=None):
231237

232238
@time_function
233239
def fetch_testcases_from_session(self, session_uuid, name_pattern=None):
234-
with sqlite3.connect(self._db_file()) as conn:
240+
with self._db_connect(self._db_file()) as conn:
235241
query = ('SELECT json_blob from sessions '
236242
f'WHERE uuid == "{session_uuid}"')
237243
getlogger().debug(query)
@@ -246,7 +252,7 @@ def fetch_testcases_from_session(self, session_uuid, name_pattern=None):
246252

247253
@time_function
248254
def fetch_sessions_time_period(self, ts_start=None, ts_end=None):
249-
with sqlite3.connect(self._db_file()) as conn:
255+
with self._db_connect(self._db_file()) as conn:
250256
query = 'SELECT json_blob from sessions'
251257
if ts_start or ts_end:
252258
query += ' WHERE ('
@@ -269,7 +275,7 @@ def fetch_sessions_time_period(self, ts_start=None, ts_end=None):
269275

270276
@time_function
271277
def fetch_session_json(self, uuid):
272-
with sqlite3.connect(self._db_file()) as conn:
278+
with self._db_connect(self._db_file()) as conn:
273279
query = f'SELECT json_blob FROM sessions WHERE uuid == "{uuid}"'
274280
getlogger().debug(query)
275281
results = conn.execute(query).fetchall()
@@ -279,7 +285,7 @@ def fetch_session_json(self, uuid):
279285
def _do_remove(self, uuid):
280286
prefix = os.path.dirname(self.__db_file)
281287
with FileLock(os.path.join(prefix, '.db.lock')):
282-
with sqlite3.connect(self._db_file()) as conn:
288+
with self._db_connect(self._db_file()) as conn:
283289
# Check first if the uuid exists
284290
query = f'SELECT * FROM sessions WHERE uuid == "{uuid}"'
285291
getlogger().debug(query)
@@ -294,7 +300,7 @@ def _do_remove2(self, uuid):
294300
'''Remove a session using the RETURNING keyword'''
295301
prefix = os.path.dirname(self.__db_file)
296302
with FileLock(os.path.join(prefix, '.db.lock')):
297-
with sqlite3.connect(self._db_file()) as conn:
303+
with self._db_connect(self._db_file()) as conn:
298304
query = (f'DELETE FROM sessions WHERE uuid == "{uuid}" '
299305
'RETURNING *')
300306
getlogger().debug(query)

reframe/schemas/config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@
543543
"type": "object",
544544
"properties": {
545545
"backend": {"type": "string"},
546+
"sqlite_conn_timeout": {"type": "number"},
546547
"sqlite_db_file": {"type": "string"}
547548
}
548549
}
@@ -624,6 +625,7 @@
624625
"modes/options": [],
625626
"modes/target_systems": ["*"],
626627
"storage/backend": "sqlite",
628+
"storage/sqlite_conn_timeout": 60,
627629
"storage/sqlite_db_file": "${HOME}/.reframe/reports/results.db",
628630
"systems/descr": "",
629631
"systems/max_local_jobs": 8,

0 commit comments

Comments
 (0)