From 6b117e558f5f902090bb6addeb091135cb8b1749 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Thu, 24 Jan 2019 19:54:53 -0500 Subject: [PATCH 1/2] Use one conenction per thread to avoid tricky race conditions. (The ref counter is simply not good enough.) --- coverage/sqldata.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/coverage/sqldata.py b/coverage/sqldata.py index 91ad3cd5e..67e9da01f 100644 --- a/coverage/sqldata.py +++ b/coverage/sqldata.py @@ -16,6 +16,7 @@ import os import sqlite3 import sys +import threading from coverage.backward import iitems from coverage.data import filename_suffix @@ -84,7 +85,7 @@ def __init__(self, basename=None, suffix=None, warn=None, debug=None): self._choose_filename() self._file_map = {} - self._db = None + self._dbs = {} self._pid = os.getpid() # Are we in sync with the data file? @@ -103,9 +104,10 @@ def _choose_filename(self): self.filename += "." + suffix def _reset(self): - if self._db is not None: - self._db.close() - self._db = None + if self._dbs: + for db in self._dbs.values(): + db.close() + self._dbs = {} self._file_map = {} self._have_used = False self._current_context_id = None @@ -113,14 +115,14 @@ def _reset(self): def _create_db(self): if self._debug.should('dataio'): self._debug.write("Creating data file {!r}".format(self.filename)) - self._db = Sqlite(self.filename, self._debug) - with self._db: + self._dbs[threading.get_ident()] = Sqlite(self.filename, self._debug) + with self._dbs[threading.get_ident()] as db: for stmt in SCHEMA.split(';'): stmt = " ".join(stmt.strip().split()) if stmt: - self._db.execute(stmt) - self._db.execute("insert into coverage_schema (version) values (?)", (SCHEMA_VERSION,)) - self._db.execute( + db.execute(stmt) + db.execute("insert into coverage_schema (version) values (?)", (SCHEMA_VERSION,)) + db.execute( "insert into meta (has_lines, has_arcs, sys_argv) values (?, ?, ?)", (self._has_lines, self._has_arcs, str(getattr(sys, 'argv', None))) ) @@ -128,10 +130,10 @@ def _create_db(self): def _open_db(self): if self._debug.should('dataio'): self._debug.write("Opening data file {!r}".format(self.filename)) - self._db = Sqlite(self.filename, self._debug) - with self._db: + self._dbs[threading.get_ident()] = Sqlite(self.filename, self._debug) + with self._dbs[threading.get_ident()] as db: try: - schema_version, = self._db.execute("select version from coverage_schema").fetchone() + schema_version, = db.execute("select version from coverage_schema").fetchone() except Exception as exc: raise CoverageException( "Data file {!r} doesn't seem to be a coverage data file: {}".format( @@ -146,22 +148,22 @@ def _open_db(self): ) ) - for row in self._db.execute("select has_lines, has_arcs from meta"): + for row in db.execute("select has_lines, has_arcs from meta"): self._has_lines, self._has_arcs = row - for path, id in self._db.execute("select path, id from file"): + for path, id in db.execute("select path, id from file"): self._file_map[path] = id def _connect(self): - if self._db is None: + if threading.get_ident() not in self._dbs: if os.path.exists(self.filename): self._open_db() else: self._create_db() - return self._db + return self._dbs[threading.get_ident()] def __nonzero__(self): - if self._db is None and not os.path.exists(self.filename): + if threading.get_ident() not in self._dbs and not os.path.exists(self.filename): return False try: with self._connect() as con: From dcd36748c5bf7693b058e3042107e294a49f2712 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sat, 26 Jan 2019 09:34:02 -0500 Subject: [PATCH 2/2] Make thread code Py2 compatible. --- coverage/sqldata.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/coverage/sqldata.py b/coverage/sqldata.py index 67e9da01f..24e5d6b8f 100644 --- a/coverage/sqldata.py +++ b/coverage/sqldata.py @@ -16,14 +16,19 @@ import os import sqlite3 import sys -import threading +from coverage import env from coverage.backward import iitems from coverage.data import filename_suffix from coverage.debug import NoDebugging, SimpleReprMixin from coverage.files import PathAliases from coverage.misc import CoverageException, file_be_gone +if env.PY2: + from thread import get_ident as get_thread_id +else: + from threading import get_ident as get_thread_id + # Schema versions: # 1: Released in 5.0a2 @@ -115,8 +120,8 @@ def _reset(self): def _create_db(self): if self._debug.should('dataio'): self._debug.write("Creating data file {!r}".format(self.filename)) - self._dbs[threading.get_ident()] = Sqlite(self.filename, self._debug) - with self._dbs[threading.get_ident()] as db: + self._dbs[get_thread_id()] = Sqlite(self.filename, self._debug) + with self._dbs[get_thread_id()] as db: for stmt in SCHEMA.split(';'): stmt = " ".join(stmt.strip().split()) if stmt: @@ -130,8 +135,8 @@ def _create_db(self): def _open_db(self): if self._debug.should('dataio'): self._debug.write("Opening data file {!r}".format(self.filename)) - self._dbs[threading.get_ident()] = Sqlite(self.filename, self._debug) - with self._dbs[threading.get_ident()] as db: + self._dbs[get_thread_id()] = Sqlite(self.filename, self._debug) + with self._dbs[get_thread_id()] as db: try: schema_version, = db.execute("select version from coverage_schema").fetchone() except Exception as exc: @@ -155,15 +160,16 @@ def _open_db(self): self._file_map[path] = id def _connect(self): - if threading.get_ident() not in self._dbs: + if get_thread_id() not in self._dbs: if os.path.exists(self.filename): self._open_db() else: self._create_db() - return self._dbs[threading.get_ident()] + return self._dbs[get_thread_id()] def __nonzero__(self): - if threading.get_ident() not in self._dbs and not os.path.exists(self.filename): + if (get_thread_id() not in self._dbs and + not os.path.exists(self.filename)): return False try: with self._connect() as con: