commit 3b9786e3d22b938e7c25fc5a40179208707fd68b Author: Damian Johnson atagar@torproject.org Date: Wed Aug 30 10:32:44 2017 -0700
Schema versioning and cache thread safety
Versioning nyx's cache schema and ensure use of the cache is done in a thread safe fashion. --- nyx/__init__.py | 40 ++++++++++++++++++++++++++++++++++++---- test/cache.py | 31 ++++++++++++++++--------------- 2 files changed, 52 insertions(+), 19 deletions(-)
diff --git a/nyx/__init__.py b/nyx/__init__.py index c7db0cb..ee146a6 100644 --- a/nyx/__init__.py +++ b/nyx/__init__.py @@ -31,6 +31,7 @@ Tor curses monitoring application. +- halt - stops daemon panels """
+import contextlib import distutils.spawn import os import sqlite3 @@ -86,6 +87,14 @@ NYX_INTERFACE = None TOR_CONTROLLER = None BASE_DIR = os.path.sep.join(__file__.split(os.path.sep)[:-1]) CACHE = None +CACHE_LOCK = threading.RLock() + +SCHEMA_VERSION = 1 # version of our scheme, bump this if you change the following +SCHEMA = ( + 'CREATE TABLE schema(version NUMBER)', + 'INSERT INTO schema(version) VALUES (%i)' % SCHEMA_VERSION, +) +
# technically can change but we use this query a *lot* so needs to be cached
@@ -252,6 +261,7 @@ def data_directory(filename, config): return os.path.join(data_dir, filename)
+@contextlib.contextmanager def cache(): """ Provides the sqlite cache for application data. @@ -261,11 +271,33 @@ def cache():
global CACHE
- if CACHE is None: - cache_path = data_directory('cache.sqlite') - CACHE = sqlite3.connect(cache_path if cache_path else ':memory:') + with CACHE_LOCK: + if CACHE is None: + cache_path = data_directory('cache.sqlite') + + if cache_path: + try: + CACHE = sqlite3.connect(cache_path) + schema = CACHE.execute('SELECT version FROM schema').fetchone()[0] + except: + schema = 'no schema' + + if schema != SCHEMA_VERSION: + stem.util.log.info('Cache schema of %s is out of date (has %s but current version is %s). Clearing the cache.' % (cache_path, schema, SCHEMA_VERSION)) + + CACHE.close() + os.remove(cache_path) + CACHE = sqlite3.connect(cache_path) + + for cmd in SCHEMA: + CACHE.execute(cmd) + else: + CACHE = sqlite3.connect(':memory:') + + for cmd in SCHEMA: + CACHE.execute(cmd)
- return CACHE + yield CACHE
@uses_settings diff --git a/test/cache.py b/test/cache.py index dc1fa40..1c30f40 100644 --- a/test/cache.py +++ b/test/cache.py @@ -16,25 +16,26 @@ class TestCache(unittest.TestCase):
@patch('nyx.data_directory', Mock(return_value = None)) def test_memory_cache(self): - cache = nyx.cache() - self.assertEqual((0, 'main', ''), cache.execute("PRAGMA database_list").fetchone()) + with nyx.cache() as cache: + self.assertEqual((0, 'main', ''), cache.execute("PRAGMA database_list").fetchone())
- cache.execute('CREATE TABLE aliases(alias TEXT, command TEXT)') - cache.execute('INSERT INTO aliases(alias, command) VALUES (?,?)', ('l', 'ls -xF --color=auto')) - cache.execute('INSERT INTO aliases(alias, command) VALUES (?,?)', ('ll', 'ls -hlA --color=auto')) - self.assertEqual('ls -hlA --color=auto', cache.execute('SELECT command FROM aliases WHERE alias=?', ('ll',)).fetchone()[0]) + cache.execute('CREATE TABLE aliases(alias TEXT, command TEXT)') + cache.execute('INSERT INTO aliases(alias, command) VALUES (?,?)', ('l', 'ls -xF --color=auto')) + cache.execute('INSERT INTO aliases(alias, command) VALUES (?,?)', ('ll', 'ls -hlA --color=auto')) + self.assertEqual('ls -hlA --color=auto', cache.execute('SELECT command FROM aliases WHERE alias=?', ('ll',)).fetchone()[0])
def test_file_cache(self): with tempfile.NamedTemporaryFile(suffix = '.sqlite') as tmp: with patch('nyx.data_directory', Mock(return_value = tmp.name)): - cache = nyx.cache() - self.assertEqual((0, 'main', tmp.name), cache.execute("PRAGMA database_list").fetchone()) - - cache.execute('CREATE TABLE aliases(alias TEXT, command TEXT)') - cache.execute('INSERT INTO aliases(alias, command) VALUES (?,?)', ('l', 'ls -xF --color=auto')) - cache.execute('INSERT INTO aliases(alias, command) VALUES (?,?)', ('ll', 'ls -hlA --color=auto')) - cache.commit() - cache.close() + with nyx.cache() as cache: + self.assertEqual((0, 'main', tmp.name), cache.execute("PRAGMA database_list").fetchone()) + + cache.execute('CREATE TABLE aliases(alias TEXT, command TEXT)') + cache.execute('INSERT INTO aliases(alias, command) VALUES (?,?)', ('l', 'ls -xF --color=auto')) + cache.execute('INSERT INTO aliases(alias, command) VALUES (?,?)', ('ll', 'ls -hlA --color=auto')) + cache.commit() + nyx.CACHE = None
- self.assertEqual('ls -hlA --color=auto', nyx.cache().execute('SELECT command FROM aliases WHERE alias=?', ('ll',)).fetchone()[0]) + with nyx.cache() as cache: + self.assertEqual('ls -hlA --color=auto', cache.execute('SELECT command FROM aliases WHERE alias=?', ('ll',)).fetchone()[0])