Do not reuse sqlite connection

Prior to this patch, `PersistentDict` has been reusing sqlite
connection for all subsequent calls. This proved to fail in
MT environments.

With this change, `PersistentDict` reopens sqlite connection from
each object and closes it down in hope to make it MT-safe.

Change-Id: Ic3e4618f44236c86e59c62bebfd817b40b5235aa
This commit is contained in:
Ilya Etingof 2019-08-27 14:29:15 +02:00
parent 43f33479f7
commit a0e3057b9a
2 changed files with 22 additions and 25 deletions

View File

@ -19,6 +19,7 @@ try:
except ImportError: except ImportError:
import collections import collections
import contextlib
from functools import wraps from functools import wraps
import os import os
import pickle import pickle
@ -73,18 +74,16 @@ def memoize(permanent_cache=None):
class PersistentDict(MutableMapping): class PersistentDict(MutableMapping):
DBPATH = os.path.join(tempfile.gettempdir(), 'sushy-emulator') DBPATH = os.path.join(tempfile.gettempdir(), 'sushy-emulator')
_connection = None _dbpath = None
def make_permanent(self, dbpath, dbfile): def make_permanent(self, dbpath, dbfile):
dbpath = dbpath or self.DBPATH dbpath = dbpath or self.DBPATH
if not os.path.exists(dbpath): if not os.path.exists(dbpath):
os.makedirs(dbpath) os.makedirs(dbpath)
dbpath = os.path.join(dbpath, dbfile) + '.sqlite'
self._connection = sqlite3.connect(dbpath) self._dbpath = os.path.join(dbpath, dbfile) + '.sqlite'
with self.connection as connection: with self.connection() as cursor:
cursor = connection.cursor()
cursor.execute( cursor.execute(
'create table if not exists cache ' 'create table if not exists cache '
'(key blob primary key not null, value blob not null)' '(key blob primary key not null, value blob not null)'
@ -100,18 +99,25 @@ class PersistentDict(MutableMapping):
def decode(blob): def decode(blob):
return pickle.loads(blob) return pickle.loads(blob)
@property @contextlib.contextmanager
def connection(self): def connection(self):
if not self._connection: if not self._dbpath:
raise TypeError('Dict is not yet persistent') raise TypeError('Dict is not yet persistent')
return self._connection connection = sqlite3.connect(self._dbpath)
try:
yield connection.cursor()
connection.commit()
finally:
connection.close()
def __getitem__(self, key): def __getitem__(self, key):
key = self.encode(key) key = self.encode(key)
with self.connection as connection: with self.connection() as cursor:
cursor = connection.cursor()
cursor.execute( cursor.execute(
'select value from cache where key=?', 'select value from cache where key=?',
(key,) (key,)
@ -127,8 +133,7 @@ class PersistentDict(MutableMapping):
key = self.encode(key) key = self.encode(key)
value = self.encode(value) value = self.encode(value)
with self.connection as connection: with self.connection() as cursor:
cursor = connection.cursor()
cursor.execute( cursor.execute(
'insert or replace into cache values (?, ?)', 'insert or replace into cache values (?, ?)',
(key, value) (key, value)
@ -137,9 +142,7 @@ class PersistentDict(MutableMapping):
def __delitem__(self, key): def __delitem__(self, key):
key = self.encode(key) key = self.encode(key)
with self.connection as connection: with self.connection() as cursor:
cursor = connection.cursor()
cursor.execute( cursor.execute(
'select count(*) from cache where key=?', 'select count(*) from cache where key=?',
(key,) (key,)
@ -154,8 +157,7 @@ class PersistentDict(MutableMapping):
) )
def __iter__(self): def __iter__(self):
with self.connection as connection: with self.connection() as cursor:
cursor = connection.cursor()
cursor.execute( cursor.execute(
'select key from cache' 'select key from cache'
) )
@ -165,9 +167,9 @@ class PersistentDict(MutableMapping):
yield self.decode(r[0]) yield self.decode(r[0])
def __len__(self): def __len__(self):
with self.connection as connection: with self.connection() as cursor:
cursor = connection.cursor()
cursor.execute( cursor.execute(
'select count(*) from cache' 'select count(*) from cache'
) )
return cursor.fetchone()[0] count = cursor.fetchone()[0]
return count

View File

@ -116,7 +116,6 @@ class PersistentDictTestCase(base.BaseTestCase):
mock_pickle.dumps.return_value = 'pickled-key' mock_pickle.dumps.return_value = 'pickled-key'
mock_connection = mock_sqlite3.connect.return_value mock_connection = mock_sqlite3.connect.return_value
mock_connection = mock_connection.__enter__.return_value
mock_cursor = mock_connection.cursor.return_value mock_cursor = mock_connection.cursor.return_value
mock_cursor.fetchone.return_value = ['pickled-value'] mock_cursor.fetchone.return_value = ['pickled-value']
@ -136,7 +135,6 @@ class PersistentDictTestCase(base.BaseTestCase):
'pickled-key', 'pickled-value'] 'pickled-key', 'pickled-value']
mock_connection = mock_sqlite3.connect.return_value mock_connection = mock_sqlite3.connect.return_value
mock_connection = mock_connection.__enter__.return_value
mock_cursor = mock_connection.cursor.return_value mock_cursor = mock_connection.cursor.return_value
pd[1] = 2 pd[1] = 2
@ -153,7 +151,6 @@ class PersistentDictTestCase(base.BaseTestCase):
mock_pickle.dumps.return_value = 'pickled-key' mock_pickle.dumps.return_value = 'pickled-key'
mock_connection = mock_sqlite3.connect.return_value mock_connection = mock_sqlite3.connect.return_value
mock_connection = mock_connection.__enter__.return_value
mock_cursor = mock_connection.cursor.return_value mock_cursor = mock_connection.cursor.return_value
del pd[1] del pd[1]
@ -169,7 +166,6 @@ class PersistentDictTestCase(base.BaseTestCase):
mock_pickle.dumps.return_value = 'pickled-key' mock_pickle.dumps.return_value = 'pickled-key'
mock_connection = mock_sqlite3.connect.return_value mock_connection = mock_sqlite3.connect.return_value
mock_connection = mock_connection.__enter__.return_value
mock_cursor = mock_connection.cursor.return_value mock_cursor = mock_connection.cursor.return_value
mock_cursor.fetchall.return_value = [['pickled-key']] mock_cursor.fetchall.return_value = [['pickled-key']]
@ -186,7 +182,6 @@ class PersistentDictTestCase(base.BaseTestCase):
pd.make_permanent('/', 'file') pd.make_permanent('/', 'file')
mock_connection = mock_sqlite3.connect.return_value mock_connection = mock_sqlite3.connect.return_value
mock_connection = mock_connection.__enter__.return_value
mock_cursor = mock_connection.cursor.return_value mock_cursor = mock_connection.cursor.return_value
expected = 1 expected = 1